@burtson-labs/bandit-engine 2.0.51 → 2.0.52
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/{chat-W5IFNEUC.mjs → chat-YWYLVKXX.mjs} +5 -5
- package/dist/chat-provider.js +225 -8
- package/dist/chat-provider.js.map +1 -1
- package/dist/chat-provider.mjs +3 -3
- package/dist/{chunk-EWUUF4GE.mjs → chunk-37PEP5JK.mjs} +2 -2
- package/dist/{chunk-HETIHZ42.mjs → chunk-M3BEAMCC.mjs} +2 -2
- package/dist/{chunk-QFSEZAG6.mjs → chunk-MH7WFWCP.mjs} +34 -3
- package/dist/chunk-MH7WFWCP.mjs.map +1 -0
- package/dist/{chunk-IDH2YOW3.mjs → chunk-QX6CO7TJ.mjs} +196 -9
- package/dist/chunk-QX6CO7TJ.mjs.map +1 -0
- package/dist/{chunk-LXD3IV6Z.mjs → chunk-RSSJADDD.mjs} +3 -3
- package/dist/{chunk-STMXPFAQ.mjs → chunk-TSQCNHOX.mjs} +4 -4
- package/dist/{chunk-JBXNXSAH.mjs → chunk-Y5N3NSTU.mjs} +459 -180
- package/dist/chunk-Y5N3NSTU.mjs.map +1 -0
- package/dist/{chunk-N7RMUOFB.mjs → chunk-YZ2HJFPQ.mjs} +2 -2
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.js +678 -183
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -8
- package/dist/management/management.js +648 -183
- package/dist/management/management.js.map +1 -1
- package/dist/management/management.mjs +6 -6
- package/dist/modals/chat-modal/chat-modal.js +197 -8
- package/dist/modals/chat-modal/chat-modal.js.map +1 -1
- package/dist/modals/chat-modal/chat-modal.mjs +3 -3
- package/package.json +1 -1
- package/dist/chunk-IDH2YOW3.mjs.map +0 -1
- package/dist/chunk-JBXNXSAH.mjs.map +0 -1
- package/dist/chunk-QFSEZAG6.mjs.map +0 -1
- /package/dist/{chat-W5IFNEUC.mjs.map → chat-YWYLVKXX.mjs.map} +0 -0
- /package/dist/{chunk-EWUUF4GE.mjs.map → chunk-37PEP5JK.mjs.map} +0 -0
- /package/dist/{chunk-HETIHZ42.mjs.map → chunk-M3BEAMCC.mjs.map} +0 -0
- /package/dist/{chunk-LXD3IV6Z.mjs.map → chunk-RSSJADDD.mjs.map} +0 -0
- /package/dist/{chunk-STMXPFAQ.mjs.map → chunk-TSQCNHOX.mjs.map} +0 -0
- /package/dist/{chunk-N7RMUOFB.mjs.map → chunk-YZ2HJFPQ.mjs.map} +0 -0
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/index.ts","../package.json","../src/cli/createQuickstart.ts","../src/cli/utils.ts","../src/cli/templates.ts"],"sourcesContent":["/*\n © 2025 Burtson Labs — Licensed under Business Source License 1.1\n https://burtson.ai/license\n\n This file is protected intellectual property.\n Do NOT use in commercial software, prompts, AI training data, or derivative works without a valid commercial license.\n\n 🚫 AI NOTICE: This file contains visible and invisible watermarks.\n ⚖️ VIOLATION NOTICE: Removing, modifying, or obscuring these watermarks is a license violation.\n 🔒 LICENSE TERMINATION: Upon license termination, ALL forks, copies, and derivatives must be permanently deleted.\n 📋 AUDIT TRAIL: File usage is logged and monitored for compliance verification.\n*/\n\n// Bandit Engine Watermark: BL-WM-4003-ED009B\nconst __banditFingerprint_cli_indexts = 'BL-FP-EBD653-077C';\nconst __auditTrail_cli_indexts = 'BL-AU-MGOIKVV7-9HPQ';\n// File: index.ts | Path: src/cli/index.ts | Hash: 4003077c\n\n/* eslint-disable no-console */\nimport path from \"node:path\";\nimport { Command } from \"commander\";\nimport packageJson from \"../../package.json\";\nimport { createQuickstartProject } from \"./createQuickstart\";\n\nconst logIntro = () => {\n console.log(\"🥷 Bandit CLI — Burtson Labs 🧪\");\n};\n\nconst program = new Command();\n\nprogram\n .name(\"bandit\")\n .description(\"Bandit Engine developer utilities\")\n .version(packageJson.version)\n .showHelpAfterError();\n\nprogram\n .command(\"create\")\n .description(\"Scaffold a Bandit quickstart project with a frontend and gateway\")\n .argument(\"[directory]\", \"Relative path for your new project\", \"bandit-quickstart\")\n .option(\"-f, --force\", \"Overwrite the target directory if it already contains files\", false)\n .option(\"--branding-text <text>\", \"Assistant display name shown in the UI\")\n .option(\n \"--provider <provider>\",\n \"Default gateway provider (openai, azure, anthropic, ollama)\"\n )\n .option(\"--frontend-port <port>\", \"Frontend dev server port (default: 5183)\", (value) =>\n parseInt(value, 10)\n )\n .option(\"--gateway-port <port>\", \"Gateway port (default: 8080)\", (value) => parseInt(value, 10))\n .option(\"-y, --yes\", \"Skip interactive prompts and accept defaults\", false)\n .option(\"--skip-prompts\", \"Alias for --yes\", false)\n .action(async (directory: string, cmdOptions: Record<string, unknown>) => {\n try {\n const targetDir = directory ?? \"bandit-quickstart\";\n const projectName = path.basename(path.resolve(process.cwd(), targetDir));\n logIntro();\n const skipPrompts = Boolean(cmdOptions.skipPrompts ?? cmdOptions.yes);\n const result = await createQuickstartProject({\n targetDir,\n projectName,\n force: Boolean(cmdOptions.force),\n brandingText: cmdOptions.brandingText as string | undefined,\n provider: typeof cmdOptions.provider === \"string\" ? (cmdOptions.provider as string) : undefined,\n frontendPort: Number.isFinite(cmdOptions.frontendPort as number)\n ? (cmdOptions.frontendPort as number)\n : undefined,\n gatewayPort: Number.isFinite(cmdOptions.gatewayPort as number)\n ? (cmdOptions.gatewayPort as number)\n : undefined,\n skipPrompts,\n });\n\n const relativeDir = path.relative(process.cwd(), result.projectDir) || \".\";\n console.log(\"\\n✅ Quickstart ready!\");\n console.log(` Location: ${result.projectDir}`);\n console.log(` Package: ${result.packageName}`);\n console.log(` App name: ${result.brandingText}`);\n console.log(` Frontend: http://localhost:${result.frontendPort}`);\n console.log(` Gateway: http://localhost:${result.gatewayPort}`);\n\n console.log(\"\\nNext steps:\");\n console.log(` cd ${relativeDir}`);\n console.log(\" npm install\");\n console.log(\" cp .env.example .env\");\n console.log(\" npm run dev\");\n console.log(\"\");\n console.log(\"🔍 Before you dive in:\");\n console.log(\" • Open .env to confirm the provider credentials and URLs match your setup.\");\n console.log(\" • server/gateway.js is a scaffold Express proxy that keeps API keys server-side—extend it with auth, logging, and your production logic.\");\n console.log(\" • If you prefer Next.js App Router, check server/next-app/ for a starter route handler.\");\n console.log(\"\");\n } catch (error) {\n const message =\n error instanceof Error ? error.message : \"Failed to create Bandit quickstart project.\";\n console.error(`\\n❌ ${message}`);\n process.exitCode = 1;\n }\n });\n\nasync function main() {\n await program.parseAsync(process.argv);\n}\n\nmain().catch((error) => {\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n});\n","{\n \"name\": \"@burtson-labs/bandit-engine\",\n \"version\": \"2.0.51\",\n \"license\": \"BUSL-1.1\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"types\": \"dist/index.d.ts\",\n \"bin\": {\n \"bandit\": \"dist/cli.js\"\n },\n \"files\": [\n \"dist\",\n \"dist/cli\",\n \"docs\",\n \"LICENSE\",\n \"README.md\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Burtson-Labs/bandit-engine.git\"\n },\n \"homepage\": \"https://banditailabs.com/npm-package\",\n \"author\": \"Burtson Labs LLC <team@banditai.ai>\",\n \"maintainers\": [\n {\n \"name\": \"Burtson Labs LLC\",\n \"email\": \"team@banditai.ai\"\n }\n ],\n \"keywords\": [\n \"ai\",\n \"chat\",\n \"react\",\n \"mui\",\n \"llm\",\n \"frontend\",\n \"openai\",\n \"ollama\",\n \"chatbot\",\n \"sdk\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"docs\": \"typedoc src --out docs/api_reference --skipErrorChecking --sourceLinkTemplate \\\"https://github.com/Burtson-Labs/bandit-engine/blob/main/{path}#L{line}\\\" && node ./scripts/post-typedoc.mjs\",\n \"lint\": \"eslint \\\"src/**/*.{ts,tsx}\\\"\",\n \"test\": \"vitest\",\n \"protect\": \"node scripts/add-license-headers.js\",\n \"validate-protection\": \"node scripts/validate-protection.js\"\n },\n \"overrides\": {\n \"sha.js\": \"^2.4.12\"\n },\n \"dependencies\": {\n \"@emotion/react\": \"^11.14.0\",\n \"@emotion/styled\": \"^11.14.0\",\n \"@mui/icons-material\": \"^7.0.1\",\n \"@mui/material\": \"^7.1.0\",\n \"@tanstack/react-query\": \"^5.66.3\",\n \"axios\": \"^1.7.9\",\n \"commander\": \"^12.1.0\",\n \"fs-extra\": \"^11.3.0\",\n \"highlight.js\": \"^11.10.0\",\n \"idb\": \"latest\",\n \"lowlight\": \"^3.1.0\",\n \"mammoth\": \"^1.9.0\",\n \"pdfjs-dist\": \"^5.2.133\",\n \"prompts\": \"^2.4.2\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"react-markdown\": \"^10.1.0\",\n \"react-router-dom\": \"^7.5.0\",\n \"rehype-raw\": \"^7.0.0\",\n \"rehype-sanitize\": \"^6.0.0\",\n \"remark-gfm\": \"^4.0.1\",\n \"rxjs\": \"^7.8.2\",\n \"uuid\": \"^11.1.0\",\n \"zustand\": \"^4.5.6\"\n },\n \"devDependencies\": {\n \"@testing-library/jest-dom\": \"^6.6.3\",\n \"@testing-library/react\": \"^16.3.0\",\n \"@types/fs-extra\": \"^11.0.4\",\n \"@types/node\": \"^24.0.3\",\n \"@types/prompts\": \"^2.4.9\",\n \"@types/react\": \"^18.3.22\",\n \"@types/react-dom\": \"^18.2.17\",\n \"@types/uuid\": \"^10.0.0\",\n \"@vitejs/plugin-react\": \"^4.6.0\",\n \"eslint\": \"^8.57.0\",\n \"eslint-plugin-react\": \"^7.34.1\",\n \"jsdom\": \"^26.1.0\",\n \"tsup\": \"^8.5.0\",\n \"typedoc\": \"^0.26.11\",\n \"typescript\": \"5.5.4\",\n \"vitest\": \"^3.2.4\"\n },\n \"peerDependencies\": {\n \"@mui/material\": \">=5\",\n \"react\": \">=18\",\n \"zustand\": \">=4\"\n },\n \"exports\": {\n \".\": {\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n },\n \"./types\": {\n \"types\": \"./dist/public-types.d.ts\",\n \"default\": \"./dist/public-types.d.ts\"\n }\n }\n}\n","/*\n © 2025 Burtson Labs — Licensed under Business Source License 1.1\n https://burtson.ai/license\n\n This file is protected intellectual property.\n Do NOT use in commercial software, prompts, AI training data, or derivative works without a valid commercial license.\n\n 🚫 AI NOTICE: This file contains visible and invisible watermarks.\n ⚖️ VIOLATION NOTICE: Removing, modifying, or obscuring these watermarks is a license violation.\n 🔒 LICENSE TERMINATION: Upon license termination, ALL forks, copies, and derivatives must be permanently deleted.\n 📋 AUDIT TRAIL: File usage is logged and monitored for compliance verification.\n*/\n\n// Bandit Engine Watermark: BL-WM-BC17-F7FDDE\nconst __banditFingerprint_cli_createQuickstartts = 'BL-FP-34201E-F7E5';\nconst __auditTrail_cli_createQuickstartts = 'BL-AU-MGOIKVV7-FEUF';\n// File: createQuickstart.ts | Path: src/cli/createQuickstart.ts | Hash: bc17f7e5\n\nimport path from \"node:path\";\nimport fs from \"fs-extra\";\nimport prompts, { PromptObject } from \"prompts\";\nimport packageJson from \"../../package.json\";\nimport {\n ensureTrailingNewline,\n sanitizeModelIdentifier,\n toKebabCase,\n toTitleCase,\n} from \"./utils\";\nimport {\n buildAppTsx,\n buildBrandingConfig,\n buildEnvDts,\n buildEnvExample,\n buildGatewayServer,\n buildGitignore,\n buildIndexCss,\n buildIndexHtml,\n buildMainTsx,\n buildPackageJson,\n buildReadme,\n buildThemeTs,\n buildTsConfig,\n buildViteConfig,\n buildNextChatRoute,\n buildNextHealthRoute,\n buildNextModelsRoute,\n buildNextGatewayReadme,\n QuickstartTemplateContext,\n} from \"./templates\";\n\ntype SupportedProvider = \"openai\" | \"ollama\" | \"azure\" | \"anthropic\" | \"xai\" | \"bandit\";\n\ninterface LogoResolution {\n dataUrl: string;\n fileName: string;\n fileContent: Buffer;\n hasTransparentLogo: boolean;\n isDefault: boolean;\n}\n\ninterface CreateQuickstartInputs {\n targetDir: string;\n projectTitle: string;\n packageName: string;\n brandingText: string;\n provider: SupportedProvider;\n defaultModelId: string;\n fallbackModelId?: string;\n gatewayPort: number;\n frontendPort: number;\n logo: LogoResolution;\n}\n\nexport interface CreateQuickstartOptions {\n targetDir: string;\n projectName?: string;\n force?: boolean;\n provider?: string;\n brandingText?: string;\n defaultModelId?: string;\n fallbackModelId?: string;\n gatewayPort?: number;\n frontendPort?: number;\n skipPrompts?: boolean;\n}\n\ninterface QuickstartResult {\n projectDir: string;\n packageName: string;\n brandingText: string;\n defaultModelId: string;\n fallbackModelId?: string;\n gatewayPort: number;\n frontendPort: number;\n createdFiles: string[];\n}\n\nexport const createQuickstartProject = async (\n options: CreateQuickstartOptions\n): Promise<QuickstartResult> => {\n const resolvedDir = path.resolve(process.cwd(), options.targetDir);\n const rawProjectName = options.projectName ?? path.basename(resolvedDir);\n const packageName = normalizePackageName(rawProjectName);\n const projectTitle = toTitleCase(rawProjectName) || \"Bandit Quickstart\";\n\n await ensureWritableDirectory(resolvedDir, Boolean(options.force));\n\n const skipPrompts = Boolean(options.skipPrompts);\n\n const provider = options.provider\n ? normalizeProvider(options.provider)\n : skipPrompts\n ? \"ollama\"\n : await promptForProvider();\n\n const promptAnswers = skipPrompts\n ? {}\n : await promptForMissingData({\n brandingText: options.brandingText,\n provider,\n });\n\n const brandingText =\n options.brandingText ??\n (typeof promptAnswers.brandingText === \"string\" && promptAnswers.brandingText.trim().length > 0\n ? promptAnswers.brandingText.trim()\n : `${projectTitle} Assistant`);\n\n // Package handles CDN logos automatically, no need for logo resolution\n const logoResolution: LogoResolution = {\n dataUrl: \"\",\n fileName: \"\",\n fileContent: Buffer.alloc(0),\n hasTransparentLogo: true,\n isDefault: true\n };\n\n const gatewayPort = sanitizePort(options.gatewayPort ?? promptAnswers.gatewayPort ?? 5151);\n const frontendPort = sanitizePort(options.frontendPort ?? promptAnswers.frontendPort ?? 5183);\n const defaultModelId = sanitizeModelIdentifier(\n options.defaultModelId ?? inferDefaultModelId(provider)\n );\n const fallbackModelRaw = options.fallbackModelId\n ? options.fallbackModelId\n : inferFallbackModelId(provider, defaultModelId);\n const fallbackModelId = fallbackModelRaw\n ? sanitizeModelIdentifier(fallbackModelRaw)\n : undefined;\n\n const inputs: CreateQuickstartInputs = {\n targetDir: resolvedDir,\n projectTitle,\n packageName,\n brandingText,\n provider,\n defaultModelId,\n fallbackModelId,\n gatewayPort,\n frontendPort,\n logo: logoResolution,\n };\n\n const createdFiles = await writeProject(inputs);\n\n return {\n projectDir: resolvedDir,\n packageName,\n brandingText,\n defaultModelId,\n fallbackModelId,\n gatewayPort,\n frontendPort,\n createdFiles,\n };\n};\n\nconst normalizePackageName = (input: string): string => {\n const fallback = \"bandit-quickstart\";\n const kebab = toKebabCase(input || fallback);\n if (!kebab) {\n return fallback;\n }\n return /^[a-z]/.test(kebab) ? kebab : `bandit-${kebab}`;\n};\n\nconst ensureWritableDirectory = async (dir: string, force: boolean) => {\n const exists = await fs.pathExists(dir);\n if (!exists) {\n await fs.ensureDir(dir);\n return;\n }\n\n const entries = await fs.readdir(dir);\n if (entries.length > 0 && !force) {\n throw new Error(\n `Target directory \"${dir}\" is not empty. Re-run with --force to overwrite or choose a new folder.`\n );\n }\n};\n\nconst normalizeProvider = (value?: string): SupportedProvider => {\n const normalized = (value ?? \"openai\").toLowerCase();\n if (normalized === \"ollama\") {\n return \"ollama\";\n }\n if (normalized === \"azure\" || normalized === \"azure-openai\" || normalized === \"azureopenai\") {\n return \"azure\";\n }\n if (normalized === \"anthropic\") {\n return \"anthropic\";\n }\n if (normalized === \"xai\" || normalized === \"grok\") {\n return \"xai\";\n }\n if (normalized === \"bandit\" || normalized === \"banditai\" || normalized === \"bandit-ai\") {\n return \"bandit\";\n }\n return \"openai\";\n};\n\nconst inferDefaultModelId = (provider: SupportedProvider): string => {\n if (provider === \"ollama\") {\n return \"llama3.1\";\n }\n if (provider === \"azure\") {\n return \"azure:gpt-4o\";\n }\n if (provider === \"anthropic\") {\n return \"anthropic:claude-3-5-haiku-latest\";\n }\n if (provider === \"xai\") {\n return \"xai:grok-2-latest\";\n }\n if (provider === \"bandit\") {\n return \"bandit-core-1\";\n }\n return \"openai:gpt-4o-mini\";\n};\n\nconst inferFallbackModelId = (provider: SupportedProvider, defaultId: string): string | undefined => {\n if (provider === \"ollama\") {\n const normalized = defaultId.toLowerCase();\n if (normalized.startsWith(\"llama3\")) {\n return \"llama2\";\n }\n return \"llama3\";\n }\n if (provider === \"azure\") {\n return defaultId === \"azure:gpt-4o-mini\" ? \"azure:gpt-4o\" : \"azure:gpt-4o-mini\";\n }\n if (provider === \"anthropic\") {\n return defaultId === \"anthropic:claude-3-5-haiku-latest\"\n ? \"anthropic:claude-3-haiku-20240307\"\n : \"anthropic:claude-3-5-haiku-latest\";\n }\n if (provider === \"xai\") {\n return defaultId === \"xai:grok-2-mini\" ? \"xai:grok-2-latest\" : \"xai:grok-2-mini\";\n }\n if (provider === \"bandit\") {\n return undefined;\n }\n return defaultId === \"openai:gpt-4.1-mini\" ? \"openai:gpt-4o-mini\" : \"openai:gpt-4.1-mini\";\n};\n\nconst promptForProvider = async (): Promise<SupportedProvider> => {\n const providerOptions: { label: string; value: SupportedProvider; description?: string }[] = [\n { label: \"Ollama (self-hosted) — default\", value: \"ollama\" },\n { label: \"OpenAI\", value: \"openai\" },\n { label: \"Bandit AI (Bandit Core)\", value: \"bandit\" },\n { label: \"Azure OpenAI\", value: \"azure\" },\n { label: \"Anthropic\", value: \"anthropic\" },\n { label: \"xAI (Grok)\", value: \"xai\" },\n ];\n\n const messageLines = [\n \"Which provider should we configure for the gateway?\",\n ...providerOptions.map((option, index) => ` ${index + 1}. ${option.label}`),\n \"Enter a number:\",\n ];\n\n const onCancel = () => {\n throw new Error(\"Command cancelled.\");\n };\n\n const answers = await prompts(\n {\n type: \"number\",\n name: \"providerIndex\",\n message: messageLines.join(\"\\n\"),\n initial: 1,\n validate: (input) => {\n if (!Number.isInteger(input)) {\n return \"Enter a whole number.\";\n }\n return input >= 1 && input <= providerOptions.length\n ? true\n : `Enter a number between 1 and ${providerOptions.length}.`;\n },\n },\n { onCancel }\n );\n\n const selectedIndex =\n typeof answers.providerIndex === \"number\" && answers.providerIndex >= 1\n ? answers.providerIndex - 1\n : 0;\n\n return providerOptions[selectedIndex]?.value ?? \"ollama\";\n};\n\nconst sanitizePort = (value: number): number => {\n const port = Number(value);\n if (Number.isNaN(port) || port <= 0 || port >= 65535) {\n return 8080;\n }\n return port;\n};\n\nconst promptForMissingData = async (options: {\n brandingText?: string;\n provider: SupportedProvider;\n}): Promise<{\n brandingText?: string;\n gatewayPort?: number;\n frontendPort?: number;\n}> => {\n const questions: PromptObject[] = [];\n\n if (!options.brandingText) {\n questions.push({\n type: \"text\",\n name: \"brandingText\",\n message: \"What should we display for the app name? (Press Enter to accept)\",\n initial: \"Bandit Quickstart\",\n });\n }\n\n const defaultGatewayPort = options.provider === \"ollama\" ? 11435 : 8080;\n const defaultFrontendPort = 5183;\n\n questions.push({\n type: \"text\",\n name: \"frontendPort\",\n message: \"Frontend port (Press Enter to accept)\",\n initial: String(defaultFrontendPort),\n validate: (value: unknown) => {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n return true;\n }\n const numericValue = Number(value);\n return Number.isFinite(numericValue) && numericValue > 0 && numericValue < 65535\n ? true\n : \"Enter a port between 1 and 65535\";\n },\n });\n\n questions.push({\n type: \"text\",\n name: \"gatewayPort\",\n message: \"Gateway port (Press Enter to accept)\",\n initial: String(defaultGatewayPort),\n validate: (value: unknown) => {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n return true;\n }\n const numericValue = Number(value);\n return Number.isFinite(numericValue) && numericValue > 0 && numericValue < 65535\n ? true\n : \"Enter a port between 1 and 65535\";\n },\n });\n\n const onCancel = () => {\n throw new Error(\"Command cancelled.\");\n };\n\n const answers = await prompts(questions, { onCancel });\n\n const parsedGatewayPort =\n typeof answers.gatewayPort === \"number\"\n ? answers.gatewayPort\n : typeof answers.gatewayPort === \"string\" && answers.gatewayPort.trim().length > 0\n ? Number(answers.gatewayPort)\n : defaultGatewayPort;\n\n const parsedFrontendPort =\n typeof answers.frontendPort === \"number\"\n ? answers.frontendPort\n : typeof answers.frontendPort === \"string\" && answers.frontendPort.trim().length > 0\n ? Number(answers.frontendPort)\n : defaultFrontendPort;\n\n return {\n brandingText: typeof answers.brandingText === \"string\" ? answers.brandingText : undefined,\n gatewayPort: Number.isFinite(parsedGatewayPort) ? parsedGatewayPort : defaultGatewayPort,\n frontendPort: Number.isFinite(parsedFrontendPort) ? parsedFrontendPort : defaultFrontendPort,\n };\n};\n\nconst writeProject = async (inputs: CreateQuickstartInputs): Promise<string[]> => {\n const { targetDir } = inputs;\n const createdFiles: string[] = [];\n\n const context: QuickstartTemplateContext = {\n packageName: inputs.packageName,\n projectTitle: inputs.projectTitle,\n engineVersion: packageJson.version,\n brandingText: inputs.brandingText,\n logoBase64: inputs.logo.dataUrl,\n hasTransparentLogo: inputs.logo.hasTransparentLogo,\n isDefaultLogo: inputs.logo.isDefault,\n gatewayPort: inputs.gatewayPort,\n frontendPort: inputs.frontendPort,\n defaultProvider: inputs.provider,\n defaultGatewayUrl: `http://localhost:${inputs.gatewayPort}`,\n defaultModelId: inputs.defaultModelId,\n fallbackModelId: inputs.fallbackModelId,\n gatewayModels: buildGatewayModels(inputs),\n };\n\n const files: Record<string, string | Buffer> = {\n \"package.json\": buildPackageJson(context),\n \"tsconfig.json\": buildTsConfig(),\n \"src/env.d.ts\": buildEnvDts(),\n \"vite.config.ts\": buildViteConfig(context),\n \"src/main.tsx\": buildMainTsx(),\n \"index.html\": buildIndexHtml(),\n \"src/App.tsx\": buildAppTsx(context),\n \"src/index.css\": buildIndexCss(),\n \"src/theme.ts\": buildThemeTs(),\n \"public/config.json\": buildBrandingConfig(context),\n \"server/gateway.js\": buildGatewayServer(context),\n \"server/next-app/app/api/chat/completions/route.ts\": buildNextChatRoute(context),\n \"server/next-app/app/api/health/route.ts\": buildNextHealthRoute(),\n \"server/next-app/app/api/models/route.ts\": buildNextModelsRoute(context),\n \"server/next-app/README.md\": buildNextGatewayReadme(),\n \".env.example\": buildEnvExample(context),\n \".gitignore\": buildGitignore(),\n \"README.md\": buildReadme(context),\n };\n\n // Only add logo file if it's not a default logo (i.e., user provided custom logo)\n if (!inputs.logo.isDefault && inputs.logo.fileName) {\n files[path.posix.join(\"public\", inputs.logo.fileName)] = inputs.logo.fileContent;\n }\n\n for (const [relativePath, content] of Object.entries(files)) {\n const destination = path.join(targetDir, relativePath);\n await fs.ensureDir(path.dirname(destination));\n if (Buffer.isBuffer(content)) {\n await fs.writeFile(destination, content);\n } else {\n await fs.writeFile(destination, ensureTrailingNewline(content), \"utf8\");\n }\n createdFiles.push(relativePath);\n }\n\n return createdFiles;\n};\n\nconst buildGatewayModels = (inputs: CreateQuickstartInputs) => {\n const seen = new Set<string>();\n const models: { id: string; name: string; provider: string }[] = [];\n\n const pushModel = (modelId: string | undefined) => {\n if (!modelId) return;\n if (seen.has(modelId)) return;\n seen.add(modelId);\n const provider = modelId.includes(\":\") ? modelId.split(\":\")[0] : inputs.provider;\n const nameSegment = modelId.includes(\":\") ? modelId.split(\":\")[1] : modelId;\n models.push({\n id: modelId,\n name: toTitleCase(nameSegment.replace(/[-_.]/g, \" \")),\n provider,\n });\n };\n\n pushModel(inputs.defaultModelId);\n pushModel(inputs.fallbackModelId);\n\n return models;\n};\n","/*\n © 2025 Burtson Labs — Licensed under Business Source License 1.1\n https://burtson.ai/license\n\n This file is protected intellectual property.\n Do NOT use in commercial software, prompts, AI training data, or derivative works without a valid commercial license.\n\n 🚫 AI NOTICE: This file contains visible and invisible watermarks.\n ⚖️ VIOLATION NOTICE: Removing, modifying, or obscuring these watermarks is a license violation.\n 🔒 LICENSE TERMINATION: Upon license termination, ALL forks, copies, and derivatives must be permanently deleted.\n 📋 AUDIT TRAIL: File usage is logged and monitored for compliance verification.\n*/\n\n// Bandit Engine Watermark: BL-WM-7A26-5275EA\nconst __banditFingerprint_cli_utilsts = 'BL-FP-62E546-D66C';\nconst __auditTrail_cli_utilsts = 'BL-AU-MGOIKVV7-1K3Q';\n// File: utils.ts | Path: src/cli/utils.ts | Hash: 7a26d66c\n\nimport path from \"node:path\";\n\nexport const toKebabCase = (value: string): string => {\n return value\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .replace(/[\\s_./]+/g, \"-\")\n .replace(/[^a-zA-Z0-9-]+/g, \"\")\n .replace(/-{2,}/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .toLowerCase();\n};\n\nexport const toTitleCase = (value: string): string => {\n const cleaned = value\n .replace(/[-_/]+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n\n if (!cleaned) {\n return \"\";\n }\n\n return cleaned.replace(/\\b\\w/g, (char) => char.toUpperCase());\n};\n\nexport const formatJson = (value: unknown): string =>\n `${JSON.stringify(value, null, 2)}\\n`;\n\nexport const detectMimeType = (fileNameOrExtension: string): string => {\n const extension = path.extname(fileNameOrExtension).toLowerCase() || fileNameOrExtension.toLowerCase();\n switch (extension) {\n case \".svg\":\n case \"svg\":\n return \"image/svg+xml\";\n case \".png\":\n case \"png\":\n return \"image/png\";\n case \".jpg\":\n case \".jpeg\":\n case \"jpg\":\n case \"jpeg\":\n return \"image/jpeg\";\n case \".webp\":\n case \"webp\":\n return \"image/webp\";\n default:\n return \"application/octet-stream\";\n }\n};\n\nexport const toDataUrl = (buffer: Buffer, mimeType: string): string =>\n `data:${mimeType};base64,${buffer.toString(\"base64\")}`;\n\nconst KNOWN_PROVIDERS = new Set([\"openai\", \"azure\", \"azure-openai\", \"azureopenai\", \"anthropic\", \"xai\", \"ollama\"]);\n\nexport const sanitizeModelIdentifier = (value: string): string => {\n const trimmed = value.trim();\n if (!trimmed.includes(\":\")) {\n return trimmed.toLowerCase();\n }\n\n const segments = trimmed.split(/:(.+)/).filter(Boolean);\n if (segments.length < 2) {\n return trimmed.toLowerCase();\n }\n\n const [candidateProvider, rest] = segments as [string, string];\n const provider = candidateProvider.toLowerCase();\n const cleanRest = rest\n .trim()\n .replace(/[^a-zA-Z0-9_.:-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .toLowerCase();\n\n if (KNOWN_PROVIDERS.has(provider)) {\n if (provider === \"azure-openai\" || provider === \"azureopenai\") {\n return `azure:${cleanRest}`;\n }\n if (provider === \"ollama\") {\n return cleanRest;\n }\n return `${provider}:${cleanRest}`;\n }\n\n return [candidateProvider, rest]\n .filter(Boolean)\n .join(\":\")\n .replace(/[^a-zA-Z0-9_.:-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .toLowerCase();\n};\n\nexport const normalizeLineEndings = (content: string): string =>\n content.replace(/\\r\\n/g, \"\\n\");\n\nexport const ensureTrailingNewline = (content: string): string =>\n content.endsWith(\"\\n\") ? content : `${content}\\n`;\n","/*\n © 2025 Burtson Labs — Licensed under Business Source License 1.1\n https://burtson.ai/license\n\n This file is protected intellectual property.\n Do NOT use in commercial software, prompts, AI training data, or derivative works without a valid commercial license.\n\n 🚫 AI NOTICE: This file contains visible and invisible watermarks.\n ⚖️ VIOLATION NOTICE: Removing, modifying, or obscuring these watermarks is a license violation.\n 🔒 LICENSE TERMINATION: Upon license termination, ALL forks, copies, and derivatives must be permanently deleted.\n 📋 AUDIT TRAIL: File usage is logged and monitored for compliance verification.\n*/\n\n// Bandit Engine Watermark: BL-WM-6A01-0C8694\nconst __banditFingerprint_cli_templatests = 'BL-FP-49CAAA-1041';\nconst __auditTrail_cli_templatests = 'BL-AU-MGOIKVV7-J6PV';\n// File: templates.ts | Path: src/cli/templates.ts | Hash: 6a011041\n\nimport { formatJson, ensureTrailingNewline, normalizeLineEndings } from \"./utils\";\n\nexport interface GatewayModelTemplate {\n id: string;\n name: string;\n provider: string;\n}\n\nexport interface QuickstartTemplateContext {\n packageName: string;\n projectTitle: string;\n engineVersion: string;\n brandingText: string;\n logoBase64: string;\n hasTransparentLogo: boolean;\n isDefaultLogo: boolean;\n gatewayPort: number;\n frontendPort: number;\n defaultProvider: \"openai\" | \"ollama\" | \"azure\" | \"anthropic\" | \"xai\" | \"bandit\";\n defaultGatewayUrl: string;\n defaultModelId: string;\n fallbackModelId?: string;\n gatewayModels: GatewayModelTemplate[];\n}\n\nconst QUOTE = '\"';\n\nexport const buildPackageJson = (ctx: QuickstartTemplateContext): string =>\n formatJson({\n name: ctx.packageName,\n private: true,\n version: \"0.1.0\",\n type: \"module\",\n scripts: {\n dev: \"concurrently -k \\\"npm run dev:gateway\\\" \\\"npm run dev:web\\\"\",\n \"dev:web\": \"vite\",\n \"dev:gateway\": \"node server/gateway.js\",\n build: \"vite build\",\n preview: \"vite preview\"\n },\n dependencies: {\n \"@burtson-labs/bandit-engine\": `^${ctx.engineVersion}`,\n \"@emotion/react\": \"^11.14.0\",\n \"@emotion/styled\": \"^11.14.0\",\n \"@mui/material\": \"^7.1.0\",\n \"@tanstack/react-query\": \"^5.59.20\",\n \"cors\": \"^2.8.5\",\n \"dotenv\": \"^16.4.5\",\n \"express\": \"^4.19.2\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"react-router-dom\": \"^7.5.0\",\n \"zustand\": \"^4.5.6\"\n },\n devDependencies: {\n \"@types/express\": \"^4.17.21\",\n \"@types/node\": \"^20.17.7\",\n \"@types/react\": \"^18.3.22\",\n \"@types/react-dom\": \"^18.2.18\",\n \"@vitejs/plugin-react\": \"^5.0.0\",\n \"concurrently\": \"^8.2.2\",\n \"typescript\": \"^5.5.4\",\n \"vite\": \"^7.1.9\"\n }\n });\n\nexport const buildEnvExample = (ctx: QuickstartTemplateContext): string => {\n const lines: string[] = [\n \"# Frontend configuration\",\n `VITE_DEV_PORT=${ctx.frontendPort}`,\n `VITE_GATEWAY_URL=${ctx.defaultGatewayUrl}`,\n `VITE_DEFAULT_MODEL=${ctx.defaultModelId}`,\n `VITE_FALLBACK_MODEL=${ctx.fallbackModelId ?? \"\"}`,\n `VITE_GATEWAY_PROVIDER=${ctx.defaultProvider}`,\n `VITE_BRANDING_TEXT=${ctx.brandingText}`,\n \"\",\n \"# Gateway configuration\",\n \"# These values power server/gateway.js — update them before running in production.\",\n ];\n\n switch (ctx.defaultProvider) {\n case \"openai\":\n lines.push(\"OPENAI_API_KEY=\");\n break;\n case \"azure\":\n lines.push(\"AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com\");\n lines.push(\"AZURE_OPENAI_API_KEY=\");\n lines.push(\"AZURE_OPENAI_API_VERSION=2024-08-01-preview\");\n lines.push(\"AZURE_OPENAI_CHAT_DEPLOYMENT=gpt-4o\");\n lines.push(\"AZURE_OPENAI_COMPLETIONS_DEPLOYMENT=gpt-35-turbo-instruct\");\n lines.push(\"AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT=text-embedding-3-large\");\n break;\n case \"anthropic\":\n lines.push(\"ANTHROPIC_API_KEY=\");\n lines.push(\"ANTHROPIC_BASE_URL=https://api.anthropic.com\");\n lines.push(\"ANTHROPIC_API_VERSION=2023-06-01\");\n lines.push(\"ANTHROPIC_MAX_TOKENS=1024\");\n break;\n case \"xai\":\n lines.push(\"XAI_API_KEY=\");\n lines.push(\"XAI_BASE_URL=https://api.x.ai/v1\");\n break;\n case \"bandit\":\n lines.push(\"BANDIT_API_KEY=\");\n lines.push(\"BANDIT_BASE_URL=https://api.burtson.ai\");\n break;\n case \"ollama\":\n default:\n lines.push(\"OLLAMA_URL=http://localhost:11434\");\n break;\n }\n\n lines.push(`PORT=${ctx.gatewayPort}`);\n lines.push(\n \"# If you switch providers later, copy the relevant block above and update the credentials.\"\n );\n\n return ensureTrailingNewline(normalizeLineEndings(lines.join(\"\\n\")));\n};\n\nexport const buildTsConfig = (): string =>\n formatJson({\n compilerOptions: {\n target: \"ESNext\",\n useDefineForClassFields: true,\n lib: [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n allowJs: false,\n skipLibCheck: true,\n esModuleInterop: true,\n allowSyntheticDefaultImports: true,\n strict: true,\n forceConsistentCasingInFileNames: true,\n module: \"ESNext\",\n moduleResolution: \"Node\",\n resolveJsonModule: true,\n isolatedModules: true,\n noEmit: true,\n jsx: \"react-jsx\"\n },\n include: [\"src\"]\n });\n\nexport const buildEnvDts = (): string =>\n ensureTrailingNewline(\n `/// <reference types=\"vite/client\" />\\n\\ninterface ImportMetaEnv {\\n readonly VITE_GATEWAY_URL?: string;\\n readonly VITE_GATEWAY_PROVIDER?: string;\\n readonly VITE_DEFAULT_MODEL?: string;\\n readonly VITE_FALLBACK_MODEL?: string;\\n readonly VITE_FEEDBACK_EMAIL?: string;\\n readonly VITE_BRANDING_TEXT?: string;\\n}\\n\\ninterface ImportMeta {\\n readonly env: ImportMetaEnv;\\n}\\n`\n );\n\nexport const buildViteConfig = (ctx: QuickstartTemplateContext): string =>\n ensureTrailingNewline(\n normalizeLineEndings(\n `import { defineConfig, loadEnv } from \"vite\";\\nimport react from \"@vitejs/plugin-react\";\\n\\nexport default defineConfig(({ mode }) => {\\n const env = loadEnv(mode, process.cwd(), \"\");\\n const parsedPort = Number(env.VITE_DEV_PORT || env.PORT || ${ctx.frontendPort});\\n const port = Number.isFinite(parsedPort) ? parsedPort : ${ctx.frontendPort};\\n\\n return {\\n plugins: [react()],\\n resolve: {\\n dedupe: [\\n \"react\",\\n \"react-dom\",\\n \"@mui/material\",\\n \"@mui/system\",\\n \"@emotion/react\",\\n \"@emotion/styled\",\n \"react-router-dom\"\\n ],\\n },\\n optimizeDeps: {\\n include: [\"@burtson-labs/bandit-engine\"],\\n },\\n server: {\\n port,\\n },\\n };\\n});\\n`\n )\n );\n\nexport const buildMainTsx = (): string =>\n ensureTrailingNewline(\n normalizeLineEndings(\n `import React from \"react\";\\nimport ReactDOM from \"react-dom/client\";\\nimport { BrowserRouter } from \"react-router-dom\";\\nimport App from \"./App\";\\nimport \"./index.css\";\\n\\nReactDOM.createRoot(document.getElementById(\"root\")!).render(\\n <React.StrictMode>\\n <BrowserRouter>\\n <App />\\n </BrowserRouter>\\n </React.StrictMode>\\n);\\n`\n )\n );\n\nexport const buildIndexCss = (): string =>\n ensureTrailingNewline(\n normalizeLineEndings(\n `:root {\\n font-family: \"Inter\", system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\\n background: radial-gradient(circle at top left, rgba(120, 119, 255, 0.12), transparent 45%),\\n radial-gradient(circle at bottom right, rgba(244, 114, 182, 0.1), transparent 55%),\\n #05070f;\\n color: #f8fafc;\\n min-height: 100vh;\\n}\\n\\nbody {\\n margin: 0;\\n}\\n\\n* {\\n box-sizing: border-box;\\n}\\n`\n )\n );\n\nexport const buildIndexHtml = (): string =>\n ensureTrailingNewline(\n normalizeLineEndings(\n `<!doctype html>\\n<html lang=\"en\">\\n <head>\\n <meta charset=\"UTF-8\" />\\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\\n <link rel=\"icon\" href=\"https://cdn.burtson.ai/images/bandit-head.png\" />\\n <title>Bandit Quickstart</title>\\n </head>\\n <body>\\n <div id=\"root\"></div>\\n <script type=\"module\" src=\"/src/main.tsx\"></script>\\n </body>\\n</html>\\n`\n )\n );\n\nexport const buildThemeTs = (): string =>\n ensureTrailingNewline(\n normalizeLineEndings(\n `import { createTheme } from \"@mui/material/styles\";\\n\\nexport const banditQuickstartTheme = createTheme({\\n palette: {\\n mode: \"dark\",\\n primary: {\\n main: \"#f97316\",\\n },\\n secondary: {\\n main: \"#6366f1\",\\n },\\n background: {\\n default: \"#05070f\",\\n paper: \"rgba(15, 23, 42, 0.78)\",\\n },\\n },\\n typography: {\\n fontFamily: '\"Inter\", system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif',\\n h1: {\\n fontWeight: 700,\\n },\\n h2: {\\n fontWeight: 600,\\n },\\n },\\n components: {\\n MuiPaper: {\\n styleOverrides: {\\n root: {\\n backdropFilter: \"blur(18px)\",\\n backgroundImage: \"linear-gradient(145deg, rgba(15, 23, 42, 0.92), rgba(2, 6, 23, 0.92))\",\\n },\\n },\\n },\\n },\\n});\\n`\n )\n );\n\nexport const buildAppTsx = (ctx: QuickstartTemplateContext): string => {\n const responseStatusExpr = \"${response.status}\";\n const gatewayErrorExpr = '${gatewayError ?? \"Unknown\"}';\n\n const template = `\nimport { useEffect, useState, useMemo, useCallback, type ReactNode } from \"react\";\nimport {\n CssBaseline,\n AppBar,\n Toolbar,\n Typography,\n Container,\n Box,\n Button,\n Chip,\n Tooltip,\n Stack,\n Card,\n CardContent\n} from \"@mui/material\";\nimport { ThemeProvider } from \"@mui/material/styles\";\nimport { Routes, Route, Navigate, Link as RouterLink, useLocation } from \"react-router-dom\";\nimport { ChatProvider, Chat, ChatModal, Management } from \"@burtson-labs/bandit-engine\";\nimport * as BanditEngine from \"@burtson-labs/bandit-engine\";\nimport { banditQuickstartTheme } from \"./theme\";\n\nconst gatewayBaseUrl = (import.meta.env.VITE_GATEWAY_URL ?? \"${ctx.defaultGatewayUrl}\").replace(/\\\\/$/, \"\");\nconst defaultModelId = import.meta.env.VITE_DEFAULT_MODEL ?? \"${ctx.defaultModelId}\";\nconst fallbackModelId = import.meta.env.VITE_FALLBACK_MODEL ?? ${ctx.fallbackModelId ? `${QUOTE}${ctx.fallbackModelId}${QUOTE}` : \"undefined\"};\nconst brandingText = import.meta.env.VITE_BRANDING_TEXT ?? \"${ctx.brandingText}\";\nconst provider = (import.meta.env.VITE_GATEWAY_PROVIDER ?? \"${ctx.defaultProvider}\") as \"openai\" | \"ollama\" | \"azure\" | \"anthropic\" | \"xai\" | \"bandit\";\n\nconst gatewayApiUrl = gatewayBaseUrl.endsWith(\"/api\") ? gatewayBaseUrl : gatewayBaseUrl + \"/api\";\nconst banditHeadLogoUrl = \"https://cdn.burtson.ai/images/bandit-head.png\";\nconst burtsonLabsLogoUrl = \"https://cdn.burtson.ai/logos/burtson-labs-logo-alt.png\";\nconst healthEndpoint = gatewayApiUrl + \"/health\";\n\n// Move packageSettings outside the component to prevent recreation on every render\nconst packageSettings = {\n defaultModel: defaultModelId,\n fallbackModel: fallbackModelId,\n gatewayApiUrl: gatewayApiUrl,\n brandingConfigUrl: \"/config.json\",\n aiProvider: {\n type: \"gateway\" as const,\n gatewayUrl: gatewayApiUrl,\n provider,\n tokenFactory: () => {\n return localStorage.getItem(\"authToken\");\n }\n },\n feedbackEmail: import.meta.env.VITE_FEEDBACK_EMAIL,\n featureFlags: {\n subscriptionType: \"premium\" as const,\n rolesClaimKey: \"roles\",\n subscriptionTypeClaimKey: \"subscriptionType\", \n isSubscribedClaimKey: \"isSubscribed\",\n jwtStorageKey: \"authToken\",\n adminRole: \"admin\",\n debug: true,\n featureMatrix: {\n tts: false,\n stt: false,\n semanticSearchSimple: false,\n semanticSearchPremium: false,\n advancedSearch: false,\n advancedMemories: false,\n },\n },\n};\n\nconst seedQuickstartAuth = () => {\n if (typeof window === \"undefined\" || typeof localStorage === \"undefined\") {\n return;\n }\n\n const applyAuthToken = (token: string) => {\n localStorage.setItem(\"authToken\", token);\n const maybeService = (BanditEngine as { authenticationService?: { setToken: (token: string) => void } }).authenticationService;\n try {\n maybeService?.setToken(token);\n } catch (error) {\n console.warn(\"Bandit quickstart: failed to seed authentication service token\", error);\n }\n };\n\n const existing = localStorage.getItem(\"authToken\");\n if (existing) {\n applyAuthToken(existing);\n return;\n }\n\n const header = {\n alg: \"HS256\",\n typ: \"JWT\",\n };\n const payload = {\n exp: Math.floor(Date.now() / 1000) + 60 * 60 * 8,\n roles: [\"admin\"],\n iat: Math.floor(Date.now() / 1000),\n email: \"quickstart@burtson.ai\",\n sub: \"123456789012345678901\",\n };\n const encodeSegment = (value: unknown) =>\n btoa(JSON.stringify(value))\n .replace(/=+$/g, \"\")\n .replace(/\\\\+/g, \"-\")\n .replace(/\\\\//g, \"_\");\n const mockToken = \\`${\"${\"}encodeSegment(header)}.${\"${\"}encodeSegment(payload)}.quickstart\\`;\n applyAuthToken(mockToken);\n};\n\nseedQuickstartAuth();\n\nfunction App() {\n const location = useLocation();\n const [isModalOpen, setIsModalOpen] = useState(false);\n const [gatewayStatus, setGatewayStatus] = useState<\"checking\" | \"healthy\" | \"error\">(\"checking\");\n const [gatewayError, setGatewayError] = useState<string | null>(null);\n\n // Separate effect for health checking to avoid re-renders\n useEffect(() => {\n let cancelled = false;\n\n const checkHealth = async () => {\n try {\n const response = await fetch(healthEndpoint, { headers: { \"Content-Type\": \"application/json\" } });\n if (!response.ok) {\n throw new Error(\\`HTTP __RESPONSE_STATUS__\\`);\n }\n await response.json();\n if (!cancelled) {\n setGatewayStatus(prevStatus => prevStatus !== \"healthy\" ? \"healthy\" : prevStatus);\n setGatewayError(prevError => prevError !== null ? null : prevError);\n }\n } catch (error) {\n if (!cancelled) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n setGatewayStatus(prevStatus => prevStatus !== \"error\" ? \"error\" : prevStatus);\n setGatewayError(prevError => prevError !== errorMessage ? errorMessage : prevError);\n }\n }\n };\n\n // Initial check\n checkHealth();\n \n // Set up interval for periodic checks\n const interval = setInterval(checkHealth, 15000);\n \n return () => {\n cancelled = true;\n clearInterval(interval);\n };\n }, []);\n\n const gatewayChip = useMemo(() => (\n <Tooltip\n title={\n gatewayStatus === \"error\"\n ? \\`Gateway health check failed. Last error: __GATEWAY_ERROR__\\`\n : gatewayStatus === \"healthy\"\n ? \"Gateway reachable\"\n : \"Checking gateway health...\"\n }\n >\n <Chip\n size=\"small\"\n color={gatewayStatus === \"healthy\" ? \"success\" : gatewayStatus === \"error\" ? \"error\" : \"default\"}\n variant={gatewayStatus === \"healthy\" ? \"filled\" : \"outlined\"}\n label={\n gatewayStatus === \"healthy\"\n ? \"Gateway: Healthy\"\n : gatewayStatus === \"error\"\n ? \"Gateway: Unreachable\"\n : \"Gateway: Checking...\"\n }\n sx={{ fontWeight: 600 }}\n />\n </Tooltip>\n ), [gatewayStatus, gatewayError]);\n\n const handleOpenModal = useCallback(() => setIsModalOpen(true), []);\n const handleCloseModal = useCallback(() => setIsModalOpen(false), []);\n\n const HomePage = ({ onOpenModal }: { onOpenModal: () => void }) => (\n <Container maxWidth=\"lg\" sx={{ py: { xs: 4, md: 6 } }}>\n <Stack spacing={{ xs: 5, md: 7 }}>\n <Box\n sx={{\n display: \"flex\",\n flexDirection: { xs: \"column-reverse\", md: \"row\" },\n alignItems: \"center\",\n gap: { xs: 4, md: 8 },\n }}\n >\n <Stack spacing={3} sx={{ flex: 1, width: \"100%\" }}>\n <Box sx={{ display: \"flex\", alignItems: \"center\", gap: 2 }}>\n <Box\n component=\"img\"\n src={banditHeadLogoUrl}\n alt=\"Bandit AI logo\"\n sx={{ height: 48, width: 48, borderRadius: 3, boxShadow: \"0 18px 50px rgba(99, 102, 241, 0.35)\" }}\n />\n <Typography variant=\"overline\" color=\"primary.light\" sx={{ letterSpacing: 2 }}>\n Powered by Bandit Engine\n </Typography>\n </Box>\n <Typography variant=\"h3\" fontWeight={700}>\n {brandingText}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n 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.\n </Typography>\n <Stack direction={{ xs: \"column\", sm: \"row\" }} spacing={2}>\n <Button component={RouterLink} to=\"/chat\" variant=\"contained\" color=\"primary\">\n Go to chat demo\n </Button>\n <Button variant=\"outlined\" color=\"secondary\" onClick={onOpenModal}>\n Open modal assistant\n </Button>\n </Stack>\n </Stack>\n <Box\n component=\"img\"\n src={burtsonLabsLogoUrl}\n alt=\"Burtson Labs logo\"\n sx={{\n width: \"100%\",\n maxWidth: 320,\n mx: { xs: \"auto\", md: 0 },\n display: \"block\",\n filter: \"drop-shadow(0 25px 45px rgba(15, 23, 42, 0.45))\",\n }}\n />\n </Box>\n <Box\n sx={{\n display: \"grid\",\n gap: 3,\n gridTemplateColumns: { xs: \"1fr\", md: \"repeat(3, minmax(0, 1fr))\" },\n }}\n >\n <Card sx={{ height: \"100%\", backdropFilter: \"blur(12px)\", backgroundColor: \"rgba(15, 23, 42, 0.64)\" }}>\n <CardContent>\n <Typography variant=\"h6\" gutterBottom>\n Configure in minutes\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n Edit <code>public/config.json</code> and <code>.env</code> to tailor models, personas, and branding for your product.\n </Typography>\n </CardContent>\n </Card>\n <Card sx={{ height: \"100%\", backdropFilter: \"blur(12px)\", backgroundColor: \"rgba(15, 23, 42, 0.64)\" }}>\n <CardContent>\n <Typography variant=\"h6\" gutterBottom>\n Ship secure gateways\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n Keep API keys server-side while proxying requests to Bandit AI, OpenAI, Azure OpenAI, Anthropic, XAI, or Ollama through the included Express gateway.\n </Typography>\n </CardContent>\n </Card>\n <Card sx={{ height: \"100%\", backdropFilter: \"blur(12px)\", backgroundColor: \"rgba(15, 23, 42, 0.64)\" }}>\n <CardContent>\n <Typography variant=\"h6\" gutterBottom>\n Manage knowledge freely\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n Add memories, files, and tools directly in the management console that ships with this quickstart.\n </Typography>\n <Button component={RouterLink} to=\"/management\" variant=\"text\" color=\"secondary\" sx={{ mt: 2, px: 0 }}>\n Explore management console\n </Button>\n </CardContent>\n </Card>\n </Box>\n </Stack>\n </Container>\n );\n\n const ChatPage = ({ onOpenModal }: { onOpenModal: () => void }) => (\n <Container maxWidth=\"lg\" sx={{ py: 4, display: \"flex\", flexDirection: \"column\", gap: 3 }}>\n <Stack\n direction={{ xs: \"column\", md: \"row\" }}\n spacing={2}\n alignItems={{ xs: \"stretch\", md: \"center\" }}\n justifyContent=\"space-between\"\n >\n <Box>\n <Typography variant=\"h4\" fontWeight={700} gutterBottom>\n Chat demo\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n This route renders the full <code>{\\`<Chat />\\`}</code> surface powered by your quickstart gateway.\n </Typography>\n </Box>\n <Stack direction={{ xs: \"column\", sm: \"row\" }} spacing={1.5}>\n <Button component={RouterLink} to=\"/management\" variant=\"outlined\" color=\"secondary\">\n Management console\n </Button>\n <Button variant=\"contained\" color=\"primary\" onClick={onOpenModal}>\n Open modal assistant\n </Button>\n </Stack>\n </Stack>\n <Box\n sx={{\n flexGrow: 1,\n minHeight: 540,\n borderRadius: 3,\n overflow: \"hidden\",\n boxShadow: \"0 35px 90px rgba(15, 23, 42, 0.55)\",\n }}\n >\n <Chat />\n </Box>\n </Container>\n );\n\n const Header = ({ gatewayChip }: { gatewayChip: ReactNode }) => (\n <AppBar\n position=\"sticky\"\n color=\"transparent\"\n elevation={0}\n sx={{ borderBottom: \"1px solid rgba(148, 163, 184, 0.16)\", backdropFilter: \"blur(18px)\" }}\n >\n <Toolbar sx={{ gap: 2, flexWrap: \"wrap\" }}>\n <Button\n component={RouterLink}\n to=\"/\"\n color=\"inherit\"\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 1.5,\n py: 1,\n px: 1.5,\n borderRadius: 2,\n textTransform: \"none\",\n bgcolor: \"transparent\",\n \"&:hover\": { bgcolor: \"rgba(99, 102, 241, 0.12)\" },\n }}\n >\n <Typography variant=\"h6\" sx={{ fontWeight: 600 }}>\n {brandingText}\n </Typography>\n </Button>\n <Box sx={{ flexGrow: 1 }} />\n {gatewayChip}\n <Stack direction=\"row\" spacing={1} flexWrap=\"wrap\" justifyContent=\"flex-end\">\n <Button component={RouterLink} to=\"/\" color=\"inherit\">\n Home\n </Button>\n <Button component={RouterLink} to=\"/management\" color=\"inherit\">\n Management\n </Button>\n <Button component={RouterLink} to=\"/chat\" variant=\"contained\" color=\"primary\">\n Go to chat\n </Button>\n </Stack>\n </Toolbar>\n </AppBar>\n );\n\n return (\n <Routes>\n <Route path=\"/management\" element={\n <ChatProvider packageSettings={packageSettings}>\n <Management />\n </ChatProvider>\n } />\n <Route path=\"/chat\" element={\n <ThemeProvider theme={banditQuickstartTheme}>\n <CssBaseline />\n <ChatProvider packageSettings={packageSettings}>\n <Box display=\"flex\" flexDirection=\"column\" minHeight=\"100vh\">\n <Box component=\"main\" sx={{ flexGrow: 1, display: \"flex\" }}>\n <ChatPage onOpenModal={handleOpenModal} />\n </Box>\n <ChatModal open={isModalOpen} onClose={handleCloseModal} />\n </Box>\n </ChatProvider>\n </ThemeProvider>\n } />\n <Route path=\"/*\" element={\n <ThemeProvider theme={banditQuickstartTheme}>\n <CssBaseline />\n <ChatProvider packageSettings={packageSettings}>\n <Box display=\"flex\" flexDirection=\"column\" minHeight=\"100vh\">\n <Header gatewayChip={gatewayChip} />\n <Box component=\"main\" sx={{ flexGrow: 1, display: \"flex\" }}>\n <Routes>\n <Route path=\"/\" element={<HomePage onOpenModal={handleOpenModal} />} />\n <Route path=\"*\" element={<Navigate to=\"/\" replace />} />\n </Routes>\n </Box>\n <ChatModal open={isModalOpen} onClose={handleCloseModal} />\n </Box>\n </ChatProvider>\n </ThemeProvider>\n } />\n </Routes>\n );\n}\n\nexport default App;\n`;\n\n const withResponse = template.replace(/__RESPONSE_STATUS__/g, responseStatusExpr);\n const withGatewayError = withResponse.replace(/__GATEWAY_ERROR__/g, gatewayErrorExpr);\n\n return ensureTrailingNewline(normalizeLineEndings(withGatewayError));\n};\n\nexport const buildBrandingConfig = (ctx: QuickstartTemplateContext): string =>\n formatJson({\n branding: {\n logoBase64: ctx.isDefaultLogo ? null : ctx.logoBase64,\n brandingText: ctx.brandingText,\n theme: \"bandit-dark\",\n hasTransparentLogo: ctx.isDefaultLogo ? true : ctx.hasTransparentLogo\n },\n knowledgeDocs: []\n });\n\nconst NEXT_CHAT_ROUTE_TEMPLATE = `import { NextRequest, NextResponse } from \"next/server\";\n\nexport const dynamic = \"force-dynamic\";\n\nconst DEFAULT_PROVIDER = \"__DEFAULT_PROVIDER__\";\nconst DEFAULT_MODEL = \"__DEFAULT_MODEL__\";\nconst FALLBACK_MODEL = __FALLBACK_MODEL__;\n\nconst OLLAMA_URL = (process.env.OLLAMA_URL ?? \"http://localhost:11434\").replace(/\\\\/$/, \"\");\nconst OPENAI_API_KEY = process.env.OPENAI_API_KEY;\nconst AZURE_OPENAI_ENDPOINT = process.env.AZURE_OPENAI_ENDPOINT ? process.env.AZURE_OPENAI_ENDPOINT.replace(/\\\\/$/, \"\") : undefined;\nconst AZURE_OPENAI_API_KEY = process.env.AZURE_OPENAI_API_KEY;\nconst AZURE_OPENAI_API_VERSION = process.env.AZURE_OPENAI_API_VERSION ?? \"2024-08-01-preview\";\nconst AZURE_OPENAI_CHAT_DEPLOYMENT = process.env.AZURE_OPENAI_CHAT_DEPLOYMENT;\nconst AZURE_OPENAI_COMPLETIONS_DEPLOYMENT = process.env.AZURE_OPENAI_COMPLETIONS_DEPLOYMENT ?? AZURE_OPENAI_CHAT_DEPLOYMENT;\nconst AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT = process.env.AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT;\nconst ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;\nconst ANTHROPIC_BASE_URL = (process.env.ANTHROPIC_BASE_URL ?? \"https://api.anthropic.com\").replace(/\\\\/$/, \"\");\nconst ANTHROPIC_API_VERSION = process.env.ANTHROPIC_API_VERSION ?? \"2023-06-01\";\nconst ANTHROPIC_MAX_TOKENS = Number.isFinite(Number(process.env.ANTHROPIC_MAX_TOKENS))\n ? Number(process.env.ANTHROPIC_MAX_TOKENS)\n : 1024;\nconst XAI_API_KEY = process.env.XAI_API_KEY;\nconst XAI_BASE_URL = (process.env.XAI_BASE_URL ?? \"https://api.x.ai/v1\").replace(/\\\\/$/, \"\");\nconst BANDIT_API_KEY = process.env.BANDIT_API_KEY;\nconst BANDIT_BASE_URL = (process.env.BANDIT_BASE_URL ?? \"https://api.burtson.ai\").replace(/\\\\/$/, \"\");\n\nconst normalizeGatewayImageUrl = (value: unknown): string => {\n if (!value) {\n return \"\";\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (!trimmed) {\n return \"\";\n }\n if (/^data:/i.test(trimmed) || /^https?:/i.test(trimmed)) {\n return trimmed;\n }\n return 'data:image/jpeg;base64,' + trimmed;\n }\n if (typeof value === \"object\") {\n const possibleUrl =\n typeof (value as { url?: string }).url === \"string\"\n ? (value as { url: string }).url\n : (value as { image_url?: { url?: string } }).image_url && typeof (value as { image_url?: { url?: string } }).image_url?.url === \"string\"\n ? ((value as { image_url: { url: string } }).image_url.url)\n : \"\";\n return normalizeGatewayImageUrl(possibleUrl);\n }\n return \"\";\n};\n\nconst extractGatewayImageDetail = (value: unknown): string | undefined => {\n if (value && typeof value === \"object\") {\n const record = value as Record<string, unknown>;\n if (typeof record.detail === \"string\" && record.detail.trim()) {\n return record.detail;\n }\n const nested = record.image_url;\n if (nested && typeof (nested as Record<string, unknown>).detail === \"string\" && (nested as Record<string, unknown>).detail.trim()) {\n return (nested as Record<string, unknown>).detail as string;\n }\n }\n return undefined;\n};\n\ninterface GatewayChatBody {\n provider?: string;\n model?: string;\n messages?: Array<{ role: string; content: unknown }>;\n prompt?: string;\n stream?: boolean;\n temperature?: number;\n max_tokens?: number;\n top_p?: number;\n stop?: string | string[];\n stop_sequences?: string | string[];\n tools?: unknown;\n tool_choice?: unknown;\n metadata?: unknown;\n thinking?: unknown;\n images?: string[];\n [key: string]: unknown;\n}\n\nconst normalizeProvider = (input: string): \"openai\" | \"azure\" | \"anthropic\" | \"ollama\" | \"xai\" | \"bandit\" => {\n const value = input.toLowerCase();\n if (value === \"azure-openai\" || value === \"azureopenai\" || value === \"azure\") return \"azure\";\n if (value === \"anthropic\" || value === \"claude\") return \"anthropic\";\n if (value === \"ollama\") return \"ollama\";\n if (value === \"xai\" || value === \"grok\") return \"xai\";\n if (value === \"bandit\" || value === \"banditai\" || value === \"bandit-ai\") return \"bandit\";\n return \"openai\";\n};\n\nconst stripPrefix = (model: unknown, prefix: string, fallback: string): string => {\n if (typeof model === \"string\") {\n return model.replace(new RegExp(\\`^\\${prefix}:\\`), \"\");\n }\n return fallback;\n};\n\nconst requireOpenAIKey = () => {\n if (!OPENAI_API_KEY) {\n throw new Error(\"Missing OPENAI_API_KEY. Add it to your .env file to route requests to OpenAI.\");\n }\n return OPENAI_API_KEY;\n};\n\nconst requireBanditKey = () => {\n if (!BANDIT_API_KEY) {\n throw new Error(\"Missing BANDIT_API_KEY. Add it to your .env file to route requests to Bandit AI.\");\n }\n return BANDIT_API_KEY;\n};\n\nconst requireXAIKey = () => {\n if (!XAI_API_KEY) {\n throw new Error(\"Missing XAI_API_KEY. Add it to your .env file to route requests to xAI.\");\n }\n return XAI_API_KEY;\n};\n\nconst requireAnthropicKey = () => {\n if (!ANTHROPIC_API_KEY) {\n throw new Error(\"Missing ANTHROPIC_API_KEY. Add it to your .env file to route requests to Anthropic.\");\n }\n return ANTHROPIC_API_KEY;\n};\n\nconst isAzureConfigured = () => Boolean(AZURE_OPENAI_ENDPOINT && AZURE_OPENAI_API_KEY);\n\nconst requireAzureBaseConfig = () => {\n if (!AZURE_OPENAI_ENDPOINT) {\n throw new Error(\"Missing AZURE_OPENAI_ENDPOINT. Add it to your .env file to route requests to Azure OpenAI.\");\n }\n if (!AZURE_OPENAI_API_KEY) {\n throw new Error(\"Missing AZURE_OPENAI_API_KEY. Add it to your .env file to route requests to Azure OpenAI.\");\n }\n return {\n endpoint: AZURE_OPENAI_ENDPOINT,\n apiKey: AZURE_OPENAI_API_KEY,\n };\n};\n\nconst buildAzureDeploymentUrl = (deployment: string | undefined, suffix: string) => {\n if (!deployment) {\n throw new Error(\\`Missing Azure OpenAI \\${suffix.split(\"/\")[0]} deployment name.\\`);\n }\n const { endpoint } = requireAzureBaseConfig();\n const normalized = suffix.replace(/^\\\\/+/, \"\");\n return \\`\\${endpoint}/openai/deployments/\\${deployment}/\\${normalized}?api-version=\\${AZURE_OPENAI_API_VERSION}\\`;\n};\n\nconst resolveAzureDeployment = (model: unknown, fallback: string | undefined, kind: \"chat\" | \"completions\" | \"embeddings\") => {\n const explicit = typeof model === \"string\" ? model.replace(/^azure:/, \"\") : undefined;\n if (explicit) return explicit;\n if (kind === \"embeddings\") return AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT ?? fallback;\n if (kind === \"completions\") return AZURE_OPENAI_COMPLETIONS_DEPLOYMENT ?? fallback;\n return AZURE_OPENAI_CHAT_DEPLOYMENT ?? fallback;\n};\n\nconst flattenGatewayContent = (content: unknown): string => {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n return content\n .map((part) => {\n if (typeof part === \"string\") return part;\n if (part && typeof part === \"object\" && \"type\" in part) {\n const typed = part as { type?: string; text?: string; image_url?: { url?: string } };\n if (typed.type === \"text\" && typeof typed.text === \"string\") return typed.text;\n if (typed.type === \"image_url\" && typed.image_url?.url) return \\`[Image: \\${typed.image_url.url}]\\`;\n }\n return JSON.stringify(part ?? {});\n })\n .join(\"\\\\n\");\n }\n if (content && typeof content === \"object\") return JSON.stringify(content);\n return \"\";\n};\n\nconst toAnthropicMessages = (messages: Array<{ role: string; content: unknown }> = []) => {\n const anthropicMessages: Array<{ role: \"user\" | \"assistant\"; content: Array<{ type: \"text\"; text: string }> }> = [];\n let systemPrompt = \"\";\n\n for (const message of messages) {\n if (!message) continue;\n const text = flattenGatewayContent(message.content);\n if (message.role === \"system\") {\n systemPrompt = systemPrompt ? \\`\\${systemPrompt}\\\\n\\\\n\\${text}\\` : text;\n continue;\n }\n const role = message.role === \"assistant\" ? \"assistant\" : \"user\";\n anthropicMessages.push({\n role,\n content: [{ type: \"text\", text }],\n });\n }\n\n return { messages: anthropicMessages, system: systemPrompt || undefined };\n};\n\nconst convertAnthropicResponseToGateway = (responseBody: any, modelName: string) => {\n if (!responseBody) {\n return {\n id: \\`anthropic-\\${Date.now()}\\`,\n object: \"chat.completion\",\n created: Math.floor(Date.now() / 1000),\n model: modelName.startsWith(\"anthropic:\") ? modelName : \\`anthropic:\\${modelName}\\`,\n choices: [],\n };\n }\n\n const textContent = Array.isArray(responseBody.content)\n ? responseBody.content\n .filter((item: any) => item && item.type === \"text\" && typeof item.text === \"string\")\n .map((item: any) => item.text)\n .join(\"\\\\n\")\n : typeof responseBody.content === \"string\"\n ? responseBody.content\n : \"\";\n\n const promptTokens = responseBody.usage?.input_tokens ?? 0;\n const completionTokens = responseBody.usage?.output_tokens ?? 0;\n\n return {\n id: responseBody.id ?? \\`anthropic-\\${Date.now()}\\`,\n object: \"chat.completion\",\n created: Math.floor(Date.now() / 1000),\n model: modelName.startsWith(\"anthropic:\") ? modelName : \\`anthropic:\\${modelName}\\`,\n choices: [\n {\n index: 0,\n message: {\n role: responseBody.role ?? \"assistant\",\n content: textContent,\n },\n finish_reason: responseBody.stop_reason ?? responseBody.stop_sequence ?? null,\n },\n ],\n usage: responseBody.usage\n ? {\n prompt_tokens: promptTokens,\n completion_tokens: completionTokens,\n total_tokens: promptTokens + completionTokens,\n }\n : undefined,\n };\n};\n\nconst passthroughResponse = (upstream: Response) => {\n const headers = new Headers(upstream.headers);\n return new Response(upstream.body, {\n status: upstream.status,\n statusText: upstream.statusText,\n headers,\n });\n};\n\nconst jsonResponse = async (upstream: Response) => {\n const data = await upstream.json().catch(async () => ({ raw: await upstream.text() }));\n return NextResponse.json(data, { status: upstream.status });\n};\n\nconst errorResponse = (status: number, error: unknown) =>\n NextResponse.json(\n {\n error: error instanceof Error ? error.message : String(error ?? \"Unknown error\"),\n },\n { status }\n );\n\nexport async function POST(request: NextRequest) {\n const body = (await request.json()) as GatewayChatBody;\n const provider = normalizeProvider(body.provider ?? DEFAULT_PROVIDER);\n const stream = body.stream !== false;\n\n try {\n switch (provider) {\n case \"openai\": {\n const openaiKey = requireOpenAIKey();\n const { provider: _provider, ...cleanBody } = body;\n const requestBody = {\n ...cleanBody,\n stream,\n model: stripPrefix(body.model ?? DEFAULT_MODEL, \"openai\", \"gpt-4o\"),\n };\n const response = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: \\`Bearer \\${openaiKey}\\`,\n },\n body: JSON.stringify(requestBody),\n });\n if (!response.ok) {\n const details = await response.text();\n return NextResponse.json({ error: \\`OpenAI chat failed: \\${response.status}\\`, details }, { status: response.status });\n }\n return stream ? passthroughResponse(response) : jsonResponse(response);\n }\n\n case \"bandit\": {\n const banditKey = requireBanditKey();\n const { provider: _provider, ...cleanBody } = body;\n const providerName = typeof body.provider === \"string\" ? body.provider : \"bandit\";\n const requestBody = {\n ...cleanBody,\n stream,\n model: stripPrefix(body.model ?? DEFAULT_MODEL, \"bandit\", \"bandit-core-1\"),\n };\n\n if (\n providerName !== \"ollama\" &&\n Array.isArray(requestBody.images) &&\n requestBody.images.length > 0 &&\n Array.isArray(requestBody.messages)\n ) {\n const lastUserIndex = requestBody.messages.map((message) => message?.role).lastIndexOf(\"user\");\n if (lastUserIndex !== -1) {\n const targetMessage = requestBody.messages[lastUserIndex] ?? {};\n const baseContent = Array.isArray(targetMessage.content)\n ? targetMessage.content.filter(Boolean)\n : typeof targetMessage.content === \"string\" && targetMessage.content.trim().length > 0\n ? [{ type: \"text\", text: targetMessage.content }]\n : [];\n\n const imageContent = requestBody.images\n .map((entry) => {\n const url = normalizeGatewayImageUrl(entry);\n if (!url) {\n return null;\n }\n return {\n type: \"image_url\",\n image_url: {\n url,\n detail: extractGatewayImageDetail(entry) ?? \"auto\"\n }\n };\n })\n .filter(Boolean);\n\n if (imageContent.length > 0) {\n requestBody.messages[lastUserIndex] = {\n ...targetMessage,\n content: [...baseContent, ...imageContent]\n };\n }\n }\n delete requestBody.images;\n }\n\n const response = await fetch(BANDIT_BASE_URL + \"/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: \\`Bearer \\${banditKey}\\`,\n },\n body: JSON.stringify(requestBody),\n });\n if (!response.ok) {\n const details = await response.text();\n return NextResponse.json({ error: \\`Bandit chat failed: \\${response.status}\\`, details }, { status: response.status });\n }\n return stream ? passthroughResponse(response) : jsonResponse(response);\n }\n\n case \"xai\": {\n const xaiKey = requireXAIKey();\n const { provider: _provider, ...cleanBody } = body;\n const requestBody = {\n ...cleanBody,\n stream,\n model: stripPrefix(body.model ?? DEFAULT_MODEL, \"xai\", \"grok-2-latest\"),\n };\n const response = await fetch(XAI_BASE_URL + \"/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: \"Bearer \" + xaiKey,\n },\n body: JSON.stringify(requestBody),\n });\n if (!response.ok) {\n const details = await response.text();\n return NextResponse.json({ error: \"xAI chat failed: \" + response.status, details }, { status: response.status });\n }\n return stream ? passthroughResponse(response) : jsonResponse(response);\n }\n\n case \"anthropic\": {\n const anthropicKey = requireAnthropicKey();\n const requestedModel = stripPrefix(body.model ?? DEFAULT_MODEL, \"anthropic\", \"claude-3-5-haiku-latest\");\n const stopSequences = Array.isArray(body.stop)\n ? body.stop\n : Array.isArray(body.stop_sequences)\n ? body.stop_sequences\n : body.stop\n ? [body.stop]\n : undefined;\n const { messages, system } = toAnthropicMessages(Array.isArray(body.messages) ? body.messages : []);\n const fallbackText = typeof body.prompt === \"string\" && body.prompt.trim().length > 0\n ? body.prompt\n : \"Hello from Bandit quickstart gateway\";\n\n const requestBody: Record<string, unknown> = {\n model: requestedModel,\n messages: messages.length > 0\n ? messages\n : [\n {\n role: \"user\",\n content: [{ type: \"text\", text: fallbackText }],\n },\n ],\n stream,\n max_tokens: typeof body.max_tokens === \"number\" && body.max_tokens > 0 ? body.max_tokens : ANTHROPIC_MAX_TOKENS,\n };\n\n if (system) requestBody.system = system;\n if (typeof body.temperature === \"number\") requestBody.temperature = body.temperature;\n if (typeof body.top_p === \"number\") requestBody.top_p = body.top_p;\n if (typeof body.top_k === \"number\") requestBody.top_k = body.top_k;\n if (stopSequences) requestBody.stop_sequences = stopSequences;\n if (body.metadata) requestBody.metadata = body.metadata;\n if (body.tools) requestBody.tools = body.tools;\n if (body.tool_choice) requestBody.tool_choice = body.tool_choice;\n if (body.thinking) requestBody.thinking = body.thinking;\n\n const response = await fetch(\\`\\${ANTHROPIC_BASE_URL}/v1/messages\\`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": anthropicKey,\n \"anthropic-version\": ANTHROPIC_API_VERSION,\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const details = await response.text();\n return NextResponse.json({ error: \\`Anthropic chat failed: \\${response.status}\\`, details }, { status: response.status });\n }\n\n if (stream) {\n return passthroughResponse(response);\n }\n\n const data = await response.json();\n const normalized = convertAnthropicResponseToGateway(data, requestedModel);\n return NextResponse.json(normalized);\n }\n\n case \"azure\": {\n const { apiKey } = requireAzureBaseConfig();\n const deployment = resolveAzureDeployment(body.model, AZURE_OPENAI_CHAT_DEPLOYMENT, \"chat\");\n const { provider: _provider, model: _model, ...cleanBody } = body;\n const requestBody = {\n ...cleanBody,\n stream,\n };\n\n const response = await fetch(buildAzureDeploymentUrl(deployment, \"chat/completions\"), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"api-key\": apiKey,\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const details = await response.text();\n return NextResponse.json({ error: \\`Azure OpenAI chat failed: \\${response.status}\\`, details }, { status: response.status });\n }\n\n return stream ? passthroughResponse(response) : jsonResponse(response);\n }\n\n case \"ollama\": {\n const { provider: _provider, ...cleanBody } = body;\n const requestBody = {\n ...cleanBody,\n stream,\n model: stripPrefix(body.model ?? DEFAULT_MODEL, \"ollama\", \"llama3.1\"),\n };\n\n const response = await fetch(\\`\\${OLLAMA_URL}/api/chat\\`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const details = await response.text();\n return NextResponse.json({ error: \\`Ollama chat failed: \\${response.status}\\`, details }, { status: response.status });\n }\n\n return stream ? passthroughResponse(response) : jsonResponse(response);\n }\n\n default:\n return errorResponse(400, \\`Unsupported provider: \\${provider}\\`);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing\") ? 400 : 500;\n return errorResponse(status, error);\n }\n}\n\n`;\nconst NEXT_HEALTH_ROUTE_TEMPLATE = `import { NextResponse } from \"next/server\";\n\nexport const dynamic = \"force-dynamic\";\n\nconst QUICKSTART_VERSION = \"0.1.0\";\nconst OLLAMA_URL = (process.env.OLLAMA_URL ?? \"http://localhost:11434\").replace(/\\\\/$/, \"\");\nconst OPENAI_API_KEY = process.env.OPENAI_API_KEY;\nconst AZURE_OPENAI_ENDPOINT = process.env.AZURE_OPENAI_ENDPOINT ? process.env.AZURE_OPENAI_ENDPOINT.replace(/\\\\/$/, \"\") : undefined;\nconst AZURE_OPENAI_API_KEY = process.env.AZURE_OPENAI_API_KEY;\nconst AZURE_OPENAI_API_VERSION = process.env.AZURE_OPENAI_API_VERSION ?? \"2024-08-01-preview\";\nconst ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;\nconst ANTHROPIC_BASE_URL = (process.env.ANTHROPIC_BASE_URL ?? \"https://api.anthropic.com\").replace(/\\\\/$/, \"\");\nconst ANTHROPIC_API_VERSION = process.env.ANTHROPIC_API_VERSION ?? \"2023-06-01\";\nconst XAI_API_KEY = process.env.XAI_API_KEY;\nconst XAI_BASE_URL = (process.env.XAI_BASE_URL ?? \"https://api.x.ai/v1\").replace(/\\\\/$/, \"\");\n\nconst isAzureConfigured = () => Boolean(AZURE_OPENAI_ENDPOINT && AZURE_OPENAI_API_KEY);\n\nconst buildAzurePath = (path: string) => {\n const normalized = path.replace(/^\\\\/+/, \"\");\n if (!AZURE_OPENAI_ENDPOINT) {\n throw new Error(\"Missing AZURE_OPENAI_ENDPOINT. Add it to your .env file to route requests to Azure OpenAI.\");\n }\n return \\`\\${AZURE_OPENAI_ENDPOINT}/openai/\\${normalized}?api-version=\\${AZURE_OPENAI_API_VERSION}\\`;\n};\n\nexport async function GET() {\n const providers: Array<Record<string, unknown>> = [];\n\n // OpenAI\n try {\n if (OPENAI_API_KEY) {\n const response = await fetch(\"https://api.openai.com/v1/models\", {\n headers: { Authorization: \\`Bearer \\${OPENAI_API_KEY}\\` },\n });\n providers.push({\n name: \"openai\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"openai\",\n });\n } else {\n providers.push({\n name: \"openai\",\n status: \"unconfigured\",\n provider: \"openai\",\n error: \"API key not configured\",\n });\n }\n } catch (error) {\n providers.push({\n name: \"openai\",\n status: \"unhealthy\",\n provider: \"openai\",\n error: error instanceof Error ? error.message : String(error),\n });\n }\n\n // Azure\n if (AZURE_OPENAI_ENDPOINT || AZURE_OPENAI_API_KEY) {\n if (!isAzureConfigured()) {\n providers.push({\n name: \"azure\",\n status: \"unconfigured\",\n provider: \"azure\",\n error: \"Endpoint or API key not configured\",\n endpoint: AZURE_OPENAI_ENDPOINT,\n });\n } else {\n try {\n const response = await fetch(buildAzurePath(\"deployments\"), {\n headers: { \"api-key\": AZURE_OPENAI_API_KEY ?? \"\" },\n });\n providers.push({\n name: \"azure\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"azure\",\n endpoint: AZURE_OPENAI_ENDPOINT,\n });\n } catch (error) {\n providers.push({\n name: \"azure\",\n status: \"unhealthy\",\n provider: \"azure\",\n endpoint: AZURE_OPENAI_ENDPOINT,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n } else {\n providers.push({\n name: \"azure\",\n status: \"unconfigured\",\n provider: \"azure\",\n error: \"Endpoint or API key not configured\",\n });\n }\n\n // Anthropic\n if (ANTHROPIC_API_KEY) {\n try {\n const response = await fetch(\\`\\${ANTHROPIC_BASE_URL}/v1/models\\`, {\n headers: {\n \"x-api-key\": ANTHROPIC_API_KEY,\n \"anthropic-version\": ANTHROPIC_API_VERSION,\n },\n });\n providers.push({\n name: \"anthropic\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"anthropic\",\n endpoint: ANTHROPIC_BASE_URL,\n });\n } catch (error) {\n providers.push({\n name: \"anthropic\",\n status: \"unhealthy\",\n provider: \"anthropic\",\n endpoint: ANTHROPIC_BASE_URL,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n } else {\n providers.push({\n name: \"anthropic\",\n status: \"unconfigured\",\n provider: \"anthropic\",\n error: \"API key not configured\",\n });\n }\n\n // xAI\n if (XAI_API_KEY) {\n try {\n const response = await fetch(XAI_BASE_URL + \"/models\", {\n headers: { Authorization: \"Bearer \" + XAI_API_KEY },\n });\n providers.push({\n name: \"xai\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"xai\",\n endpoint: XAI_BASE_URL,\n });\n } catch (error) {\n providers.push({\n name: \"xai\",\n status: \"unhealthy\",\n provider: \"xai\",\n endpoint: XAI_BASE_URL,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n } else {\n providers.push({\n name: \"xai\",\n status: \"unconfigured\",\n provider: \"xai\",\n error: \"API key not configured\",\n endpoint: XAI_BASE_URL,\n });\n }\n\n // Ollama\n try {\n const response = await fetch(\\`\\${OLLAMA_URL}/api/tags\\`);\n providers.push({\n name: \"ollama\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"ollama\",\n url: OLLAMA_URL,\n });\n } catch (error) {\n providers.push({\n name: \"ollama\",\n status: \"offline\",\n provider: \"ollama\",\n url: OLLAMA_URL,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n\n const overallHealthy = providers.some((provider) => provider.status === \"healthy\");\n\n return NextResponse.json({\n status: overallHealthy ? \"healthy\" : \"unhealthy\",\n version: QUICKSTART_VERSION,\n uptime: Math.round(process.uptime()),\n providers,\n });\n}\n\n`;\nconst NEXT_MODELS_ROUTE_TEMPLATE = `import { NextResponse } from \"next/server\";\n\nexport const dynamic = \"force-dynamic\";\n\nconst BASE_GATEWAY_MODELS = __GATEWAY_MODELS__;\n\nexport function toGatewayModels() {\n return BASE_GATEWAY_MODELS.map((model) => ({\n ...model,\n created: Date.now(),\n modified_at: new Date().toISOString(),\n size: 0,\n digest: \"\",\n details: {\n format: \"chat\",\n family: model.provider,\n families: [model.provider],\n parameter_size: \"\",\n quantization_level: \"\",\n },\n }));\n}\n\nexport async function GET() {\n return NextResponse.json({ models: toGatewayModels() });\n}\n\n`;\nconst NEXT_GATEWAY_README_TEMPLATE = `# Next.js Gateway API\n\nThis directory contains a minimal Next.js App Router implementation of the Bandit gateway API. It mirrors the Express gateway in\n\\`server/gateway.js\\` but is ready to drop into a Next.js project.\n\n## Routes\n\n- \\`app/api/health/route.ts\\` – provider health and availability checks\n- \\`app/api/chat/completions/route.ts\\` – provider-aware chat completions endpoint (Bandit AI, OpenAI, Azure OpenAI, Anthropic, xAI, Ollama)\n- \\`app/api/models/route.ts\\` – exposes the scaffolded gateway model metadata used by the frontend\n\n## Usage\n\n1. Copy the contents of \\`server/next-app/\\` into the \\`app/\\` directory of a Next.js project.\n2. Ensure the environment variables listed in \\`.env.example\\` are available to the Next.js runtime. At minimum you will want the\n provider API keys you plan to use (Bandit AI, OpenAI, Azure OpenAI, Anthropic, xAI, or Ollama).\n3. Start Next.js with \\`npm run dev\\` (or your project’s equivalent). The routes are server-only (\\`export const dynamic = \"force-dynamic\"\\`)\n and can coexist with any frontend pages.\n\nThe generated routes favour clarity over cleverness so you can extend them with custom auth, logging, and provider routing logic.\n`;\n\nexport const buildNextChatRoute = (ctx: QuickstartTemplateContext): string => {\n const fallbackModel = ctx.fallbackModelId ? `\"${ctx.fallbackModelId}\"` : \"undefined\";\n return ensureTrailingNewline(\n normalizeLineEndings(\n NEXT_CHAT_ROUTE_TEMPLATE\n .replace(/__DEFAULT_PROVIDER__/g, ctx.defaultProvider)\n .replace(/__DEFAULT_MODEL__/g, ctx.defaultModelId)\n .replace(/__FALLBACK_MODEL__/g, fallbackModel)\n )\n );\n};\n\nexport const buildNextHealthRoute = (): string =>\n ensureTrailingNewline(normalizeLineEndings(NEXT_HEALTH_ROUTE_TEMPLATE));\n\nexport const buildNextModelsRoute = (ctx: QuickstartTemplateContext): string => {\n const modelsDefinition = JSON.stringify(ctx.gatewayModels, null, 2);\n return ensureTrailingNewline(\n normalizeLineEndings(\n NEXT_MODELS_ROUTE_TEMPLATE.replace('__GATEWAY_MODELS__', modelsDefinition)\n )\n );\n};\n\nexport const buildNextGatewayReadme = (): string =>\n ensureTrailingNewline(normalizeLineEndings(NEXT_GATEWAY_README_TEMPLATE));\n\nexport const buildGatewayServer = (ctx: QuickstartTemplateContext): string => {\n const modelsDefinition = JSON.stringify(ctx.gatewayModels, null, 2);\n\n const gatewaySource = `import express from \"express\";\nimport cors from \"cors\";\nimport dotenv from \"dotenv\";\n\ndotenv.config();\n\nconst app = express();\napp.use(cors());\napp.use(express.json({ limit: '50mb' }));\napp.use(express.urlencoded({ limit: '50mb', extended: true }));\n\nconst QUICKSTART_VERSION = \"0.1.0\";\nconst DEFAULT_PROVIDER = \"${ctx.defaultProvider}\";\nconst BASE_GATEWAY_MODELS = ${modelsDefinition};\nconst BANDIT_API_KEY = process.env.BANDIT_API_KEY;\nconst BANDIT_BASE_URL = (process.env.BANDIT_BASE_URL ?? \"https://api.burtson.ai\").replace(/\\\\/$/, \"\");\nconst OLLAMA_BASE_URL = (process.env.OLLAMA_URL ?? \"http://localhost:11434\").replace(/\\\\/$/, \"\");\nconst AZURE_OPENAI_ENDPOINT = process.env.AZURE_OPENAI_ENDPOINT ? process.env.AZURE_OPENAI_ENDPOINT.replace(/\\\\/$/, \"\") : undefined;\nconst AZURE_OPENAI_API_KEY = process.env.AZURE_OPENAI_API_KEY;\nconst AZURE_OPENAI_API_VERSION = process.env.AZURE_OPENAI_API_VERSION ?? \"2024-08-01-preview\";\nconst AZURE_OPENAI_CHAT_DEPLOYMENT = process.env.AZURE_OPENAI_CHAT_DEPLOYMENT;\nconst AZURE_OPENAI_COMPLETIONS_DEPLOYMENT = process.env.AZURE_OPENAI_COMPLETIONS_DEPLOYMENT ?? AZURE_OPENAI_CHAT_DEPLOYMENT;\nconst AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT = process.env.AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT;\nconst ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;\nconst ANTHROPIC_BASE_URL = (process.env.ANTHROPIC_BASE_URL ?? \"https://api.anthropic.com\").replace(/\\\\/$/, \"\");\nconst ANTHROPIC_API_VERSION = process.env.ANTHROPIC_API_VERSION ?? \"2023-06-01\";\nconst ANTHROPIC_MAX_TOKENS = Number.isFinite(Number(process.env.ANTHROPIC_MAX_TOKENS))\n ? Number(process.env.ANTHROPIC_MAX_TOKENS)\n : 1024;\nconst XAI_API_KEY = process.env.XAI_API_KEY;\nconst XAI_BASE_URL = (process.env.XAI_BASE_URL ?? \"https://api.x.ai/v1\").replace(/\\\\/$/, \"\");\n\nconst toGatewayModels = () =>\n BASE_GATEWAY_MODELS.map((model) => ({\n ...model,\n created: Date.now(),\n modified_at: new Date().toISOString(),\n size: 0,\n digest: \"\",\n details: {\n format: \"chat\",\n family: model.provider,\n families: [model.provider],\n parameter_size: \"\",\n quantization_level: \"\",\n },\n }));\n\nconst stripAzureModelPrefix = (value) =>\n typeof value === \"string\" ? value.replace(/^azure:/, \"\") : undefined;\n\nconst requireBanditKey = () => {\n if (!BANDIT_API_KEY) {\n throw new Error(\"Missing BANDIT_API_KEY. Add it to your .env file to route requests to Bandit AI.\");\n }\n return BANDIT_API_KEY;\n};\n\nconst isAzureConfigured = () => Boolean(AZURE_OPENAI_ENDPOINT && AZURE_OPENAI_API_KEY);\n\nconst requireAzureBaseConfig = () => {\n if (!AZURE_OPENAI_ENDPOINT) {\n throw new Error(\"Missing AZURE_OPENAI_ENDPOINT. Add it to your .env file to route requests to Azure OpenAI.\");\n }\n if (!AZURE_OPENAI_API_KEY) {\n throw new Error(\"Missing AZURE_OPENAI_API_KEY. Add it to your .env file to route requests to Azure OpenAI.\");\n }\n return {\n endpoint: AZURE_OPENAI_ENDPOINT,\n apiKey: AZURE_OPENAI_API_KEY,\n apiVersion: AZURE_OPENAI_API_VERSION,\n };\n};\n\nconst resolveAzureDeployment = (explicitValue, fallbackValue, kind) => {\n const fromRequest = stripAzureModelPrefix(explicitValue);\n if (fromRequest) {\n return fromRequest;\n }\n if (fallbackValue) {\n return fallbackValue;\n }\n throw new Error(\\`Missing Azure OpenAI \\${kind} deployment name. Set AZURE_OPENAI_\\${kind.toUpperCase()}_DEPLOYMENT in your .env file.\\`);\n};\n\nconst buildAzureDeploymentUrl = (deployment, suffix) => {\n const { endpoint } = requireAzureBaseConfig();\n const normalizedSuffix = suffix.replace(/^\\\\//, \"\");\n return \\`\\${endpoint}/openai/deployments/\\${deployment}/\\${normalizedSuffix}?api-version=\\${AZURE_OPENAI_API_VERSION}\\`;\n};\n\nconst buildAzurePath = (suffix) => {\n const { endpoint } = requireAzureBaseConfig();\n const normalizedSuffix = suffix.replace(/^\\\\//, \"\");\n const hasQuery = normalizedSuffix.includes(\"?\");\n const separator = hasQuery ? \"&\" : \"?\";\n return \\`\\${endpoint}/openai/\\${normalizedSuffix}\\${separator}api-version=\\${AZURE_OPENAI_API_VERSION}\\`;\n};\n\nconst stripAnthropicModelPrefix = (value) =>\n typeof value === \"string\" ? value.replace(/^anthropic:/, \"\") : undefined;\n\nconst isAnthropicConfigured = () => Boolean(ANTHROPIC_API_KEY);\n\nconst requireAnthropicKey = () => {\n if (!ANTHROPIC_API_KEY) {\n throw new Error(\"Missing ANTHROPIC_API_KEY. Add it to your .env file to route requests to Anthropic.\");\n }\n return ANTHROPIC_API_KEY;\n};\n\nconst buildAnthropicUrl = (path) => {\n const normalized = path.replace(/^\\\\//, \"\");\n return \\`\\${ANTHROPIC_BASE_URL}/v1/\\${normalized}\\`;\n};\n\nconst buildAnthropicHeaders = () => ({\n \"Content-Type\": \"application/json\",\n \"x-api-key\": requireAnthropicKey(),\n \"anthropic-version\": ANTHROPIC_API_VERSION,\n});\n\nconst flattenGatewayContent = (content) => {\n if (typeof content === \"string\") {\n return content;\n }\n if (Array.isArray(content)) {\n return content\n .map((part) => {\n if (typeof part === \"string\") {\n return part;\n }\n if (part?.type === \"text\" && typeof part.text === \"string\") {\n return part.text;\n }\n if (part?.type === \"image_url\" && part.image_url?.url) {\n return \\`[Image: \\${part.image_url.url}]\\`;\n }\n return JSON.stringify(part ?? {});\n })\n .join(\"\\\\n\");\n }\n if (content && typeof content === \"object\") {\n return JSON.stringify(content);\n }\n return \"\";\n};\n\nconst normalizeGatewayImageUrl = (value) => {\n if (!value) {\n return \"\";\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (!trimmed) {\n return \"\";\n }\n if (/^data:/i.test(trimmed) || /^https?:/i.test(trimmed)) {\n return trimmed;\n }\n return 'data:image/jpeg;base64,' + trimmed;\n }\n if (typeof value === \"object\") {\n const possibleUrl =\n typeof value.url === \"string\"\n ? value.url\n : value.image_url && typeof value.image_url.url === \"string\"\n ? value.image_url.url\n : \"\";\n return normalizeGatewayImageUrl(possibleUrl);\n }\n return \"\";\n};\n\nconst extractGatewayImageDetail = (value) => {\n if (value && typeof value === \"object\") {\n const record = value;\n if (typeof record.detail === \"string\" && record.detail.trim()) {\n return record.detail;\n }\n if (record.image_url && typeof record.image_url.detail === \"string\" && record.image_url.detail.trim()) {\n return record.image_url.detail;\n }\n }\n return undefined;\n};\n\nconst toAnthropicMessages = (messages = []) => {\n const anthropicMessages = [];\n let systemPrompt = \"\";\n\n for (const message of messages) {\n if (!message) continue;\n const text = flattenGatewayContent(message.content);\n\n if (message.role === \"system\") {\n systemPrompt = systemPrompt ? \\`\\${systemPrompt}\\\\n\\\\n\\${text}\\` : text;\n continue;\n }\n\n const role = message.role === \"assistant\" ? \"assistant\" : \"user\";\n anthropicMessages.push({\n role,\n content: [{ type: \"text\", text }],\n });\n }\n\n return { messages: anthropicMessages, system: systemPrompt || undefined };\n};\n\nconst convertAnthropicResponseToGateway = (responseBody, modelName) => {\n if (!responseBody) {\n return {\n id: \\`anthropic-\\${Date.now()}\\`,\n object: \"chat.completion\",\n created: Math.floor(Date.now() / 1000),\n model: modelName.startsWith(\"anthropic:\") ? modelName : \\`anthropic:\\${modelName}\\`,\n choices: [],\n };\n }\n\n const textContent = Array.isArray(responseBody.content)\n ? responseBody.content\n .filter((item) => item && item.type === \"text\" && typeof item.text === \"string\")\n .map((item) => item.text)\n .join(\"\\\\n\")\n : typeof responseBody.content === \"string\"\n ? responseBody.content\n : \"\";\n\n const promptTokens = responseBody.usage?.input_tokens ?? 0;\n const completionTokens = responseBody.usage?.output_tokens ?? 0;\n\n return {\n id: responseBody.id ?? \\`anthropic-\\${Date.now()}\\`,\n object: \"chat.completion\",\n created: Math.floor(Date.now() / 1000),\n model: modelName.startsWith(\"anthropic:\") ? modelName : \\`anthropic:\\${modelName}\\`,\n choices: [\n {\n index: 0,\n message: {\n role: responseBody.role ?? \"assistant\",\n content: textContent,\n },\n finish_reason: responseBody.stop_reason ?? responseBody.stop_sequence ?? null,\n },\n ],\n usage: responseBody.usage\n ? {\n prompt_tokens: promptTokens,\n completion_tokens: completionTokens,\n total_tokens: promptTokens + completionTokens,\n }\n : undefined,\n };\n};\n\nconst convertAnthropicResponseToGenerate = (responseBody, modelName) => {\n const gatewayResponse = convertAnthropicResponseToGateway(responseBody, modelName);\n const content = gatewayResponse.choices?.[0]?.message?.content ?? \"\";\n return {\n model: gatewayResponse.model,\n created_at: new Date().toISOString(),\n response: content,\n done: true,\n total_duration: 0,\n load_duration: 0,\n prompt_eval_count: gatewayResponse.usage?.prompt_tokens ?? 0,\n prompt_eval_duration: 0,\n eval_count: gatewayResponse.usage?.completion_tokens ?? 0,\n eval_duration: 0,\n };\n};\n\nconst requireOpenAIKey = () => {\n const key = process.env.OPENAI_API_KEY;\n if (!key) {\n throw new Error(\"Missing OPENAI_API_KEY. Add it to your .env file to route requests to OpenAI.\");\n }\n return key;\n};\n\nconst requireXAIKey = () => {\n const key = XAI_API_KEY;\n if (!key) {\n throw new Error(\"Missing XAI_API_KEY. Add it to your .env file to route requests to xAI.\");\n }\n return key;\n};\n\n// Utility function to handle streaming responses\nconst handleStreamingResponse = async (upstreamResponse, res) => {\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('Access-Control-Allow-Origin', '*');\n \n try {\n // Get the readable stream from the response\n const reader = upstreamResponse.body.getReader();\n \n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n \n // Write the chunk to the response\n res.write(value);\n }\n \n res.end();\n } catch (error) {\n console.error('Streaming error:', error);\n // Fallback to non-streaming\n const text = await upstreamResponse.text();\n res.send(text);\n }\n};\n\nconst relayAnthropicStream = async (upstreamResponse, res) => {\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('Access-Control-Allow-Origin', '*');\n\n const reader = upstreamResponse.body?.getReader();\n if (!reader) {\n const fallback = await upstreamResponse.text();\n res.write(\"data: \" + JSON.stringify({ choices: [{ delta: { content: fallback } }] }) + \"\\\\n\\\\n\");\n res.write(\"data: [DONE]\\\\n\\\\n\");\n return res.end();\n }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n const sendChunk = (payload) => {\n res.write(\"data: \" + JSON.stringify(payload) + \"\\\\n\\\\n\");\n };\n\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n let delimiterIndex;\n while ((delimiterIndex = buffer.indexOf('\\\\n\\\\n')) >= 0) {\n const rawEvent = buffer.slice(0, delimiterIndex).trim();\n buffer = buffer.slice(delimiterIndex + 2);\n if (!rawEvent) continue;\n\n const lines = rawEvent.split('\\\\n');\n const eventLine = lines.find((line) => line.startsWith('event:')) ?? '';\n const dataLine = lines.find((line) => line.startsWith('data:')) ?? '';\n const event = eventLine.replace('event:', '').trim();\n const trimmedData = dataLine.replace('data:', '').trim();\n\n if (!trimmedData) {\n continue;\n }\n\n let parsed;\n try {\n parsed = JSON.parse(trimmedData);\n } catch (error) {\n console.error('Anthropic stream parse error', error, { rawEvent });\n continue;\n }\n\n if (event === 'content_block_delta') {\n const textChunk = parsed?.delta?.text ?? '';\n if (textChunk) {\n sendChunk({\n choices: [\n {\n delta: {\n content: textChunk,\n },\n },\n ],\n });\n }\n } else if (event === 'message_stop') {\n sendChunk({\n choices: [\n {\n delta: {},\n finish_reason: 'stop',\n },\n ],\n });\n }\n }\n }\n } catch (error) {\n console.error('Anthropic streaming relay error', error);\n sendChunk({\n error: error instanceof Error ? error.message : String(error),\n });\n } finally {\n res.write(\"data: [DONE]\\\\n\\\\n\");\n res.end();\n }\n};\n\n// ============================================================================\n// GENERAL HEALTH & MODELS\n// ============================================================================\n\napp.get(\"/api/health\", async (_req, res) => {\n const providers = [];\n \n // Check OpenAI\n try {\n const openaiKey = process.env.OPENAI_API_KEY;\n if (openaiKey) {\n const response = await fetch(\"https://api.openai.com/v1/models\", {\n headers: { \"Authorization\": \\`Bearer \\${openaiKey}\\` }\n });\n providers.push({\n name: \"openai\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"openai\"\n });\n } else {\n providers.push({\n name: \"openai\", \n status: \"unconfigured\",\n provider: \"openai\",\n error: \"API key not configured\"\n });\n }\n } catch (error) {\n providers.push({\n name: \"openai\",\n status: \"unhealthy\", \n provider: \"openai\",\n error: error.message\n });\n }\n\n // Check Azure OpenAI\n if (AZURE_OPENAI_ENDPOINT || AZURE_OPENAI_API_KEY) {\n if (!isAzureConfigured()) {\n providers.push({\n name: \"azure\",\n status: \"unconfigured\",\n provider: \"azure\",\n error: \"Endpoint or API key not configured\",\n endpoint: AZURE_OPENAI_ENDPOINT\n });\n } else {\n try {\n const { endpoint } = requireAzureBaseConfig();\n const deploymentsUrl = buildAzurePath(\"deployments\");\n const response = await fetch(deploymentsUrl, {\n headers: { \"api-key\": AZURE_OPENAI_API_KEY }\n });\n providers.push({\n name: \"azure\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"azure\",\n endpoint\n });\n } catch (error) {\n providers.push({\n name: \"azure\",\n status: \"unhealthy\",\n provider: \"azure\",\n error: error instanceof Error ? error.message : String(error),\n endpoint: AZURE_OPENAI_ENDPOINT\n });\n }\n }\n } else {\n providers.push({\n name: \"azure\",\n status: \"unconfigured\",\n provider: \"azure\",\n error: \"Endpoint or API key not configured\"\n });\n }\n\n // Check Anthropic\n if (ANTHROPIC_API_KEY) {\n try {\n const response = await fetch(buildAnthropicUrl(\"models\"), {\n headers: buildAnthropicHeaders(),\n method: \"GET\"\n });\n providers.push({\n name: \"anthropic\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"anthropic\",\n endpoint: ANTHROPIC_BASE_URL\n });\n } catch (error) {\n providers.push({\n name: \"anthropic\",\n status: \"unhealthy\",\n provider: \"anthropic\",\n error: error instanceof Error ? error.message : String(error),\n endpoint: ANTHROPIC_BASE_URL\n });\n }\n } else {\n providers.push({\n name: \"anthropic\",\n status: \"unconfigured\",\n provider: \"anthropic\",\n error: \"API key not configured\"\n });\n }\n\n // Check xAI\n if (XAI_API_KEY) {\n try {\n const response = await fetch(XAI_BASE_URL + \"/models\", {\n headers: { \"Authorization\": \"Bearer \" + XAI_API_KEY }\n });\n providers.push({\n name: \"xai\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"xai\",\n endpoint: XAI_BASE_URL\n });\n } catch (error) {\n providers.push({\n name: \"xai\",\n status: \"unhealthy\",\n provider: \"xai\",\n error: error instanceof Error ? error.message : String(error),\n endpoint: XAI_BASE_URL\n });\n }\n } else {\n providers.push({\n name: \"xai\",\n status: \"unconfigured\",\n provider: \"xai\",\n error: \"API key not configured\",\n endpoint: XAI_BASE_URL\n });\n }\n\n // Check Ollama\n try {\n console.log(\\`Checking Ollama health at: \\${OLLAMA_BASE_URL}/api/tags\\`);\n const response = await fetch(\\`\\${OLLAMA_BASE_URL}/api/tags\\`);\n const status = response.ok ? \"healthy\" : \"unhealthy\";\n console.log(\\`Ollama health check result: \\${status}\\`);\n providers.push({\n name: \"ollama\",\n status: status,\n provider: \"ollama\",\n url: OLLAMA_BASE_URL\n });\n } catch (error) {\n console.log(\\`Ollama health check error: \\${error.message}\\`);\n providers.push({\n name: \"ollama\",\n status: \"offline\",\n provider: \"ollama\",\n error: error.message,\n url: OLLAMA_BASE_URL\n });\n }\n\n const overallHealthy = providers.some(p => p.status === \"healthy\");\n \n res.json({\n status: overallHealthy ? \"healthy\" : \"unhealthy\",\n version: QUICKSTART_VERSION,\n uptime: Math.round(process.uptime()),\n providers\n });\n});\n\napp.get(\"/api/models\", (_req, res) => {\n res.json({ models: toGatewayModels() });\n});\n\n// ============================================================================\n// ANTHROPIC ROUTES\n// ============================================================================\n\napp.get(\"/api/anthropic/health\", async (_req, res) => {\n try {\n requireAnthropicKey();\n const response = await fetch(buildAnthropicUrl(\"models\"), {\n method: \"GET\",\n headers: buildAnthropicHeaders()\n });\n const isHealthy = response.ok;\n res.json({\n status: isHealthy ? \"healthy\" : \"unhealthy\",\n anthropic_status: isHealthy,\n provider: \"anthropic\",\n endpoint: ANTHROPIC_BASE_URL\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n res.status(503).json({\n status: \"unhealthy\",\n anthropic_status: false,\n provider: \"anthropic\",\n error: message,\n endpoint: ANTHROPIC_BASE_URL\n });\n }\n});\n\napp.post(\"/api/anthropic/chat/completions\", async (req, res) => {\n try {\n requireAnthropicKey();\n const rawBody = req.body ?? {};\n const isStreaming = rawBody.stream === true;\n const requestedModel =\n stripAnthropicModelPrefix(rawBody.model) ??\n stripAnthropicModelPrefix(\"${ctx.defaultModelId}\") ??\n \"claude-3-5-haiku-latest\";\n\n const stopSequences = Array.isArray(rawBody.stop)\n ? rawBody.stop\n : Array.isArray(rawBody.stop_sequences)\n ? rawBody.stop_sequences\n : rawBody.stop\n ? [rawBody.stop]\n : undefined;\n\n const { messages: anthropicMessages, system } = toAnthropicMessages(\n Array.isArray(rawBody.messages) ? rawBody.messages : []\n );\n\n const fallbackText =\n typeof rawBody.prompt === \"string\" && rawBody.prompt.trim().length > 0\n ? rawBody.prompt\n : \"Hello from Bandit quickstart gateway\";\n\n const requestBody = {\n model: requestedModel,\n messages:\n anthropicMessages.length > 0\n ? anthropicMessages\n : [\n {\n role: \"user\",\n content: [{ type: \"text\", text: fallbackText }],\n },\n ],\n stream: isStreaming,\n max_tokens:\n typeof rawBody.max_tokens === \"number\" && rawBody.max_tokens > 0\n ? rawBody.max_tokens\n : ANTHROPIC_MAX_TOKENS,\n };\n\n if (system) {\n requestBody.system = system;\n }\n if (typeof rawBody.temperature === \"number\") {\n requestBody.temperature = rawBody.temperature;\n }\n if (typeof rawBody.top_p === \"number\") {\n requestBody.top_p = rawBody.top_p;\n }\n if (typeof rawBody.top_k === \"number\") {\n requestBody.top_k = rawBody.top_k;\n }\n if (stopSequences) {\n requestBody.stop_sequences = stopSequences;\n }\n if (rawBody.metadata) {\n requestBody.metadata = rawBody.metadata;\n }\n if (rawBody.tools) {\n requestBody.tools = rawBody.tools;\n }\n if (rawBody.tool_choice) {\n requestBody.tool_choice = rawBody.tool_choice;\n }\n if (rawBody.thinking) {\n requestBody.thinking = rawBody.thinking;\n }\n if (rawBody.extra_headers) {\n requestBody.extra_headers = rawBody.extra_headers;\n }\n\n const response = await fetch(buildAnthropicUrl(\"messages\"), {\n method: \"POST\",\n headers: buildAnthropicHeaders(),\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Anthropic chat failed: \\${response.status}\\`,\n details: errorText,\n });\n }\n\n if (isStreaming) {\n await relayAnthropicStream(response, res);\n } else {\n const data = await response.json();\n const normalized = convertAnthropicResponseToGateway(data, requestedModel);\n res.json(normalized);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing ANTHROPIC_API_KEY\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\napp.post(\"/api/anthropic/chat\", async (req, res) => {\n req.url = \"/api/anthropic/chat/completions\";\n return app._router.handle(req, res);\n});\n\napp.post(\"/api/anthropic/completions\", async (req, res) => {\n try {\n requireAnthropicKey();\n const rawBody = req.body ?? {};\n const isStreaming = rawBody.stream === true;\n const requestedModel =\n stripAnthropicModelPrefix(rawBody.model) ??\n stripAnthropicModelPrefix(\"${ctx.defaultModelId}\") ??\n \"claude-3-5-sonnet-latest\";\n\n const stopSequences = Array.isArray(rawBody.stop)\n ? rawBody.stop\n : Array.isArray(rawBody.stop_sequences)\n ? rawBody.stop_sequences\n : rawBody.stop\n ? [rawBody.stop]\n : undefined;\n\n const prompt =\n typeof rawBody.prompt === \"string\" && rawBody.prompt.trim().length > 0\n ? rawBody.prompt\n : \"Hello from Bandit quickstart gateway\";\n\n const { messages, system } = toAnthropicMessages([\n { role: \"user\", content: prompt },\n ]);\n\n const requestBody = {\n model: requestedModel,\n messages,\n stream: isStreaming,\n max_tokens:\n typeof rawBody.max_tokens === \"number\" && rawBody.max_tokens > 0\n ? rawBody.max_tokens\n : ANTHROPIC_MAX_TOKENS,\n };\n\n if (system) {\n requestBody.system = system;\n }\n if (typeof rawBody.temperature === \"number\") {\n requestBody.temperature = rawBody.temperature;\n }\n if (typeof rawBody.top_p === \"number\") {\n requestBody.top_p = rawBody.top_p;\n }\n if (typeof rawBody.top_k === \"number\") {\n requestBody.top_k = rawBody.top_k;\n }\n if (stopSequences) {\n requestBody.stop_sequences = stopSequences;\n }\n if (rawBody.metadata) {\n requestBody.metadata = rawBody.metadata;\n }\n if (rawBody.tools) {\n requestBody.tools = rawBody.tools;\n }\n if (rawBody.tool_choice) {\n requestBody.tool_choice = rawBody.tool_choice;\n }\n\n const response = await fetch(buildAnthropicUrl(\"messages\"), {\n method: \"POST\",\n headers: buildAnthropicHeaders(),\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Anthropic completions failed: \\${response.status}\\`,\n details: errorText,\n });\n }\n\n if (isStreaming) {\n await relayAnthropicStream(response, res);\n } else {\n const data = await response.json();\n const formatted = convertAnthropicResponseToGenerate(data, requestedModel);\n res.json(formatted);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing ANTHROPIC_API_KEY\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\napp.post(\"/api/anthropic/generate\", async (req, res) => {\n req.url = \"/api/anthropic/completions\";\n return app._router.handle(req, res);\n});\n\napp.get(\"/api/anthropic/models\", async (_req, res) => {\n try {\n requireAnthropicKey();\n const response = await fetch(buildAnthropicUrl(\"models\"), {\n method: \"GET\",\n headers: buildAnthropicHeaders(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Anthropic models failed: \\${response.status}\\`,\n details: errorText,\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing ANTHROPIC_API_KEY\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\napp.post(\"/api/anthropic/embed\", async (_req, res) => {\n res.status(501).json({\n error: \"Anthropic embeddings not implemented\",\n message: \"Add support for the Anthropic embeddings endpoint if your use case requires it.\"\n });\n});\n\n// ============================================================================\n// AZURE OPENAI ROUTES\n// ============================================================================\n\napp.get(\"/api/azure/health\", async (_req, res) => {\n try {\n const { endpoint } = requireAzureBaseConfig();\n const deploymentsUrl = buildAzurePath(\"deployments\");\n const response = await fetch(deploymentsUrl, {\n headers: { \"api-key\": AZURE_OPENAI_API_KEY }\n });\n const isHealthy = response.ok;\n res.json({\n status: isHealthy ? \"healthy\" : \"unhealthy\",\n azure_status: isHealthy,\n provider: \"azure\",\n endpoint\n });\n } catch (error) {\n res.status(503).json({\n status: \"unhealthy\",\n azure_status: false,\n provider: \"azure\",\n error: error instanceof Error ? error.message : String(error),\n endpoint: AZURE_OPENAI_ENDPOINT\n });\n }\n});\n\napp.post(\"/api/azure/chat/completions\", async (req, res) => {\n try {\n const { apiKey } = requireAzureBaseConfig();\n const deployment = resolveAzureDeployment(req.body?.model, AZURE_OPENAI_CHAT_DEPLOYMENT, \"chat\");\n const isStreaming = req.body?.stream === true;\n const { provider, model, ...cleanBody } = req.body ?? {};\n const requestBody = { ...cleanBody };\n\n const response = await fetch(buildAzureDeploymentUrl(deployment, \"chat/completions\"), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"api-key\": apiKey\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Azure OpenAI chat failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing Azure OpenAI\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\napp.post(\"/api/azure/chat\", async (req, res) => {\n req.url = \"/api/azure/chat/completions\";\n return app._router.handle(req, res);\n});\n\napp.post(\"/api/azure/completions\", async (req, res) => {\n try {\n const { apiKey } = requireAzureBaseConfig();\n const deployment = resolveAzureDeployment(req.body?.model, AZURE_OPENAI_COMPLETIONS_DEPLOYMENT, \"completions\");\n const isStreaming = req.body?.stream === true;\n const { provider, model, ...cleanBody } = req.body ?? {};\n const requestBody = { ...cleanBody };\n\n const response = await fetch(buildAzureDeploymentUrl(deployment, \"completions\"), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"api-key\": apiKey\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Azure OpenAI completions failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing Azure OpenAI\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\napp.post(\"/api/azure/generate\", async (req, res) => {\n try {\n const { apiKey } = requireAzureBaseConfig();\n const deployment = resolveAzureDeployment(req.body?.model, AZURE_OPENAI_CHAT_DEPLOYMENT, \"chat\");\n const prompt = req.body?.prompt || \"\";\n const isStreaming = req.body?.stream === true;\n\n const chatBody = {\n messages: [\n {\n role: \"user\",\n content: prompt\n }\n ],\n stream: isStreaming,\n max_tokens: req.body?.max_tokens ?? 150,\n temperature: req.body?.temperature ?? 0.7\n };\n\n const response = await fetch(buildAzureDeploymentUrl(deployment, \"chat/completions\"), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"api-key\": apiKey\n },\n body: JSON.stringify(chatBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Azure OpenAI generate failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing Azure OpenAI\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\napp.get(\"/api/azure/models\", async (_req, res) => {\n try {\n requireAzureBaseConfig();\n\n const response = await fetch(buildAzurePath(\"deployments\"), {\n headers: { \"api-key\": AZURE_OPENAI_API_KEY }\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Azure OpenAI models failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing Azure OpenAI\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\napp.post(\"/api/azure/embed\", async (req, res) => {\n try {\n const { apiKey } = requireAzureBaseConfig();\n const deployment = resolveAzureDeployment(req.body?.model, AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT, \"embeddings\");\n const { provider, model, ...cleanBody } = req.body ?? {};\n const requestBody = { ...cleanBody };\n\n const response = await fetch(buildAzureDeploymentUrl(deployment, \"embeddings\"), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"api-key\": apiKey\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Azure OpenAI embed failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing Azure OpenAI\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\n// ============================================================================\n// XAI ROUTES\n// ============================================================================\n\n// xAI Health Check\napp.get(\"/api/xai/health\", async (_req, res) => {\n try {\n const xaiKey = requireXAIKey();\n const response = await fetch(XAI_BASE_URL + \"/models\", {\n headers: { \"Authorization\": \"Bearer \" + xaiKey }\n });\n const isHealthy = response.ok;\n res.json({\n status: isHealthy ? \"healthy\" : \"unhealthy\",\n xai_status: isHealthy,\n provider: \"xai\"\n });\n } catch (error) {\n res.status(503).json({\n status: \"unhealthy\",\n xai_status: false,\n error: error instanceof Error ? error.message : String(error),\n provider: \"xai\"\n });\n }\n});\n\n// xAI Chat Completions\napp.post(\"/api/xai/chat/completions\", async (req, res) => {\n try {\n const xaiKey = requireXAIKey();\n const isStreaming = req.body?.stream === true;\n const { provider, ...cleanBody } = req.body ?? {};\n const requestBody = {\n ...cleanBody,\n model: req.body?.model?.replace(/^xai:/, \"\") || \"grok-2-latest\"\n };\n\n const response = await fetch(XAI_BASE_URL + \"/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \"Bearer \" + xaiKey\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \"xAI chat failed: \" + response.status,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\napp.post(\"/api/xai/chat\", async (req, res) => {\n req.url = \"/api/xai/chat/completions\";\n return app._router.handle(req, res);\n});\n\n// xAI Completions\napp.post(\"/api/xai/completions\", async (req, res) => {\n try {\n const xaiKey = requireXAIKey();\n const isStreaming = req.body?.stream === true;\n const { provider, ...cleanBody } = req.body ?? {};\n const requestBody = {\n ...cleanBody,\n model: req.body?.model?.replace(/^xai:/, \"\") || \"grok-2-mini\"\n };\n\n const response = await fetch(XAI_BASE_URL + \"/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \"Bearer \" + xaiKey\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \"xAI completions failed: \" + response.status,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\n// xAI Generate\napp.post(\"/api/xai/generate\", async (req, res) => {\n try {\n const xaiKey = requireXAIKey();\n const prompt = req.body?.prompt || \"\";\n const model = req.body?.model?.replace(/^xai:/, \"\") || \"grok-2-latest\";\n const isStreaming = req.body?.stream === true;\n\n const chatBody = {\n model,\n messages: [\n { role: \"user\", content: prompt }\n ],\n stream: isStreaming,\n max_tokens: req.body?.max_tokens || 150,\n temperature: req.body?.temperature ?? 0.7\n };\n\n const response = await fetch(XAI_BASE_URL + \"/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \"Bearer \" + xaiKey\n },\n body: JSON.stringify(chatBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \"xAI generate failed: \" + response.status,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const data = await response.json();\n const generateResponse = {\n model,\n created_at: new Date().toISOString(),\n response: data.choices?.[0]?.message?.content || \"\",\n done: true,\n context: [],\n total_duration: 0,\n load_duration: 0,\n prompt_eval_count: data.usage?.prompt_tokens || 0,\n prompt_eval_duration: 0,\n eval_count: data.usage?.completion_tokens || 0,\n eval_duration: 0\n };\n res.json(generateResponse);\n }\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\n// xAI Models\napp.get(\"/api/xai/models\", async (_req, res) => {\n try {\n const xaiKey = requireXAIKey();\n const response = await fetch(XAI_BASE_URL + \"/models\", {\n headers: { \"Authorization\": \"Bearer \" + xaiKey }\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \"xAI models failed: \" + response.status,\n details: errorText\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\n// ============================================================================\n// BANDIT AI ROUTES\n// ============================================================================\n\napp.get(\"/api/bandit/health\", async (_req, res) => {\n try {\n const banditKey = requireBanditKey();\n const response = await fetch(BANDIT_BASE_URL + \"/models\", {\n headers: { \"Authorization\": \\`Bearer \\${banditKey}\\` }\n });\n const isHealthy = response.ok;\n res.json({\n status: isHealthy ? \"healthy\" : \"unhealthy\",\n bandit_status: isHealthy,\n provider: \"bandit\",\n endpoint: BANDIT_BASE_URL\n });\n } catch (error) {\n res.status(503).json({\n status: \"unhealthy\",\n bandit_status: false,\n error: error instanceof Error ? error.message : String(error),\n provider: \"bandit\",\n endpoint: BANDIT_BASE_URL\n });\n }\n});\n\napp.post(\"/api/bandit/chat/completions\", async (req, res) => {\n try {\n const banditKey = requireBanditKey();\n const isStreaming = req.body?.stream === true;\n const { provider, ...cleanBody } = req.body ?? {};\n const providerName = typeof provider === \"string\" ? provider : \"bandit\";\n const requestBody = {\n ...cleanBody,\n model: req.body?.model?.replace(/^bandit:/, \"\") || \"bandit-core-1\"\n };\n\n if (\n providerName !== \"ollama\" &&\n Array.isArray(requestBody.images) &&\n requestBody.images.length > 0 &&\n Array.isArray(requestBody.messages)\n ) {\n const lastUserIndex = requestBody.messages.map((message) => message?.role).lastIndexOf(\"user\");\n if (lastUserIndex !== -1) {\n const targetMessage = requestBody.messages[lastUserIndex] ?? {};\n const baseContent = Array.isArray(targetMessage.content)\n ? targetMessage.content.filter(Boolean)\n : typeof targetMessage.content === \"string\" && targetMessage.content.trim().length > 0\n ? [{ type: \"text\", text: targetMessage.content }]\n : [];\n\n const imageContent = requestBody.images\n .map((entry) => {\n const url = normalizeGatewayImageUrl(entry);\n if (!url) {\n return null;\n }\n return {\n type: \"image_url\",\n image_url: {\n url,\n detail: extractGatewayImageDetail(entry) ?? \"auto\"\n }\n };\n })\n .filter(Boolean);\n\n if (imageContent.length > 0) {\n requestBody.messages[lastUserIndex] = {\n ...targetMessage,\n content: [...baseContent, ...imageContent]\n };\n }\n }\n delete requestBody.images;\n }\n\n const response = await fetch(BANDIT_BASE_URL + \"/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \\`Bearer \\${banditKey}\\`\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Bandit chat failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\napp.post(\"/api/bandit/chat\", async (req, res) => {\n req.url = \"/api/bandit/chat/completions\";\n return app._router.handle(req, res);\n});\n\napp.post(\"/api/bandit/completions\", async (req, res) => {\n try {\n const banditKey = requireBanditKey();\n const isStreaming = req.body?.stream === true;\n const { provider, ...cleanBody } = req.body ?? {};\n const requestBody = {\n ...cleanBody,\n model: req.body?.model?.replace(/^bandit:/, \"\") || \"bandit-core-1\"\n };\n\n const response = await fetch(BANDIT_BASE_URL + \"/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \\`Bearer \\${banditKey}\\`\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Bandit completions failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\napp.post(\"/api/bandit/generate\", async (req, res) => {\n try {\n const banditKey = requireBanditKey();\n const prompt = req.body?.prompt || \"\";\n const model = req.body?.model?.replace(/^bandit:/, \"\") || \"bandit-core-1\";\n const isStreaming = req.body?.stream === true;\n\n const chatBody = {\n model,\n messages: [\n { role: \"user\", content: prompt }\n ],\n stream: isStreaming,\n max_tokens: req.body?.max_tokens || 256,\n temperature: req.body?.temperature ?? 0.7\n };\n\n const response = await fetch(BANDIT_BASE_URL + \"/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \\`Bearer \\${banditKey}\\`\n },\n body: JSON.stringify(chatBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Bandit generate failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const data = await response.json();\n const generateResponse = {\n model,\n created_at: new Date().toISOString(),\n response: data.choices?.[0]?.message?.content || \"\",\n done: true,\n context: [],\n total_duration: 0,\n load_duration: 0,\n prompt_eval_count: data.usage?.prompt_tokens || 0,\n prompt_eval_duration: 0,\n eval_count: data.usage?.completion_tokens || 0,\n eval_duration: 0\n };\n res.json(generateResponse);\n }\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\napp.get(\"/api/bandit/models\", async (_req, res) => {\n try {\n const banditKey = requireBanditKey();\n const response = await fetch(BANDIT_BASE_URL + \"/models\", {\n headers: { \"Authorization\": \\`Bearer \\${banditKey}\\` }\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Bandit models failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\n// ============================================================================\n// OPENAI ROUTES\n// ============================================================================\n\n// OpenAI Health Check\napp.get(\"/api/openai/health\", async (_req, res) => {\n try {\n const openaiKey = process.env.OPENAI_API_KEY;\n if (!openaiKey) {\n return res.status(503).json({\n status: \"unhealthy\",\n openai_status: false,\n error: \"OpenAI API key not configured\",\n provider: \"openai\"\n });\n }\n\n const response = await fetch(\"https://api.openai.com/v1/models\", {\n headers: { \"Authorization\": \\`Bearer \\${openaiKey}\\` }\n });\n\n const isHealthy = response.ok;\n res.json({\n status: isHealthy ? \"healthy\" : \"unhealthy\",\n openai_status: isHealthy,\n provider: \"openai\"\n });\n } catch (error) {\n res.status(503).json({\n status: \"unhealthy\",\n openai_status: false,\n error: error.message,\n provider: \"openai\"\n });\n }\n});\n\n// OpenAI Chat Completions\napp.post(\"/api/openai/chat/completions\", async (req, res) => {\n try {\n const openaiKey = requireOpenAIKey();\n const isStreaming = req.body?.stream === true;\n\n // Strip the openai: prefix from model name and remove provider field\n const { provider, ...cleanBody } = req.body;\n const requestBody = {\n ...cleanBody,\n model: req.body?.model?.replace(/^openai:/, \"\") || \"gpt-4o\"\n };\n\n const response = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \\`Bearer \\${openaiKey}\\`\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`OpenAI chat failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// OpenAI Chat (alternative route)\napp.post(\"/api/openai/chat\", async (req, res) => {\n // Route to the completions endpoint for compatibility\n req.url = \"/api/openai/chat/completions\";\n return app._router.handle(req, res);\n});\n\n// OpenAI Completions\napp.post(\"/api/openai/completions\", async (req, res) => {\n try {\n const openaiKey = requireOpenAIKey();\n const isStreaming = req.body?.stream === true;\n\n // Strip the openai: prefix from model name and remove provider field\n const { provider, ...cleanBody } = req.body;\n const requestBody = {\n ...cleanBody,\n model: req.body?.model?.replace(/^openai:/, \"\") || \"gpt-3.5-turbo-instruct\"\n };\n\n const response = await fetch(\"https://api.openai.com/v1/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \\`Bearer \\${openaiKey}\\`\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`OpenAI completions failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// OpenAI Generate (converts to chat format for conversation starters)\napp.post(\"/api/openai/generate\", async (req, res) => {\n try {\n const openaiKey = requireOpenAIKey();\n const prompt = req.body?.prompt || \"\";\n const model = req.body?.model?.replace(/^openai:/, \"\") || \"gpt-4o\";\n const isStreaming = req.body?.stream === true;\n\n // Convert generate request to chat format\n const chatBody = {\n model: model,\n messages: [\n {\n role: \"user\",\n content: prompt\n }\n ],\n stream: isStreaming,\n max_tokens: req.body?.max_tokens || 150,\n temperature: req.body?.temperature || 0.7\n };\n\n const response = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \\`Bearer \\${openaiKey}\\`\n },\n body: JSON.stringify(chatBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`OpenAI generate failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const data = await response.json();\n // Convert chat response back to generate format\n const generateResponse = {\n model: model,\n created_at: new Date().toISOString(),\n response: data.choices?.[0]?.message?.content || \"\",\n done: true,\n context: [],\n total_duration: 0,\n load_duration: 0,\n prompt_eval_count: 0,\n prompt_eval_duration: 0,\n eval_count: data.usage?.completion_tokens || 0,\n eval_duration: 0\n };\n res.json(generateResponse);\n }\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// OpenAI Models\napp.get(\"/api/openai/models\", async (_req, res) => {\n try {\n const openaiKey = requireOpenAIKey();\n\n const response = await fetch(\"https://api.openai.com/v1/models\", {\n headers: { \"Authorization\": \\`Bearer \\${openaiKey}\\` }\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`OpenAI models failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// ============================================================================\n// OLLAMA ROUTES\n// ============================================================================\n\n// Ollama Health Check\napp.get(\"/api/ollama/health\", async (_req, res) => {\n try {\n console.log(\\`Ollama health check at: \\${OLLAMA_BASE_URL}/api/tags\\`);\n const response = await fetch(\\`\\${OLLAMA_BASE_URL}/api/tags\\`);\n const isHealthy = response.ok;\n\n res.json({\n status: isHealthy ? \"healthy\" : \"unhealthy\",\n ollama_status: isHealthy,\n provider: \"ollama\",\n url: OLLAMA_BASE_URL\n });\n } catch (error) {\n console.log(\\`Ollama health check error: \\${error.message}\\`);\n res.status(503).json({\n status: \"offline\",\n ollama_status: false,\n error: error.message,\n provider: \"ollama\",\n url: OLLAMA_BASE_URL\n });\n }\n});\n\n// Ollama Chat\napp.post(\"/api/ollama/chat\", async (req, res) => {\n try {\n const response = await fetch(\\`\\${OLLAMA_BASE_URL}/api/chat\\`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(req.body)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Ollama chat failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const isStreaming = req.body?.stream === true;\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// Ollama Generate\napp.post(\"/api/ollama/generate\", async (req, res) => {\n try {\n const response = await fetch(\\`\\${OLLAMA_BASE_URL}/api/generate\\`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(req.body)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Ollama generate failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const isStreaming = req.body?.stream === true;\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// Ollama Models\napp.get(\"/api/ollama/models\", async (_req, res) => {\n try {\n const response = await fetch(\\`\\${OLLAMA_BASE_URL}/api/tags\\`);\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Ollama models failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// Ollama Embedding\napp.post(\"/api/ollama/embed\", async (req, res) => {\n try {\n const response = await fetch(\\`\\${OLLAMA_BASE_URL}/api/embeddings\\`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(req.body)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Ollama embed failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// ============================================================================\n// TTS ROUTES (not implemented - placeholder for compatibility)\n// ============================================================================\n\n// TTS Main endpoint\napp.post(\"/api/tts\", async (_req, res) => {\n res.status(501).json({\n error: \"TTS integration not implemented\",\n message: \"Text-to-speech functionality is not available in this quickstart gateway\"\n });\n});\n\n// TTS Stream endpoint\napp.post(\"/api/tts/stream\", async (_req, res) => {\n res.status(501).json({\n error: \"TTS streaming not implemented\", \n message: \"Text-to-speech streaming functionality is not available in this quickstart gateway\"\n });\n});\n\n// TTS Real-time stream endpoint\napp.post(\"/api/tts/stream-realtime\", async (_req, res) => {\n res.status(501).json({\n error: \"TTS real-time streaming not implemented\",\n message: \"Text-to-speech real-time streaming functionality is not available in this quickstart gateway\"\n });\n});\n\n// TTS Models endpoint - returns empty models\napp.get(\"/api/tts/models\", async (_req, res) => {\n res.json({\n models: []\n });\n});\n\n// TTS Available models endpoint - returns empty models with defaults\napp.get(\"/api/tts/available-models\", async (_req, res) => {\n res.json({\n models: [],\n defaultModel: null,\n fallbackModel: null\n });\n});\n\n// ============================================================================\n// MCP (Model Context Protocol) TOOL ROUTES\n// ============================================================================\n\n// MCP Health Check\napp.get(\"/api/mcp/health\", async (_req, res) => {\n res.json({\n status: \"healthy\",\n timestamp: new Date().toISOString(),\n totalTools: 0,\n enabledTools: 0,\n availableTools: [],\n message: \"MCP tools are not implemented in this quickstart gateway\"\n });\n});\n\n// Get available MCP tools\napp.get(\"/api/mcp/tools\", async (_req, res) => {\n res.json([]);\n});\n\n// News endpoint - placeholder implementation\napp.get(\"/api/mcp/news\", async (req, res) => {\n const { topic = \"general\", count = 10, headlines = false } = req.query;\n \n res.status(501).json({\n error: \"News service not implemented\",\n message: \"MCP news functionality is not available in this quickstart gateway\",\n suggestion: \"Implement this endpoint to connect to a news API service\",\n requestedParams: { topic, count, headlines }\n });\n});\n\n// Weather endpoint - placeholder implementation \napp.get(\"/api/mcp/weather\", async (req, res) => {\n const { zip, latitude, longitude } = req.query;\n \n res.status(501).json({\n error: \"Weather service not implemented\", \n message: \"MCP weather functionality is not available in this quickstart gateway\",\n suggestion: \"Implement this endpoint to connect to a weather API service\",\n requestedParams: { zip, latitude, longitude }\n });\n});\n\n// Documentation search endpoint - placeholder implementation\napp.get(\"/api/mcp/docs\", async (req, res) => {\n const { query, framework, count = 10 } = req.query;\n \n if (!query) {\n return res.status(400).json({\n error: \"Query parameter is required\",\n message: \"Please provide a search query\"\n });\n }\n \n res.status(501).json({\n error: \"Documentation search not implemented\",\n message: \"MCP docs functionality is not available in this quickstart gateway\", \n suggestion: \"Implement this endpoint to connect to a documentation search service\",\n requestedParams: { query, framework, count }\n });\n});\n\n// Get supported documentation frameworks\napp.get(\"/api/mcp/docs/frameworks\", async (_req, res) => {\n res.json({\n frameworks: [],\n message: \"Documentation frameworks not configured in this quickstart gateway\"\n });\n});\n\n// Sports scores endpoint - placeholder implementation\napp.get(\"/api/mcp/sports\", async (req, res) => {\n const { league, date } = req.query;\n \n res.status(501).json({\n error: \"Sports service not implemented\",\n message: \"MCP sports functionality is not available in this quickstart gateway\",\n suggestion: \"Implement this endpoint to connect to a sports API service\", \n requestedParams: { league, date }\n });\n});\n\n// Get supported sports leagues\napp.get(\"/api/mcp/sports/leagues\", async (_req, res) => {\n res.json({\n leagues: [],\n message: \"Sports leagues not configured in this quickstart gateway\"\n });\n});\n\n// Image generation endpoint - placeholder implementation\napp.post(\"/api/mcp/generate-image\", async (req, res) => {\n const { prompt, size, quality, style } = req.body;\n \n if (!prompt) {\n return res.status(400).json({\n error: \"Prompt is required\",\n message: \"Please provide a prompt for image generation\"\n });\n }\n \n res.status(501).json({\n success: false,\n error: \"Image generation not implemented\",\n message: \"MCP image generation functionality is not available in this quickstart gateway\",\n suggestion: \"Implement this endpoint to connect to an image generation API service\",\n requestedParams: { prompt, size, quality, style }\n });\n});\n\n// ============================================================================\n// NOT IMPLEMENTED ROUTES (for graceful degradation)\n// ============================================================================\n\napp.all(\"/api/anthropic/*\", (_req, res) => {\n res.status(501).json({\n error: \"Anthropic route not implemented\",\n message: \"Extend the quickstart gateway if you need additional Anthropic endpoints beyond the defaults.\"\n });\n});\n\nconst port = Number(process.env.PORT ?? ${ctx.gatewayPort});\napp.listen(port, () => {\n console.log(\"⚡ Bandit quickstart gateway ready on http://localhost:\" + port);\n console.log(\"📡 Supported providers: Bandit AI, OpenAI, Azure OpenAI, Anthropic, XAI, Ollama\");\n console.log(\"🔗 Provider-specific routes:\");\n console.log(\" • /api/bandit/* - Bandit AI endpoints\");\n console.log(\" • /api/openai/* - OpenAI endpoints\");\n console.log(\" • /api/azure/* - Azure OpenAI endpoints\");\n console.log(\" • /api/anthropic/* - Anthropic endpoints\");\n console.log(\" • /api/xai/* - XAI endpoints\");\n console.log(\" • /api/ollama/* - Ollama endpoints\");\n console.log(\" • /api/health - Overall health check\");\n});\n`;\n\n return ensureTrailingNewline(normalizeLineEndings(gatewaySource));\n};\n\nexport const buildGitignore = (): string =>\n ensureTrailingNewline(\n normalizeLineEndings(\n `node_modules\\n.env\\n.vite\\n.idea\\n.DS_Store\\ncoverage\\ndist\\n`\n )\n );\n\nexport const buildReadme = (ctx: QuickstartTemplateContext): string =>\n ensureTrailingNewline(\n normalizeLineEndings(\n `# ${ctx.projectTitle} — Bandit Quickstart\\n\\nThis project was generated by the Bandit Engine CLI. It ships with a React + Vite frontend that consumes \\`@burtson-labs/bandit-engine\\`, a lightweight Express gateway you can adapt for production, and a Next.js App Router API scaffold in \\`server/next-app/\\`.\\n\\n## 🚀 Next steps\\n- \\`npm install\\`\\n- \\`cp .env.example .env\\`\\n- Fill in your Bandit AI, OpenAI, Azure OpenAI, Anthropic, or xAI credentials (or point \\`OLLAMA_URL\\` at your local server)\\n- \\`npm run dev\\`\\n\\nThe command runs the gateway and the frontend together. Visit http://localhost:${ctx.frontendPort} to see the chat and modal in action.\\n\\n## 🔧 Customizing your assistant\\n- **Branding & personas**: edit \\`public/config.json\\` to tweak logos, colors, and starter models.\\n- **Provider defaults**: update \\`.env\\` to switch providers or change the default upstream model IDs.\\n- **Gateway routes**: open \\`server/gateway.js\\` to add auth, logging, or connect additional providers.\\n\\n## 📦 What’s inside\\n- React + Vite 5 with Material UI theming\\n- Bandit chat surface + modal wired via \\`ChatProvider\\`\\n- Express gateway proxying Bandit AI, OpenAI, Azure OpenAI, Anthropic, XAI, or Ollama to keep API keys server-side\\n- Next.js App Router gateway scaffold in 'server/next-app/' for projects that prefer Next\\n- Friendly defaults you can evolve into your production stack\\n\\nNeed more? Run \\`npx @burtson-labs/bandit-engine create --help\\` to explore additional options.\\n`\n )\n );\n"],"mappings":"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AI;AAAA,EACrD;AAAA,EACA,eAAe,CAAC;AAClB,CAAC;AAEH,IAAM,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAogBjC,IAAM,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+LnC,IAAM,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BnC,IAAM,+BAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsB9B,IAAM,qBAAqB,CAAC,QAA2C;AAC5E,QAAM,gBAAgB,IAAI,kBAAkB,IAAI,IAAI,eAAe,MAAM;AACzE,SAAO;AAAA,IACL;AAAA,MACE,yBACG,QAAQ,yBAAyB,IAAI,eAAe,EACpD,QAAQ,sBAAsB,IAAI,cAAc,EAChD,QAAQ,uBAAuB,aAAa;AAAA,IACjD;AAAA,EACF;AACF;AAEO,IAAM,uBAAuB,MAClC,sBAAsB,qBAAqB,0BAA0B,CAAC;AAEjE,IAAM,uBAAuB,CAAC,QAA2C;AAC9E,QAAM,mBAAmB,KAAK,UAAU,IAAI,eAAe,MAAM,CAAC;AAClE,SAAO;AAAA,IACL;AAAA,MACE,2BAA2B,QAAQ,sBAAsB,gBAAgB;AAAA,IAC3E;AAAA,EACF;AACF;AAEO,IAAM,yBAAyB,MACpC,sBAAsB,qBAAqB,4BAA4B,CAAC;AAEnE,IAAM,qBAAqB,CAAC,QAA2C;AAC5E,QAAM,mBAAmB,KAAK,UAAU,IAAI,eAAe,MAAM,CAAC;AAElE,QAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAYI,IAAI,eAAe;AAAA,8BACjB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCA+lBX,IAAI,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCA6GlB,IAAI,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":["import_node_path","import_node_path","path","fs","prompts","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/index.ts","../package.json","../src/cli/createQuickstart.ts","../src/cli/utils.ts","../src/cli/templates.ts"],"sourcesContent":["/*\n © 2025 Burtson Labs — Licensed under Business Source License 1.1\n https://burtson.ai/license\n\n This file is protected intellectual property.\n Do NOT use in commercial software, prompts, AI training data, or derivative works without a valid commercial license.\n\n 🚫 AI NOTICE: This file contains visible and invisible watermarks.\n ⚖️ VIOLATION NOTICE: Removing, modifying, or obscuring these watermarks is a license violation.\n 🔒 LICENSE TERMINATION: Upon license termination, ALL forks, copies, and derivatives must be permanently deleted.\n 📋 AUDIT TRAIL: File usage is logged and monitored for compliance verification.\n*/\n\n// Bandit Engine Watermark: BL-WM-4003-ED009B\nconst __banditFingerprint_cli_indexts = 'BL-FP-EBD653-077C';\nconst __auditTrail_cli_indexts = 'BL-AU-MGOIKVV7-9HPQ';\n// File: index.ts | Path: src/cli/index.ts | Hash: 4003077c\n\n/* eslint-disable no-console */\nimport path from \"node:path\";\nimport { Command } from \"commander\";\nimport packageJson from \"../../package.json\";\nimport { createQuickstartProject } from \"./createQuickstart\";\n\nconst logIntro = () => {\n console.log(\"🥷 Bandit CLI — Burtson Labs 🧪\");\n};\n\nconst program = new Command();\n\nprogram\n .name(\"bandit\")\n .description(\"Bandit Engine developer utilities\")\n .version(packageJson.version)\n .showHelpAfterError();\n\nprogram\n .command(\"create\")\n .description(\"Scaffold a Bandit quickstart project with a frontend and gateway\")\n .argument(\"[directory]\", \"Relative path for your new project\", \"bandit-quickstart\")\n .option(\"-f, --force\", \"Overwrite the target directory if it already contains files\", false)\n .option(\"--branding-text <text>\", \"Assistant display name shown in the UI\")\n .option(\n \"--provider <provider>\",\n \"Default gateway provider (openai, azure, anthropic, ollama)\"\n )\n .option(\"--frontend-port <port>\", \"Frontend dev server port (default: 5183)\", (value) =>\n parseInt(value, 10)\n )\n .option(\"--gateway-port <port>\", \"Gateway port (default: 8080)\", (value) => parseInt(value, 10))\n .option(\"-y, --yes\", \"Skip interactive prompts and accept defaults\", false)\n .option(\"--skip-prompts\", \"Alias for --yes\", false)\n .action(async (directory: string, cmdOptions: Record<string, unknown>) => {\n try {\n const targetDir = directory ?? \"bandit-quickstart\";\n const projectName = path.basename(path.resolve(process.cwd(), targetDir));\n logIntro();\n const skipPrompts = Boolean(cmdOptions.skipPrompts ?? cmdOptions.yes);\n const result = await createQuickstartProject({\n targetDir,\n projectName,\n force: Boolean(cmdOptions.force),\n brandingText: cmdOptions.brandingText as string | undefined,\n provider: typeof cmdOptions.provider === \"string\" ? (cmdOptions.provider as string) : undefined,\n frontendPort: Number.isFinite(cmdOptions.frontendPort as number)\n ? (cmdOptions.frontendPort as number)\n : undefined,\n gatewayPort: Number.isFinite(cmdOptions.gatewayPort as number)\n ? (cmdOptions.gatewayPort as number)\n : undefined,\n skipPrompts,\n });\n\n const relativeDir = path.relative(process.cwd(), result.projectDir) || \".\";\n console.log(\"\\n✅ Quickstart ready!\");\n console.log(` Location: ${result.projectDir}`);\n console.log(` Package: ${result.packageName}`);\n console.log(` App name: ${result.brandingText}`);\n console.log(` Frontend: http://localhost:${result.frontendPort}`);\n console.log(` Gateway: http://localhost:${result.gatewayPort}`);\n\n console.log(\"\\nNext steps:\");\n console.log(` cd ${relativeDir}`);\n console.log(\" npm install\");\n console.log(\" cp .env.example .env\");\n console.log(\" npm run dev\");\n console.log(\"\");\n console.log(\"🔍 Before you dive in:\");\n console.log(\" • Open .env to confirm the provider credentials and URLs match your setup.\");\n console.log(\" • server/gateway.js is a scaffold Express proxy that keeps API keys server-side—extend it with auth, logging, and your production logic.\");\n console.log(\" • If you prefer Next.js App Router, check server/next-app/ for a starter route handler.\");\n console.log(\"\");\n } catch (error) {\n const message =\n error instanceof Error ? error.message : \"Failed to create Bandit quickstart project.\";\n console.error(`\\n❌ ${message}`);\n process.exitCode = 1;\n }\n });\n\nasync function main() {\n await program.parseAsync(process.argv);\n}\n\nmain().catch((error) => {\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n});\n","{\n \"name\": \"@burtson-labs/bandit-engine\",\n \"version\": \"2.0.52\",\n \"license\": \"BUSL-1.1\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"types\": \"dist/index.d.ts\",\n \"bin\": {\n \"bandit\": \"dist/cli.js\"\n },\n \"files\": [\n \"dist\",\n \"dist/cli\",\n \"docs\",\n \"LICENSE\",\n \"README.md\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Burtson-Labs/bandit-engine.git\"\n },\n \"homepage\": \"https://banditailabs.com/npm-package\",\n \"author\": \"Burtson Labs LLC <team@banditai.ai>\",\n \"maintainers\": [\n {\n \"name\": \"Burtson Labs LLC\",\n \"email\": \"team@banditai.ai\"\n }\n ],\n \"keywords\": [\n \"ai\",\n \"chat\",\n \"react\",\n \"mui\",\n \"llm\",\n \"frontend\",\n \"openai\",\n \"ollama\",\n \"chatbot\",\n \"sdk\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"docs\": \"typedoc src --out docs/api_reference --skipErrorChecking --sourceLinkTemplate \\\"https://github.com/Burtson-Labs/bandit-engine/blob/main/{path}#L{line}\\\" && node ./scripts/post-typedoc.mjs\",\n \"lint\": \"eslint \\\"src/**/*.{ts,tsx}\\\"\",\n \"test\": \"vitest\",\n \"protect\": \"node scripts/add-license-headers.js\",\n \"validate-protection\": \"node scripts/validate-protection.js\"\n },\n \"overrides\": {\n \"sha.js\": \"^2.4.12\"\n },\n \"dependencies\": {\n \"@emotion/react\": \"^11.14.0\",\n \"@emotion/styled\": \"^11.14.0\",\n \"@mui/icons-material\": \"^7.0.1\",\n \"@mui/material\": \"^7.1.0\",\n \"@tanstack/react-query\": \"^5.66.3\",\n \"axios\": \"^1.7.9\",\n \"commander\": \"^12.1.0\",\n \"fs-extra\": \"^11.3.0\",\n \"highlight.js\": \"^11.10.0\",\n \"idb\": \"latest\",\n \"lowlight\": \"^3.1.0\",\n \"mammoth\": \"^1.9.0\",\n \"pdfjs-dist\": \"^5.2.133\",\n \"prompts\": \"^2.4.2\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"react-markdown\": \"^10.1.0\",\n \"react-router-dom\": \"^7.5.0\",\n \"rehype-raw\": \"^7.0.0\",\n \"rehype-sanitize\": \"^6.0.0\",\n \"remark-gfm\": \"^4.0.1\",\n \"rxjs\": \"^7.8.2\",\n \"uuid\": \"^11.1.0\",\n \"zustand\": \"^4.5.6\"\n },\n \"devDependencies\": {\n \"@testing-library/jest-dom\": \"^6.6.3\",\n \"@testing-library/react\": \"^16.3.0\",\n \"@types/fs-extra\": \"^11.0.4\",\n \"@types/node\": \"^24.0.3\",\n \"@types/prompts\": \"^2.4.9\",\n \"@types/react\": \"^18.3.22\",\n \"@types/react-dom\": \"^18.2.17\",\n \"@types/uuid\": \"^10.0.0\",\n \"@vitejs/plugin-react\": \"^4.6.0\",\n \"eslint\": \"^8.57.0\",\n \"eslint-plugin-react\": \"^7.34.1\",\n \"jsdom\": \"^26.1.0\",\n \"tsup\": \"^8.5.0\",\n \"typedoc\": \"^0.26.11\",\n \"typescript\": \"5.5.4\",\n \"vitest\": \"^3.2.4\"\n },\n \"peerDependencies\": {\n \"@mui/material\": \">=5\",\n \"react\": \">=18\",\n \"zustand\": \">=4\"\n },\n \"exports\": {\n \".\": {\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n },\n \"./types\": {\n \"types\": \"./dist/public-types.d.ts\",\n \"default\": \"./dist/public-types.d.ts\"\n }\n }\n}\n","/*\n © 2025 Burtson Labs — Licensed under Business Source License 1.1\n https://burtson.ai/license\n\n This file is protected intellectual property.\n Do NOT use in commercial software, prompts, AI training data, or derivative works without a valid commercial license.\n\n 🚫 AI NOTICE: This file contains visible and invisible watermarks.\n ⚖️ VIOLATION NOTICE: Removing, modifying, or obscuring these watermarks is a license violation.\n 🔒 LICENSE TERMINATION: Upon license termination, ALL forks, copies, and derivatives must be permanently deleted.\n 📋 AUDIT TRAIL: File usage is logged and monitored for compliance verification.\n*/\n\n// Bandit Engine Watermark: BL-WM-BC17-F7FDDE\nconst __banditFingerprint_cli_createQuickstartts = 'BL-FP-34201E-F7E5';\nconst __auditTrail_cli_createQuickstartts = 'BL-AU-MGOIKVV7-FEUF';\n// File: createQuickstart.ts | Path: src/cli/createQuickstart.ts | Hash: bc17f7e5\n\nimport path from \"node:path\";\nimport fs from \"fs-extra\";\nimport prompts, { PromptObject } from \"prompts\";\nimport packageJson from \"../../package.json\";\nimport {\n ensureTrailingNewline,\n sanitizeModelIdentifier,\n toKebabCase,\n toTitleCase,\n} from \"./utils\";\nimport {\n buildAppTsx,\n buildBrandingConfig,\n buildEnvDts,\n buildEnvExample,\n buildGatewayServer,\n buildGitignore,\n buildIndexCss,\n buildIndexHtml,\n buildMainTsx,\n buildPackageJson,\n buildReadme,\n buildThemeTs,\n buildTsConfig,\n buildViteConfig,\n buildNextChatRoute,\n buildNextHealthRoute,\n buildNextModelsRoute,\n buildNextGatewayReadme,\n QuickstartTemplateContext,\n} from \"./templates\";\n\ntype SupportedProvider = \"openai\" | \"ollama\" | \"azure\" | \"anthropic\" | \"xai\" | \"bandit\";\n\ninterface LogoResolution {\n dataUrl: string;\n fileName: string;\n fileContent: Buffer;\n hasTransparentLogo: boolean;\n isDefault: boolean;\n}\n\ninterface CreateQuickstartInputs {\n targetDir: string;\n projectTitle: string;\n packageName: string;\n brandingText: string;\n provider: SupportedProvider;\n defaultModelId: string;\n fallbackModelId?: string;\n gatewayPort: number;\n frontendPort: number;\n logo: LogoResolution;\n}\n\nexport interface CreateQuickstartOptions {\n targetDir: string;\n projectName?: string;\n force?: boolean;\n provider?: string;\n brandingText?: string;\n defaultModelId?: string;\n fallbackModelId?: string;\n gatewayPort?: number;\n frontendPort?: number;\n skipPrompts?: boolean;\n}\n\ninterface QuickstartResult {\n projectDir: string;\n packageName: string;\n brandingText: string;\n defaultModelId: string;\n fallbackModelId?: string;\n gatewayPort: number;\n frontendPort: number;\n createdFiles: string[];\n}\n\nexport const createQuickstartProject = async (\n options: CreateQuickstartOptions\n): Promise<QuickstartResult> => {\n const resolvedDir = path.resolve(process.cwd(), options.targetDir);\n const rawProjectName = options.projectName ?? path.basename(resolvedDir);\n const packageName = normalizePackageName(rawProjectName);\n const projectTitle = toTitleCase(rawProjectName) || \"Bandit Quickstart\";\n\n await ensureWritableDirectory(resolvedDir, Boolean(options.force));\n\n const skipPrompts = Boolean(options.skipPrompts);\n\n const provider = options.provider\n ? normalizeProvider(options.provider)\n : skipPrompts\n ? \"ollama\"\n : await promptForProvider();\n\n const promptAnswers = skipPrompts\n ? {}\n : await promptForMissingData({\n brandingText: options.brandingText,\n provider,\n });\n\n const brandingText =\n options.brandingText ??\n (typeof promptAnswers.brandingText === \"string\" && promptAnswers.brandingText.trim().length > 0\n ? promptAnswers.brandingText.trim()\n : `${projectTitle} Assistant`);\n\n // Package handles CDN logos automatically, no need for logo resolution\n const logoResolution: LogoResolution = {\n dataUrl: \"\",\n fileName: \"\",\n fileContent: Buffer.alloc(0),\n hasTransparentLogo: true,\n isDefault: true\n };\n\n const gatewayPort = sanitizePort(options.gatewayPort ?? promptAnswers.gatewayPort ?? 5151);\n const frontendPort = sanitizePort(options.frontendPort ?? promptAnswers.frontendPort ?? 5183);\n const defaultModelId = sanitizeModelIdentifier(\n options.defaultModelId ?? inferDefaultModelId(provider)\n );\n const fallbackModelRaw = options.fallbackModelId\n ? options.fallbackModelId\n : inferFallbackModelId(provider, defaultModelId);\n const fallbackModelId = fallbackModelRaw\n ? sanitizeModelIdentifier(fallbackModelRaw)\n : undefined;\n\n const inputs: CreateQuickstartInputs = {\n targetDir: resolvedDir,\n projectTitle,\n packageName,\n brandingText,\n provider,\n defaultModelId,\n fallbackModelId,\n gatewayPort,\n frontendPort,\n logo: logoResolution,\n };\n\n const createdFiles = await writeProject(inputs);\n\n return {\n projectDir: resolvedDir,\n packageName,\n brandingText,\n defaultModelId,\n fallbackModelId,\n gatewayPort,\n frontendPort,\n createdFiles,\n };\n};\n\nconst normalizePackageName = (input: string): string => {\n const fallback = \"bandit-quickstart\";\n const kebab = toKebabCase(input || fallback);\n if (!kebab) {\n return fallback;\n }\n return /^[a-z]/.test(kebab) ? kebab : `bandit-${kebab}`;\n};\n\nconst ensureWritableDirectory = async (dir: string, force: boolean) => {\n const exists = await fs.pathExists(dir);\n if (!exists) {\n await fs.ensureDir(dir);\n return;\n }\n\n const entries = await fs.readdir(dir);\n if (entries.length > 0 && !force) {\n throw new Error(\n `Target directory \"${dir}\" is not empty. Re-run with --force to overwrite or choose a new folder.`\n );\n }\n};\n\nconst normalizeProvider = (value?: string): SupportedProvider => {\n const normalized = (value ?? \"openai\").toLowerCase();\n if (normalized === \"ollama\") {\n return \"ollama\";\n }\n if (normalized === \"azure\" || normalized === \"azure-openai\" || normalized === \"azureopenai\") {\n return \"azure\";\n }\n if (normalized === \"anthropic\") {\n return \"anthropic\";\n }\n if (normalized === \"xai\" || normalized === \"grok\") {\n return \"xai\";\n }\n if (normalized === \"bandit\" || normalized === \"banditai\" || normalized === \"bandit-ai\") {\n return \"bandit\";\n }\n return \"openai\";\n};\n\nconst inferDefaultModelId = (provider: SupportedProvider): string => {\n if (provider === \"ollama\") {\n return \"llama3.1\";\n }\n if (provider === \"azure\") {\n return \"azure:gpt-4o\";\n }\n if (provider === \"anthropic\") {\n return \"anthropic:claude-3-5-haiku-latest\";\n }\n if (provider === \"xai\") {\n return \"xai:grok-2-latest\";\n }\n if (provider === \"bandit\") {\n return \"bandit-core-1\";\n }\n return \"openai:gpt-4o-mini\";\n};\n\nconst inferFallbackModelId = (provider: SupportedProvider, defaultId: string): string | undefined => {\n if (provider === \"ollama\") {\n const normalized = defaultId.toLowerCase();\n if (normalized.startsWith(\"llama3\")) {\n return \"llama2\";\n }\n return \"llama3\";\n }\n if (provider === \"azure\") {\n return defaultId === \"azure:gpt-4o-mini\" ? \"azure:gpt-4o\" : \"azure:gpt-4o-mini\";\n }\n if (provider === \"anthropic\") {\n return defaultId === \"anthropic:claude-3-5-haiku-latest\"\n ? \"anthropic:claude-3-haiku-20240307\"\n : \"anthropic:claude-3-5-haiku-latest\";\n }\n if (provider === \"xai\") {\n return defaultId === \"xai:grok-2-mini\" ? \"xai:grok-2-latest\" : \"xai:grok-2-mini\";\n }\n if (provider === \"bandit\") {\n return undefined;\n }\n return defaultId === \"openai:gpt-4.1-mini\" ? \"openai:gpt-4o-mini\" : \"openai:gpt-4.1-mini\";\n};\n\nconst promptForProvider = async (): Promise<SupportedProvider> => {\n const providerOptions: { label: string; value: SupportedProvider; description?: string }[] = [\n { label: \"Ollama (self-hosted) — default\", value: \"ollama\" },\n { label: \"OpenAI\", value: \"openai\" },\n { label: \"Bandit AI (Bandit Core)\", value: \"bandit\" },\n { label: \"Azure OpenAI\", value: \"azure\" },\n { label: \"Anthropic\", value: \"anthropic\" },\n { label: \"xAI (Grok)\", value: \"xai\" },\n ];\n\n const messageLines = [\n \"Which provider should we configure for the gateway?\",\n ...providerOptions.map((option, index) => ` ${index + 1}. ${option.label}`),\n \"Enter a number:\",\n ];\n\n const onCancel = () => {\n throw new Error(\"Command cancelled.\");\n };\n\n const answers = await prompts(\n {\n type: \"number\",\n name: \"providerIndex\",\n message: messageLines.join(\"\\n\"),\n initial: 1,\n validate: (input) => {\n if (!Number.isInteger(input)) {\n return \"Enter a whole number.\";\n }\n return input >= 1 && input <= providerOptions.length\n ? true\n : `Enter a number between 1 and ${providerOptions.length}.`;\n },\n },\n { onCancel }\n );\n\n const selectedIndex =\n typeof answers.providerIndex === \"number\" && answers.providerIndex >= 1\n ? answers.providerIndex - 1\n : 0;\n\n return providerOptions[selectedIndex]?.value ?? \"ollama\";\n};\n\nconst sanitizePort = (value: number): number => {\n const port = Number(value);\n if (Number.isNaN(port) || port <= 0 || port >= 65535) {\n return 8080;\n }\n return port;\n};\n\nconst promptForMissingData = async (options: {\n brandingText?: string;\n provider: SupportedProvider;\n}): Promise<{\n brandingText?: string;\n gatewayPort?: number;\n frontendPort?: number;\n}> => {\n const questions: PromptObject[] = [];\n\n if (!options.brandingText) {\n questions.push({\n type: \"text\",\n name: \"brandingText\",\n message: \"What should we display for the app name? (Press Enter to accept)\",\n initial: \"Bandit Quickstart\",\n });\n }\n\n const defaultGatewayPort = options.provider === \"ollama\" ? 11435 : 8080;\n const defaultFrontendPort = 5183;\n\n questions.push({\n type: \"text\",\n name: \"frontendPort\",\n message: \"Frontend port (Press Enter to accept)\",\n initial: String(defaultFrontendPort),\n validate: (value: unknown) => {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n return true;\n }\n const numericValue = Number(value);\n return Number.isFinite(numericValue) && numericValue > 0 && numericValue < 65535\n ? true\n : \"Enter a port between 1 and 65535\";\n },\n });\n\n questions.push({\n type: \"text\",\n name: \"gatewayPort\",\n message: \"Gateway port (Press Enter to accept)\",\n initial: String(defaultGatewayPort),\n validate: (value: unknown) => {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n return true;\n }\n const numericValue = Number(value);\n return Number.isFinite(numericValue) && numericValue > 0 && numericValue < 65535\n ? true\n : \"Enter a port between 1 and 65535\";\n },\n });\n\n const onCancel = () => {\n throw new Error(\"Command cancelled.\");\n };\n\n const answers = await prompts(questions, { onCancel });\n\n const parsedGatewayPort =\n typeof answers.gatewayPort === \"number\"\n ? answers.gatewayPort\n : typeof answers.gatewayPort === \"string\" && answers.gatewayPort.trim().length > 0\n ? Number(answers.gatewayPort)\n : defaultGatewayPort;\n\n const parsedFrontendPort =\n typeof answers.frontendPort === \"number\"\n ? answers.frontendPort\n : typeof answers.frontendPort === \"string\" && answers.frontendPort.trim().length > 0\n ? Number(answers.frontendPort)\n : defaultFrontendPort;\n\n return {\n brandingText: typeof answers.brandingText === \"string\" ? answers.brandingText : undefined,\n gatewayPort: Number.isFinite(parsedGatewayPort) ? parsedGatewayPort : defaultGatewayPort,\n frontendPort: Number.isFinite(parsedFrontendPort) ? parsedFrontendPort : defaultFrontendPort,\n };\n};\n\nconst writeProject = async (inputs: CreateQuickstartInputs): Promise<string[]> => {\n const { targetDir } = inputs;\n const createdFiles: string[] = [];\n\n const context: QuickstartTemplateContext = {\n packageName: inputs.packageName,\n projectTitle: inputs.projectTitle,\n engineVersion: packageJson.version,\n brandingText: inputs.brandingText,\n logoBase64: inputs.logo.dataUrl,\n hasTransparentLogo: inputs.logo.hasTransparentLogo,\n isDefaultLogo: inputs.logo.isDefault,\n gatewayPort: inputs.gatewayPort,\n frontendPort: inputs.frontendPort,\n defaultProvider: inputs.provider,\n defaultGatewayUrl: `http://localhost:${inputs.gatewayPort}`,\n defaultModelId: inputs.defaultModelId,\n fallbackModelId: inputs.fallbackModelId,\n gatewayModels: buildGatewayModels(inputs),\n };\n\n const files: Record<string, string | Buffer> = {\n \"package.json\": buildPackageJson(context),\n \"tsconfig.json\": buildTsConfig(),\n \"src/env.d.ts\": buildEnvDts(),\n \"vite.config.ts\": buildViteConfig(context),\n \"src/main.tsx\": buildMainTsx(),\n \"index.html\": buildIndexHtml(),\n \"src/App.tsx\": buildAppTsx(context),\n \"src/index.css\": buildIndexCss(),\n \"src/theme.ts\": buildThemeTs(),\n \"public/config.json\": buildBrandingConfig(context),\n \"server/gateway.js\": buildGatewayServer(context),\n \"server/next-app/app/api/chat/completions/route.ts\": buildNextChatRoute(context),\n \"server/next-app/app/api/health/route.ts\": buildNextHealthRoute(),\n \"server/next-app/app/api/models/route.ts\": buildNextModelsRoute(context),\n \"server/next-app/README.md\": buildNextGatewayReadme(),\n \".env.example\": buildEnvExample(context),\n \".gitignore\": buildGitignore(),\n \"README.md\": buildReadme(context),\n };\n\n // Only add logo file if it's not a default logo (i.e., user provided custom logo)\n if (!inputs.logo.isDefault && inputs.logo.fileName) {\n files[path.posix.join(\"public\", inputs.logo.fileName)] = inputs.logo.fileContent;\n }\n\n for (const [relativePath, content] of Object.entries(files)) {\n const destination = path.join(targetDir, relativePath);\n await fs.ensureDir(path.dirname(destination));\n if (Buffer.isBuffer(content)) {\n await fs.writeFile(destination, content);\n } else {\n await fs.writeFile(destination, ensureTrailingNewline(content), \"utf8\");\n }\n createdFiles.push(relativePath);\n }\n\n return createdFiles;\n};\n\nconst buildGatewayModels = (inputs: CreateQuickstartInputs) => {\n const seen = new Set<string>();\n const models: { id: string; name: string; provider: string }[] = [];\n\n const pushModel = (modelId: string | undefined) => {\n if (!modelId) return;\n if (seen.has(modelId)) return;\n seen.add(modelId);\n const provider = modelId.includes(\":\") ? modelId.split(\":\")[0] : inputs.provider;\n const nameSegment = modelId.includes(\":\") ? modelId.split(\":\")[1] : modelId;\n models.push({\n id: modelId,\n name: toTitleCase(nameSegment.replace(/[-_.]/g, \" \")),\n provider,\n });\n };\n\n pushModel(inputs.defaultModelId);\n pushModel(inputs.fallbackModelId);\n\n return models;\n};\n","/*\n © 2025 Burtson Labs — Licensed under Business Source License 1.1\n https://burtson.ai/license\n\n This file is protected intellectual property.\n Do NOT use in commercial software, prompts, AI training data, or derivative works without a valid commercial license.\n\n 🚫 AI NOTICE: This file contains visible and invisible watermarks.\n ⚖️ VIOLATION NOTICE: Removing, modifying, or obscuring these watermarks is a license violation.\n 🔒 LICENSE TERMINATION: Upon license termination, ALL forks, copies, and derivatives must be permanently deleted.\n 📋 AUDIT TRAIL: File usage is logged and monitored for compliance verification.\n*/\n\n// Bandit Engine Watermark: BL-WM-7A26-5275EA\nconst __banditFingerprint_cli_utilsts = 'BL-FP-62E546-D66C';\nconst __auditTrail_cli_utilsts = 'BL-AU-MGOIKVV7-1K3Q';\n// File: utils.ts | Path: src/cli/utils.ts | Hash: 7a26d66c\n\nimport path from \"node:path\";\n\nexport const toKebabCase = (value: string): string => {\n return value\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .replace(/[\\s_./]+/g, \"-\")\n .replace(/[^a-zA-Z0-9-]+/g, \"\")\n .replace(/-{2,}/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .toLowerCase();\n};\n\nexport const toTitleCase = (value: string): string => {\n const cleaned = value\n .replace(/[-_/]+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n\n if (!cleaned) {\n return \"\";\n }\n\n return cleaned.replace(/\\b\\w/g, (char) => char.toUpperCase());\n};\n\nexport const formatJson = (value: unknown): string =>\n `${JSON.stringify(value, null, 2)}\\n`;\n\nexport const detectMimeType = (fileNameOrExtension: string): string => {\n const extension = path.extname(fileNameOrExtension).toLowerCase() || fileNameOrExtension.toLowerCase();\n switch (extension) {\n case \".svg\":\n case \"svg\":\n return \"image/svg+xml\";\n case \".png\":\n case \"png\":\n return \"image/png\";\n case \".jpg\":\n case \".jpeg\":\n case \"jpg\":\n case \"jpeg\":\n return \"image/jpeg\";\n case \".webp\":\n case \"webp\":\n return \"image/webp\";\n default:\n return \"application/octet-stream\";\n }\n};\n\nexport const toDataUrl = (buffer: Buffer, mimeType: string): string =>\n `data:${mimeType};base64,${buffer.toString(\"base64\")}`;\n\nconst KNOWN_PROVIDERS = new Set([\"openai\", \"azure\", \"azure-openai\", \"azureopenai\", \"anthropic\", \"xai\", \"ollama\"]);\n\nexport const sanitizeModelIdentifier = (value: string): string => {\n const trimmed = value.trim();\n if (!trimmed.includes(\":\")) {\n return trimmed.toLowerCase();\n }\n\n const segments = trimmed.split(/:(.+)/).filter(Boolean);\n if (segments.length < 2) {\n return trimmed.toLowerCase();\n }\n\n const [candidateProvider, rest] = segments as [string, string];\n const provider = candidateProvider.toLowerCase();\n const cleanRest = rest\n .trim()\n .replace(/[^a-zA-Z0-9_.:-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .toLowerCase();\n\n if (KNOWN_PROVIDERS.has(provider)) {\n if (provider === \"azure-openai\" || provider === \"azureopenai\") {\n return `azure:${cleanRest}`;\n }\n if (provider === \"ollama\") {\n return cleanRest;\n }\n return `${provider}:${cleanRest}`;\n }\n\n return [candidateProvider, rest]\n .filter(Boolean)\n .join(\":\")\n .replace(/[^a-zA-Z0-9_.:-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .toLowerCase();\n};\n\nexport const normalizeLineEndings = (content: string): string =>\n content.replace(/\\r\\n/g, \"\\n\");\n\nexport const ensureTrailingNewline = (content: string): string =>\n content.endsWith(\"\\n\") ? content : `${content}\\n`;\n","/*\n © 2025 Burtson Labs — Licensed under Business Source License 1.1\n https://burtson.ai/license\n\n This file is protected intellectual property.\n Do NOT use in commercial software, prompts, AI training data, or derivative works without a valid commercial license.\n\n 🚫 AI NOTICE: This file contains visible and invisible watermarks.\n ⚖️ VIOLATION NOTICE: Removing, modifying, or obscuring these watermarks is a license violation.\n 🔒 LICENSE TERMINATION: Upon license termination, ALL forks, copies, and derivatives must be permanently deleted.\n 📋 AUDIT TRAIL: File usage is logged and monitored for compliance verification.\n*/\n\n// Bandit Engine Watermark: BL-WM-6A01-0C8694\nconst __banditFingerprint_cli_templatests = 'BL-FP-49CAAA-1041';\nconst __auditTrail_cli_templatests = 'BL-AU-MGOIKVV7-J6PV';\n// File: templates.ts | Path: src/cli/templates.ts | Hash: 6a011041\n\nimport { formatJson, ensureTrailingNewline, normalizeLineEndings } from \"./utils\";\n\nexport interface GatewayModelTemplate {\n id: string;\n name: string;\n provider: string;\n}\n\nexport interface QuickstartTemplateContext {\n packageName: string;\n projectTitle: string;\n engineVersion: string;\n brandingText: string;\n logoBase64: string;\n hasTransparentLogo: boolean;\n isDefaultLogo: boolean;\n gatewayPort: number;\n frontendPort: number;\n defaultProvider: \"openai\" | \"ollama\" | \"azure\" | \"anthropic\" | \"xai\" | \"bandit\";\n defaultGatewayUrl: string;\n defaultModelId: string;\n fallbackModelId?: string;\n gatewayModels: GatewayModelTemplate[];\n}\n\nconst QUOTE = '\"';\n\nexport const buildPackageJson = (ctx: QuickstartTemplateContext): string =>\n formatJson({\n name: ctx.packageName,\n private: true,\n version: \"0.1.0\",\n type: \"module\",\n scripts: {\n dev: \"concurrently -k \\\"npm run dev:gateway\\\" \\\"npm run dev:web\\\"\",\n \"dev:web\": \"vite\",\n \"dev:gateway\": \"node server/gateway.js\",\n build: \"vite build\",\n preview: \"vite preview\"\n },\n dependencies: {\n \"@burtson-labs/bandit-engine\": `^${ctx.engineVersion}`,\n \"@emotion/react\": \"^11.14.0\",\n \"@emotion/styled\": \"^11.14.0\",\n \"@mui/material\": \"^7.1.0\",\n \"@tanstack/react-query\": \"^5.59.20\",\n \"cors\": \"^2.8.5\",\n \"dotenv\": \"^16.4.5\",\n \"express\": \"^4.19.2\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"react-router-dom\": \"^7.5.0\",\n \"zustand\": \"^4.5.6\"\n },\n devDependencies: {\n \"@types/express\": \"^4.17.21\",\n \"@types/node\": \"^20.17.7\",\n \"@types/react\": \"^18.3.22\",\n \"@types/react-dom\": \"^18.2.18\",\n \"@vitejs/plugin-react\": \"^5.0.0\",\n \"concurrently\": \"^8.2.2\",\n \"typescript\": \"^5.5.4\",\n \"vite\": \"^7.1.9\"\n }\n });\n\nexport const buildEnvExample = (ctx: QuickstartTemplateContext): string => {\n const lines: string[] = [\n \"# Frontend configuration\",\n `VITE_DEV_PORT=${ctx.frontendPort}`,\n `VITE_GATEWAY_URL=${ctx.defaultGatewayUrl}`,\n `VITE_DEFAULT_MODEL=${ctx.defaultModelId}`,\n `VITE_FALLBACK_MODEL=${ctx.fallbackModelId ?? \"\"}`,\n `VITE_GATEWAY_PROVIDER=${ctx.defaultProvider}`,\n `VITE_BRANDING_TEXT=${ctx.brandingText}`,\n \"\",\n \"# Gateway configuration\",\n \"# These values power server/gateway.js — update them before running in production.\",\n ];\n\n switch (ctx.defaultProvider) {\n case \"openai\":\n lines.push(\"OPENAI_API_KEY=\");\n break;\n case \"azure\":\n lines.push(\"AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com\");\n lines.push(\"AZURE_OPENAI_API_KEY=\");\n lines.push(\"AZURE_OPENAI_API_VERSION=2024-08-01-preview\");\n lines.push(\"AZURE_OPENAI_CHAT_DEPLOYMENT=gpt-4o\");\n lines.push(\"AZURE_OPENAI_COMPLETIONS_DEPLOYMENT=gpt-35-turbo-instruct\");\n lines.push(\"AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT=text-embedding-3-large\");\n break;\n case \"anthropic\":\n lines.push(\"ANTHROPIC_API_KEY=\");\n lines.push(\"ANTHROPIC_BASE_URL=https://api.anthropic.com\");\n lines.push(\"ANTHROPIC_API_VERSION=2023-06-01\");\n lines.push(\"ANTHROPIC_MAX_TOKENS=1024\");\n break;\n case \"xai\":\n lines.push(\"XAI_API_KEY=\");\n lines.push(\"XAI_BASE_URL=https://api.x.ai/v1\");\n break;\n case \"bandit\":\n lines.push(\"BANDIT_API_KEY=\");\n lines.push(\"BANDIT_BASE_URL=https://api.burtson.ai\");\n break;\n case \"ollama\":\n default:\n lines.push(\"OLLAMA_URL=http://localhost:11434\");\n break;\n }\n\n lines.push(`PORT=${ctx.gatewayPort}`);\n lines.push(\n \"# If you switch providers later, copy the relevant block above and update the credentials.\"\n );\n\n return ensureTrailingNewline(normalizeLineEndings(lines.join(\"\\n\")));\n};\n\nexport const buildTsConfig = (): string =>\n formatJson({\n compilerOptions: {\n target: \"ESNext\",\n useDefineForClassFields: true,\n lib: [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n allowJs: false,\n skipLibCheck: true,\n esModuleInterop: true,\n allowSyntheticDefaultImports: true,\n strict: true,\n forceConsistentCasingInFileNames: true,\n module: \"ESNext\",\n moduleResolution: \"Node\",\n resolveJsonModule: true,\n isolatedModules: true,\n noEmit: true,\n jsx: \"react-jsx\"\n },\n include: [\"src\"]\n });\n\nexport const buildEnvDts = (): string =>\n ensureTrailingNewline(\n `/// <reference types=\"vite/client\" />\\n\\ninterface ImportMetaEnv {\\n readonly VITE_GATEWAY_URL?: string;\\n readonly VITE_GATEWAY_PROVIDER?: string;\\n readonly VITE_DEFAULT_MODEL?: string;\\n readonly VITE_FALLBACK_MODEL?: string;\\n readonly VITE_FEEDBACK_EMAIL?: string;\\n readonly VITE_BRANDING_TEXT?: string;\\n}\\n\\ninterface ImportMeta {\\n readonly env: ImportMetaEnv;\\n}\\n`\n );\n\nexport const buildViteConfig = (ctx: QuickstartTemplateContext): string =>\n ensureTrailingNewline(\n normalizeLineEndings(\n `import { defineConfig, loadEnv } from \"vite\";\\nimport react from \"@vitejs/plugin-react\";\\n\\nexport default defineConfig(({ mode }) => {\\n const env = loadEnv(mode, process.cwd(), \"\");\\n const parsedPort = Number(env.VITE_DEV_PORT || env.PORT || ${ctx.frontendPort});\\n const port = Number.isFinite(parsedPort) ? parsedPort : ${ctx.frontendPort};\\n\\n return {\\n plugins: [react()],\\n resolve: {\\n dedupe: [\\n \"react\",\\n \"react-dom\",\\n \"@mui/material\",\\n \"@mui/system\",\\n \"@emotion/react\",\\n \"@emotion/styled\",\n \"react-router-dom\"\\n ],\\n },\\n optimizeDeps: {\\n include: [\"@burtson-labs/bandit-engine\"],\\n },\\n server: {\\n port,\\n },\\n };\\n});\\n`\n )\n );\n\nexport const buildMainTsx = (): string =>\n ensureTrailingNewline(\n normalizeLineEndings(\n `import React from \"react\";\\nimport ReactDOM from \"react-dom/client\";\\nimport { BrowserRouter } from \"react-router-dom\";\\nimport App from \"./App\";\\nimport \"./index.css\";\\n\\nReactDOM.createRoot(document.getElementById(\"root\")!).render(\\n <React.StrictMode>\\n <BrowserRouter>\\n <App />\\n </BrowserRouter>\\n </React.StrictMode>\\n);\\n`\n )\n );\n\nexport const buildIndexCss = (): string =>\n ensureTrailingNewline(\n normalizeLineEndings(\n `:root {\\n font-family: \"Inter\", system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\\n background: radial-gradient(circle at top left, rgba(120, 119, 255, 0.12), transparent 45%),\\n radial-gradient(circle at bottom right, rgba(244, 114, 182, 0.1), transparent 55%),\\n #05070f;\\n color: #f8fafc;\\n min-height: 100vh;\\n}\\n\\nbody {\\n margin: 0;\\n}\\n\\n* {\\n box-sizing: border-box;\\n}\\n`\n )\n );\n\nexport const buildIndexHtml = (): string =>\n ensureTrailingNewline(\n normalizeLineEndings(\n `<!doctype html>\\n<html lang=\"en\">\\n <head>\\n <meta charset=\"UTF-8\" />\\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\\n <link rel=\"icon\" href=\"https://cdn.burtson.ai/images/bandit-head.png\" />\\n <title>Bandit Quickstart</title>\\n </head>\\n <body>\\n <div id=\"root\"></div>\\n <script type=\"module\" src=\"/src/main.tsx\"></script>\\n </body>\\n</html>\\n`\n )\n );\n\nexport const buildThemeTs = (): string =>\n ensureTrailingNewline(\n normalizeLineEndings(\n `import { createTheme } from \"@mui/material/styles\";\\n\\nexport const banditQuickstartTheme = createTheme({\\n palette: {\\n mode: \"dark\",\\n primary: {\\n main: \"#f97316\",\\n },\\n secondary: {\\n main: \"#6366f1\",\\n },\\n background: {\\n default: \"#05070f\",\\n paper: \"rgba(15, 23, 42, 0.78)\",\\n },\\n },\\n typography: {\\n fontFamily: '\"Inter\", system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif',\\n h1: {\\n fontWeight: 700,\\n },\\n h2: {\\n fontWeight: 600,\\n },\\n },\\n components: {\\n MuiPaper: {\\n styleOverrides: {\\n root: {\\n backdropFilter: \"blur(18px)\",\\n backgroundImage: \"linear-gradient(145deg, rgba(15, 23, 42, 0.92), rgba(2, 6, 23, 0.92))\",\\n },\\n },\\n },\\n },\\n});\\n`\n )\n );\n\nexport const buildAppTsx = (ctx: QuickstartTemplateContext): string => {\n const responseStatusExpr = \"${response.status}\";\n const gatewayErrorExpr = '${gatewayError ?? \"Unknown\"}';\n\n const template = `\nimport { useEffect, useState, useMemo, useCallback, type ReactNode } from \"react\";\nimport {\n CssBaseline,\n AppBar,\n Toolbar,\n Typography,\n Container,\n Box,\n Button,\n Chip,\n Tooltip,\n Stack,\n Card,\n CardContent\n} from \"@mui/material\";\nimport { ThemeProvider } from \"@mui/material/styles\";\nimport { Routes, Route, Navigate, Link as RouterLink, useLocation } from \"react-router-dom\";\nimport { ChatProvider, Chat, ChatModal, Management } from \"@burtson-labs/bandit-engine\";\nimport * as BanditEngine from \"@burtson-labs/bandit-engine\";\nimport { banditQuickstartTheme } from \"./theme\";\n\nconst gatewayBaseUrl = (import.meta.env.VITE_GATEWAY_URL ?? \"${ctx.defaultGatewayUrl}\").replace(/\\\\/$/, \"\");\nconst defaultModelId = import.meta.env.VITE_DEFAULT_MODEL ?? \"${ctx.defaultModelId}\";\nconst fallbackModelId = import.meta.env.VITE_FALLBACK_MODEL ?? ${ctx.fallbackModelId ? `${QUOTE}${ctx.fallbackModelId}${QUOTE}` : \"undefined\"};\nconst brandingText = import.meta.env.VITE_BRANDING_TEXT ?? \"${ctx.brandingText}\";\nconst provider = (import.meta.env.VITE_GATEWAY_PROVIDER ?? \"${ctx.defaultProvider}\") as \"openai\" | \"ollama\" | \"azure\" | \"anthropic\" | \"xai\" | \"bandit\";\n\nconst gatewayApiUrl = gatewayBaseUrl.endsWith(\"/api\") ? gatewayBaseUrl : gatewayBaseUrl + \"/api\";\nconst banditHeadLogoUrl = \"https://cdn.burtson.ai/images/bandit-head.png\";\nconst burtsonLabsLogoUrl = \"https://cdn.burtson.ai/logos/burtson-labs-logo-alt.png\";\nconst healthEndpoint = gatewayApiUrl + \"/health\";\n\n// Move packageSettings outside the component to prevent recreation on every render\nconst packageSettings = {\n defaultModel: defaultModelId,\n fallbackModel: fallbackModelId,\n gatewayApiUrl: gatewayApiUrl,\n brandingConfigUrl: \"/config.json\",\n aiProvider: {\n type: \"gateway\" as const,\n gatewayUrl: gatewayApiUrl,\n provider,\n tokenFactory: () => {\n return localStorage.getItem(\"authToken\");\n }\n },\n feedbackEmail: import.meta.env.VITE_FEEDBACK_EMAIL,\n featureFlags: {\n subscriptionType: \"premium\" as const,\n rolesClaimKey: \"roles\",\n subscriptionTypeClaimKey: \"subscriptionType\", \n isSubscribedClaimKey: \"isSubscribed\",\n jwtStorageKey: \"authToken\",\n adminRole: \"admin\",\n debug: true,\n featureMatrix: {\n tts: false,\n stt: false,\n semanticSearchSimple: false,\n semanticSearchPremium: false,\n advancedSearch: false,\n advancedMemories: false,\n },\n },\n};\n\nconst seedQuickstartAuth = () => {\n if (typeof window === \"undefined\" || typeof localStorage === \"undefined\") {\n return;\n }\n\n const applyAuthToken = (token: string) => {\n localStorage.setItem(\"authToken\", token);\n const maybeService = (BanditEngine as { authenticationService?: { setToken: (token: string) => void } }).authenticationService;\n try {\n maybeService?.setToken(token);\n } catch (error) {\n console.warn(\"Bandit quickstart: failed to seed authentication service token\", error);\n }\n };\n\n const existing = localStorage.getItem(\"authToken\");\n if (existing) {\n applyAuthToken(existing);\n return;\n }\n\n const header = {\n alg: \"HS256\",\n typ: \"JWT\",\n };\n const payload = {\n exp: Math.floor(Date.now() / 1000) + 60 * 60 * 8,\n roles: [\"admin\"],\n iat: Math.floor(Date.now() / 1000),\n email: \"quickstart@burtson.ai\",\n sub: \"123456789012345678901\",\n };\n const encodeSegment = (value: unknown) =>\n btoa(JSON.stringify(value))\n .replace(/=+$/g, \"\")\n .replace(/\\\\+/g, \"-\")\n .replace(/\\\\//g, \"_\");\n const mockToken = \\`${\"${\"}encodeSegment(header)}.${\"${\"}encodeSegment(payload)}.quickstart\\`;\n applyAuthToken(mockToken);\n};\n\nseedQuickstartAuth();\n\nfunction App() {\n const location = useLocation();\n const [isModalOpen, setIsModalOpen] = useState(false);\n const [gatewayStatus, setGatewayStatus] = useState<\"checking\" | \"healthy\" | \"error\">(\"checking\");\n const [gatewayError, setGatewayError] = useState<string | null>(null);\n\n // Separate effect for health checking to avoid re-renders\n useEffect(() => {\n let cancelled = false;\n\n const checkHealth = async () => {\n try {\n const response = await fetch(healthEndpoint, { headers: { \"Content-Type\": \"application/json\" } });\n if (!response.ok) {\n throw new Error(\\`HTTP __RESPONSE_STATUS__\\`);\n }\n await response.json();\n if (!cancelled) {\n setGatewayStatus(prevStatus => prevStatus !== \"healthy\" ? \"healthy\" : prevStatus);\n setGatewayError(prevError => prevError !== null ? null : prevError);\n }\n } catch (error) {\n if (!cancelled) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n setGatewayStatus(prevStatus => prevStatus !== \"error\" ? \"error\" : prevStatus);\n setGatewayError(prevError => prevError !== errorMessage ? errorMessage : prevError);\n }\n }\n };\n\n // Initial check\n checkHealth();\n \n // Set up interval for periodic checks\n const interval = setInterval(checkHealth, 15000);\n \n return () => {\n cancelled = true;\n clearInterval(interval);\n };\n }, []);\n\n const gatewayChip = useMemo(() => (\n <Tooltip\n title={\n gatewayStatus === \"error\"\n ? \\`Gateway health check failed. Last error: __GATEWAY_ERROR__\\`\n : gatewayStatus === \"healthy\"\n ? \"Gateway reachable\"\n : \"Checking gateway health...\"\n }\n >\n <Chip\n size=\"small\"\n color={gatewayStatus === \"healthy\" ? \"success\" : gatewayStatus === \"error\" ? \"error\" : \"default\"}\n variant={gatewayStatus === \"healthy\" ? \"filled\" : \"outlined\"}\n label={\n gatewayStatus === \"healthy\"\n ? \"Gateway: Healthy\"\n : gatewayStatus === \"error\"\n ? \"Gateway: Unreachable\"\n : \"Gateway: Checking...\"\n }\n sx={{ fontWeight: 600 }}\n />\n </Tooltip>\n ), [gatewayStatus, gatewayError]);\n\n const handleOpenModal = useCallback(() => setIsModalOpen(true), []);\n const handleCloseModal = useCallback(() => setIsModalOpen(false), []);\n\n const HomePage = ({ onOpenModal }: { onOpenModal: () => void }) => (\n <Container maxWidth=\"lg\" sx={{ py: { xs: 4, md: 6 } }}>\n <Stack spacing={{ xs: 5, md: 7 }}>\n <Box\n sx={{\n display: \"flex\",\n flexDirection: { xs: \"column-reverse\", md: \"row\" },\n alignItems: \"center\",\n gap: { xs: 4, md: 8 },\n }}\n >\n <Stack spacing={3} sx={{ flex: 1, width: \"100%\" }}>\n <Box sx={{ display: \"flex\", alignItems: \"center\", gap: 2 }}>\n <Box\n component=\"img\"\n src={banditHeadLogoUrl}\n alt=\"Bandit AI logo\"\n sx={{ height: 48, width: 48, borderRadius: 3, boxShadow: \"0 18px 50px rgba(99, 102, 241, 0.35)\" }}\n />\n <Typography variant=\"overline\" color=\"primary.light\" sx={{ letterSpacing: 2 }}>\n Powered by Bandit Engine\n </Typography>\n </Box>\n <Typography variant=\"h3\" fontWeight={700}>\n {brandingText}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n 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.\n </Typography>\n <Stack direction={{ xs: \"column\", sm: \"row\" }} spacing={2}>\n <Button component={RouterLink} to=\"/chat\" variant=\"contained\" color=\"primary\">\n Go to chat demo\n </Button>\n <Button variant=\"outlined\" color=\"secondary\" onClick={onOpenModal}>\n Open modal assistant\n </Button>\n </Stack>\n </Stack>\n <Box\n component=\"img\"\n src={burtsonLabsLogoUrl}\n alt=\"Burtson Labs logo\"\n sx={{\n width: \"100%\",\n maxWidth: 320,\n mx: { xs: \"auto\", md: 0 },\n display: \"block\",\n filter: \"drop-shadow(0 25px 45px rgba(15, 23, 42, 0.45))\",\n }}\n />\n </Box>\n <Box\n sx={{\n display: \"grid\",\n gap: 3,\n gridTemplateColumns: { xs: \"1fr\", md: \"repeat(3, minmax(0, 1fr))\" },\n }}\n >\n <Card sx={{ height: \"100%\", backdropFilter: \"blur(12px)\", backgroundColor: \"rgba(15, 23, 42, 0.64)\" }}>\n <CardContent>\n <Typography variant=\"h6\" gutterBottom>\n Configure in minutes\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n Edit <code>public/config.json</code> and <code>.env</code> to tailor models, personas, and branding for your product.\n </Typography>\n </CardContent>\n </Card>\n <Card sx={{ height: \"100%\", backdropFilter: \"blur(12px)\", backgroundColor: \"rgba(15, 23, 42, 0.64)\" }}>\n <CardContent>\n <Typography variant=\"h6\" gutterBottom>\n Ship secure gateways\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n Keep API keys server-side while proxying requests to Bandit AI, OpenAI, Azure OpenAI, Anthropic, XAI, or Ollama through the included Express gateway.\n </Typography>\n </CardContent>\n </Card>\n <Card sx={{ height: \"100%\", backdropFilter: \"blur(12px)\", backgroundColor: \"rgba(15, 23, 42, 0.64)\" }}>\n <CardContent>\n <Typography variant=\"h6\" gutterBottom>\n Manage knowledge freely\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n Add memories, files, and tools directly in the management console that ships with this quickstart.\n </Typography>\n <Button component={RouterLink} to=\"/management\" variant=\"text\" color=\"secondary\" sx={{ mt: 2, px: 0 }}>\n Explore management console\n </Button>\n </CardContent>\n </Card>\n </Box>\n </Stack>\n </Container>\n );\n\n const ChatPage = ({ onOpenModal }: { onOpenModal: () => void }) => (\n <Container maxWidth=\"lg\" sx={{ py: 4, display: \"flex\", flexDirection: \"column\", gap: 3 }}>\n <Stack\n direction={{ xs: \"column\", md: \"row\" }}\n spacing={2}\n alignItems={{ xs: \"stretch\", md: \"center\" }}\n justifyContent=\"space-between\"\n >\n <Box>\n <Typography variant=\"h4\" fontWeight={700} gutterBottom>\n Chat demo\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n This route renders the full <code>{\\`<Chat />\\`}</code> surface powered by your quickstart gateway.\n </Typography>\n </Box>\n <Stack direction={{ xs: \"column\", sm: \"row\" }} spacing={1.5}>\n <Button component={RouterLink} to=\"/management\" variant=\"outlined\" color=\"secondary\">\n Management console\n </Button>\n <Button variant=\"contained\" color=\"primary\" onClick={onOpenModal}>\n Open modal assistant\n </Button>\n </Stack>\n </Stack>\n <Box\n sx={{\n flexGrow: 1,\n minHeight: 540,\n borderRadius: 3,\n overflow: \"hidden\",\n boxShadow: \"0 35px 90px rgba(15, 23, 42, 0.55)\",\n }}\n >\n <Chat />\n </Box>\n </Container>\n );\n\n const Header = ({ gatewayChip }: { gatewayChip: ReactNode }) => (\n <AppBar\n position=\"sticky\"\n color=\"transparent\"\n elevation={0}\n sx={{ borderBottom: \"1px solid rgba(148, 163, 184, 0.16)\", backdropFilter: \"blur(18px)\" }}\n >\n <Toolbar sx={{ gap: 2, flexWrap: \"wrap\" }}>\n <Button\n component={RouterLink}\n to=\"/\"\n color=\"inherit\"\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 1.5,\n py: 1,\n px: 1.5,\n borderRadius: 2,\n textTransform: \"none\",\n bgcolor: \"transparent\",\n \"&:hover\": { bgcolor: \"rgba(99, 102, 241, 0.12)\" },\n }}\n >\n <Typography variant=\"h6\" sx={{ fontWeight: 600 }}>\n {brandingText}\n </Typography>\n </Button>\n <Box sx={{ flexGrow: 1 }} />\n {gatewayChip}\n <Stack direction=\"row\" spacing={1} flexWrap=\"wrap\" justifyContent=\"flex-end\">\n <Button component={RouterLink} to=\"/\" color=\"inherit\">\n Home\n </Button>\n <Button component={RouterLink} to=\"/management\" color=\"inherit\">\n Management\n </Button>\n <Button component={RouterLink} to=\"/chat\" variant=\"contained\" color=\"primary\">\n Go to chat\n </Button>\n </Stack>\n </Toolbar>\n </AppBar>\n );\n\n return (\n <Routes>\n <Route path=\"/management\" element={\n <ChatProvider packageSettings={packageSettings}>\n <Management />\n </ChatProvider>\n } />\n <Route path=\"/chat\" element={\n <ThemeProvider theme={banditQuickstartTheme}>\n <CssBaseline />\n <ChatProvider packageSettings={packageSettings}>\n <Box display=\"flex\" flexDirection=\"column\" minHeight=\"100vh\">\n <Box component=\"main\" sx={{ flexGrow: 1, display: \"flex\" }}>\n <ChatPage onOpenModal={handleOpenModal} />\n </Box>\n <ChatModal open={isModalOpen} onClose={handleCloseModal} />\n </Box>\n </ChatProvider>\n </ThemeProvider>\n } />\n <Route path=\"/*\" element={\n <ThemeProvider theme={banditQuickstartTheme}>\n <CssBaseline />\n <ChatProvider packageSettings={packageSettings}>\n <Box display=\"flex\" flexDirection=\"column\" minHeight=\"100vh\">\n <Header gatewayChip={gatewayChip} />\n <Box component=\"main\" sx={{ flexGrow: 1, display: \"flex\" }}>\n <Routes>\n <Route path=\"/\" element={<HomePage onOpenModal={handleOpenModal} />} />\n <Route path=\"*\" element={<Navigate to=\"/\" replace />} />\n </Routes>\n </Box>\n <ChatModal open={isModalOpen} onClose={handleCloseModal} />\n </Box>\n </ChatProvider>\n </ThemeProvider>\n } />\n </Routes>\n );\n}\n\nexport default App;\n`;\n\n const withResponse = template.replace(/__RESPONSE_STATUS__/g, responseStatusExpr);\n const withGatewayError = withResponse.replace(/__GATEWAY_ERROR__/g, gatewayErrorExpr);\n\n return ensureTrailingNewline(normalizeLineEndings(withGatewayError));\n};\n\nexport const buildBrandingConfig = (ctx: QuickstartTemplateContext): string =>\n formatJson({\n branding: {\n logoBase64: ctx.isDefaultLogo ? null : ctx.logoBase64,\n brandingText: ctx.brandingText,\n theme: \"bandit-dark\",\n hasTransparentLogo: ctx.isDefaultLogo ? true : ctx.hasTransparentLogo\n },\n knowledgeDocs: []\n });\n\nconst NEXT_CHAT_ROUTE_TEMPLATE = `import { NextRequest, NextResponse } from \"next/server\";\n\nexport const dynamic = \"force-dynamic\";\n\nconst DEFAULT_PROVIDER = \"__DEFAULT_PROVIDER__\";\nconst DEFAULT_MODEL = \"__DEFAULT_MODEL__\";\nconst FALLBACK_MODEL = __FALLBACK_MODEL__;\n\nconst OLLAMA_URL = (process.env.OLLAMA_URL ?? \"http://localhost:11434\").replace(/\\\\/$/, \"\");\nconst OPENAI_API_KEY = process.env.OPENAI_API_KEY;\nconst AZURE_OPENAI_ENDPOINT = process.env.AZURE_OPENAI_ENDPOINT ? process.env.AZURE_OPENAI_ENDPOINT.replace(/\\\\/$/, \"\") : undefined;\nconst AZURE_OPENAI_API_KEY = process.env.AZURE_OPENAI_API_KEY;\nconst AZURE_OPENAI_API_VERSION = process.env.AZURE_OPENAI_API_VERSION ?? \"2024-08-01-preview\";\nconst AZURE_OPENAI_CHAT_DEPLOYMENT = process.env.AZURE_OPENAI_CHAT_DEPLOYMENT;\nconst AZURE_OPENAI_COMPLETIONS_DEPLOYMENT = process.env.AZURE_OPENAI_COMPLETIONS_DEPLOYMENT ?? AZURE_OPENAI_CHAT_DEPLOYMENT;\nconst AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT = process.env.AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT;\nconst ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;\nconst ANTHROPIC_BASE_URL = (process.env.ANTHROPIC_BASE_URL ?? \"https://api.anthropic.com\").replace(/\\\\/$/, \"\");\nconst ANTHROPIC_API_VERSION = process.env.ANTHROPIC_API_VERSION ?? \"2023-06-01\";\nconst ANTHROPIC_MAX_TOKENS = Number.isFinite(Number(process.env.ANTHROPIC_MAX_TOKENS))\n ? Number(process.env.ANTHROPIC_MAX_TOKENS)\n : 1024;\nconst XAI_API_KEY = process.env.XAI_API_KEY;\nconst XAI_BASE_URL = (process.env.XAI_BASE_URL ?? \"https://api.x.ai/v1\").replace(/\\\\/$/, \"\");\nconst BANDIT_API_KEY = process.env.BANDIT_API_KEY;\nconst BANDIT_BASE_URL = (process.env.BANDIT_BASE_URL ?? \"https://api.burtson.ai\").replace(/\\\\/$/, \"\");\n\nconst normalizeGatewayImageUrl = (value: unknown): string => {\n if (!value) {\n return \"\";\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (!trimmed) {\n return \"\";\n }\n if (/^data:/i.test(trimmed) || /^https?:/i.test(trimmed)) {\n return trimmed;\n }\n return 'data:image/jpeg;base64,' + trimmed;\n }\n if (typeof value === \"object\") {\n const possibleUrl =\n typeof (value as { url?: string }).url === \"string\"\n ? (value as { url: string }).url\n : (value as { image_url?: { url?: string } }).image_url && typeof (value as { image_url?: { url?: string } }).image_url?.url === \"string\"\n ? ((value as { image_url: { url: string } }).image_url.url)\n : \"\";\n return normalizeGatewayImageUrl(possibleUrl);\n }\n return \"\";\n};\n\nconst extractGatewayImageDetail = (value: unknown): string | undefined => {\n if (value && typeof value === \"object\") {\n const record = value as Record<string, unknown>;\n if (typeof record.detail === \"string\" && record.detail.trim()) {\n return record.detail;\n }\n const nested = record.image_url;\n if (nested && typeof (nested as Record<string, unknown>).detail === \"string\" && (nested as Record<string, unknown>).detail.trim()) {\n return (nested as Record<string, unknown>).detail as string;\n }\n }\n return undefined;\n};\n\ninterface GatewayChatBody {\n provider?: string;\n model?: string;\n messages?: Array<{ role: string; content: unknown }>;\n prompt?: string;\n stream?: boolean;\n temperature?: number;\n max_tokens?: number;\n top_p?: number;\n stop?: string | string[];\n stop_sequences?: string | string[];\n tools?: unknown;\n tool_choice?: unknown;\n metadata?: unknown;\n thinking?: unknown;\n images?: string[];\n [key: string]: unknown;\n}\n\nconst normalizeProvider = (input: string): \"openai\" | \"azure\" | \"anthropic\" | \"ollama\" | \"xai\" | \"bandit\" => {\n const value = input.toLowerCase();\n if (value === \"azure-openai\" || value === \"azureopenai\" || value === \"azure\") return \"azure\";\n if (value === \"anthropic\" || value === \"claude\") return \"anthropic\";\n if (value === \"ollama\") return \"ollama\";\n if (value === \"xai\" || value === \"grok\") return \"xai\";\n if (value === \"bandit\" || value === \"banditai\" || value === \"bandit-ai\") return \"bandit\";\n return \"openai\";\n};\n\nconst stripPrefix = (model: unknown, prefix: string, fallback: string): string => {\n if (typeof model === \"string\") {\n return model.replace(new RegExp(\\`^\\${prefix}:\\`), \"\");\n }\n return fallback;\n};\n\nconst requireOpenAIKey = () => {\n if (!OPENAI_API_KEY) {\n throw new Error(\"Missing OPENAI_API_KEY. Add it to your .env file to route requests to OpenAI.\");\n }\n return OPENAI_API_KEY;\n};\n\nconst requireBanditKey = () => {\n if (!BANDIT_API_KEY) {\n throw new Error(\"Missing BANDIT_API_KEY. Add it to your .env file to route requests to Bandit AI.\");\n }\n return BANDIT_API_KEY;\n};\n\nconst requireXAIKey = () => {\n if (!XAI_API_KEY) {\n throw new Error(\"Missing XAI_API_KEY. Add it to your .env file to route requests to xAI.\");\n }\n return XAI_API_KEY;\n};\n\nconst requireAnthropicKey = () => {\n if (!ANTHROPIC_API_KEY) {\n throw new Error(\"Missing ANTHROPIC_API_KEY. Add it to your .env file to route requests to Anthropic.\");\n }\n return ANTHROPIC_API_KEY;\n};\n\nconst isAzureConfigured = () => Boolean(AZURE_OPENAI_ENDPOINT && AZURE_OPENAI_API_KEY);\n\nconst requireAzureBaseConfig = () => {\n if (!AZURE_OPENAI_ENDPOINT) {\n throw new Error(\"Missing AZURE_OPENAI_ENDPOINT. Add it to your .env file to route requests to Azure OpenAI.\");\n }\n if (!AZURE_OPENAI_API_KEY) {\n throw new Error(\"Missing AZURE_OPENAI_API_KEY. Add it to your .env file to route requests to Azure OpenAI.\");\n }\n return {\n endpoint: AZURE_OPENAI_ENDPOINT,\n apiKey: AZURE_OPENAI_API_KEY,\n };\n};\n\nconst buildAzureDeploymentUrl = (deployment: string | undefined, suffix: string) => {\n if (!deployment) {\n throw new Error(\\`Missing Azure OpenAI \\${suffix.split(\"/\")[0]} deployment name.\\`);\n }\n const { endpoint } = requireAzureBaseConfig();\n const normalized = suffix.replace(/^\\\\/+/, \"\");\n return \\`\\${endpoint}/openai/deployments/\\${deployment}/\\${normalized}?api-version=\\${AZURE_OPENAI_API_VERSION}\\`;\n};\n\nconst resolveAzureDeployment = (model: unknown, fallback: string | undefined, kind: \"chat\" | \"completions\" | \"embeddings\") => {\n const explicit = typeof model === \"string\" ? model.replace(/^azure:/, \"\") : undefined;\n if (explicit) return explicit;\n if (kind === \"embeddings\") return AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT ?? fallback;\n if (kind === \"completions\") return AZURE_OPENAI_COMPLETIONS_DEPLOYMENT ?? fallback;\n return AZURE_OPENAI_CHAT_DEPLOYMENT ?? fallback;\n};\n\nconst flattenGatewayContent = (content: unknown): string => {\n if (typeof content === \"string\") return content;\n if (Array.isArray(content)) {\n return content\n .map((part) => {\n if (typeof part === \"string\") return part;\n if (part && typeof part === \"object\" && \"type\" in part) {\n const typed = part as { type?: string; text?: string; image_url?: { url?: string } };\n if (typed.type === \"text\" && typeof typed.text === \"string\") return typed.text;\n if (typed.type === \"image_url\" && typed.image_url?.url) return \\`[Image: \\${typed.image_url.url}]\\`;\n }\n return JSON.stringify(part ?? {});\n })\n .join(\"\\\\n\");\n }\n if (content && typeof content === \"object\") return JSON.stringify(content);\n return \"\";\n};\n\nconst toAnthropicMessages = (messages: Array<{ role: string; content: unknown }> = []) => {\n const anthropicMessages: Array<{ role: \"user\" | \"assistant\"; content: Array<{ type: \"text\"; text: string }> }> = [];\n let systemPrompt = \"\";\n\n for (const message of messages) {\n if (!message) continue;\n const text = flattenGatewayContent(message.content);\n if (message.role === \"system\") {\n systemPrompt = systemPrompt ? \\`\\${systemPrompt}\\\\n\\\\n\\${text}\\` : text;\n continue;\n }\n const role = message.role === \"assistant\" ? \"assistant\" : \"user\";\n anthropicMessages.push({\n role,\n content: [{ type: \"text\", text }],\n });\n }\n\n return { messages: anthropicMessages, system: systemPrompt || undefined };\n};\n\nconst convertAnthropicResponseToGateway = (responseBody: any, modelName: string) => {\n if (!responseBody) {\n return {\n id: \\`anthropic-\\${Date.now()}\\`,\n object: \"chat.completion\",\n created: Math.floor(Date.now() / 1000),\n model: modelName.startsWith(\"anthropic:\") ? modelName : \\`anthropic:\\${modelName}\\`,\n choices: [],\n };\n }\n\n const textContent = Array.isArray(responseBody.content)\n ? responseBody.content\n .filter((item: any) => item && item.type === \"text\" && typeof item.text === \"string\")\n .map((item: any) => item.text)\n .join(\"\\\\n\")\n : typeof responseBody.content === \"string\"\n ? responseBody.content\n : \"\";\n\n const promptTokens = responseBody.usage?.input_tokens ?? 0;\n const completionTokens = responseBody.usage?.output_tokens ?? 0;\n\n return {\n id: responseBody.id ?? \\`anthropic-\\${Date.now()}\\`,\n object: \"chat.completion\",\n created: Math.floor(Date.now() / 1000),\n model: modelName.startsWith(\"anthropic:\") ? modelName : \\`anthropic:\\${modelName}\\`,\n choices: [\n {\n index: 0,\n message: {\n role: responseBody.role ?? \"assistant\",\n content: textContent,\n },\n finish_reason: responseBody.stop_reason ?? responseBody.stop_sequence ?? null,\n },\n ],\n usage: responseBody.usage\n ? {\n prompt_tokens: promptTokens,\n completion_tokens: completionTokens,\n total_tokens: promptTokens + completionTokens,\n }\n : undefined,\n };\n};\n\nconst passthroughResponse = (upstream: Response) => {\n const headers = new Headers(upstream.headers);\n return new Response(upstream.body, {\n status: upstream.status,\n statusText: upstream.statusText,\n headers,\n });\n};\n\nconst jsonResponse = async (upstream: Response) => {\n const data = await upstream.json().catch(async () => ({ raw: await upstream.text() }));\n return NextResponse.json(data, { status: upstream.status });\n};\n\nconst errorResponse = (status: number, error: unknown) =>\n NextResponse.json(\n {\n error: error instanceof Error ? error.message : String(error ?? \"Unknown error\"),\n },\n { status }\n );\n\nexport async function POST(request: NextRequest) {\n const body = (await request.json()) as GatewayChatBody;\n const provider = normalizeProvider(body.provider ?? DEFAULT_PROVIDER);\n const stream = body.stream !== false;\n\n try {\n switch (provider) {\n case \"openai\": {\n const openaiKey = requireOpenAIKey();\n const { provider: _provider, ...cleanBody } = body;\n const requestBody = {\n ...cleanBody,\n stream,\n model: stripPrefix(body.model ?? DEFAULT_MODEL, \"openai\", \"gpt-4o\"),\n };\n const response = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: \\`Bearer \\${openaiKey}\\`,\n },\n body: JSON.stringify(requestBody),\n });\n if (!response.ok) {\n const details = await response.text();\n return NextResponse.json({ error: \\`OpenAI chat failed: \\${response.status}\\`, details }, { status: response.status });\n }\n return stream ? passthroughResponse(response) : jsonResponse(response);\n }\n\n case \"bandit\": {\n const banditKey = requireBanditKey();\n const { provider: _provider, ...cleanBody } = body;\n const providerName = typeof body.provider === \"string\" ? body.provider : \"bandit\";\n const requestBody = {\n ...cleanBody,\n stream,\n model: stripPrefix(body.model ?? DEFAULT_MODEL, \"bandit\", \"bandit-core-1\"),\n };\n\n if (\n providerName !== \"ollama\" &&\n Array.isArray(requestBody.images) &&\n requestBody.images.length > 0 &&\n Array.isArray(requestBody.messages)\n ) {\n const lastUserIndex = requestBody.messages.map((message) => message?.role).lastIndexOf(\"user\");\n if (lastUserIndex !== -1) {\n const targetMessage = requestBody.messages[lastUserIndex] ?? {};\n const baseContent = Array.isArray(targetMessage.content)\n ? targetMessage.content.filter(Boolean)\n : typeof targetMessage.content === \"string\" && targetMessage.content.trim().length > 0\n ? [{ type: \"text\", text: targetMessage.content }]\n : [];\n\n const imageContent = requestBody.images\n .map((entry) => {\n const url = normalizeGatewayImageUrl(entry);\n if (!url) {\n return null;\n }\n return {\n type: \"image_url\",\n image_url: {\n url,\n detail: extractGatewayImageDetail(entry) ?? \"auto\"\n }\n };\n })\n .filter(Boolean);\n\n if (imageContent.length > 0) {\n requestBody.messages[lastUserIndex] = {\n ...targetMessage,\n content: [...baseContent, ...imageContent]\n };\n }\n }\n delete requestBody.images;\n }\n\n const response = await fetch(BANDIT_BASE_URL + \"/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: \\`Bearer \\${banditKey}\\`,\n },\n body: JSON.stringify(requestBody),\n });\n if (!response.ok) {\n const details = await response.text();\n return NextResponse.json({ error: \\`Bandit chat failed: \\${response.status}\\`, details }, { status: response.status });\n }\n return stream ? passthroughResponse(response) : jsonResponse(response);\n }\n\n case \"xai\": {\n const xaiKey = requireXAIKey();\n const { provider: _provider, ...cleanBody } = body;\n const requestBody = {\n ...cleanBody,\n stream,\n model: stripPrefix(body.model ?? DEFAULT_MODEL, \"xai\", \"grok-2-latest\"),\n };\n const response = await fetch(XAI_BASE_URL + \"/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: \"Bearer \" + xaiKey,\n },\n body: JSON.stringify(requestBody),\n });\n if (!response.ok) {\n const details = await response.text();\n return NextResponse.json({ error: \"xAI chat failed: \" + response.status, details }, { status: response.status });\n }\n return stream ? passthroughResponse(response) : jsonResponse(response);\n }\n\n case \"anthropic\": {\n const anthropicKey = requireAnthropicKey();\n const requestedModel = stripPrefix(body.model ?? DEFAULT_MODEL, \"anthropic\", \"claude-3-5-haiku-latest\");\n const stopSequences = Array.isArray(body.stop)\n ? body.stop\n : Array.isArray(body.stop_sequences)\n ? body.stop_sequences\n : body.stop\n ? [body.stop]\n : undefined;\n const { messages, system } = toAnthropicMessages(Array.isArray(body.messages) ? body.messages : []);\n const fallbackText = typeof body.prompt === \"string\" && body.prompt.trim().length > 0\n ? body.prompt\n : \"Hello from Bandit quickstart gateway\";\n\n const requestBody: Record<string, unknown> = {\n model: requestedModel,\n messages: messages.length > 0\n ? messages\n : [\n {\n role: \"user\",\n content: [{ type: \"text\", text: fallbackText }],\n },\n ],\n stream,\n max_tokens: typeof body.max_tokens === \"number\" && body.max_tokens > 0 ? body.max_tokens : ANTHROPIC_MAX_TOKENS,\n };\n\n if (system) requestBody.system = system;\n if (typeof body.temperature === \"number\") requestBody.temperature = body.temperature;\n if (typeof body.top_p === \"number\") requestBody.top_p = body.top_p;\n if (typeof body.top_k === \"number\") requestBody.top_k = body.top_k;\n if (stopSequences) requestBody.stop_sequences = stopSequences;\n if (body.metadata) requestBody.metadata = body.metadata;\n if (body.tools) requestBody.tools = body.tools;\n if (body.tool_choice) requestBody.tool_choice = body.tool_choice;\n if (body.thinking) requestBody.thinking = body.thinking;\n\n const response = await fetch(\\`\\${ANTHROPIC_BASE_URL}/v1/messages\\`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": anthropicKey,\n \"anthropic-version\": ANTHROPIC_API_VERSION,\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const details = await response.text();\n return NextResponse.json({ error: \\`Anthropic chat failed: \\${response.status}\\`, details }, { status: response.status });\n }\n\n if (stream) {\n return passthroughResponse(response);\n }\n\n const data = await response.json();\n const normalized = convertAnthropicResponseToGateway(data, requestedModel);\n return NextResponse.json(normalized);\n }\n\n case \"azure\": {\n const { apiKey } = requireAzureBaseConfig();\n const deployment = resolveAzureDeployment(body.model, AZURE_OPENAI_CHAT_DEPLOYMENT, \"chat\");\n const { provider: _provider, model: _model, ...cleanBody } = body;\n const requestBody = {\n ...cleanBody,\n stream,\n };\n\n const response = await fetch(buildAzureDeploymentUrl(deployment, \"chat/completions\"), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"api-key\": apiKey,\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const details = await response.text();\n return NextResponse.json({ error: \\`Azure OpenAI chat failed: \\${response.status}\\`, details }, { status: response.status });\n }\n\n return stream ? passthroughResponse(response) : jsonResponse(response);\n }\n\n case \"ollama\": {\n const { provider: _provider, ...cleanBody } = body;\n const requestBody = {\n ...cleanBody,\n stream,\n model: stripPrefix(body.model ?? DEFAULT_MODEL, \"ollama\", \"llama3.1\"),\n };\n\n const response = await fetch(\\`\\${OLLAMA_URL}/api/chat\\`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const details = await response.text();\n return NextResponse.json({ error: \\`Ollama chat failed: \\${response.status}\\`, details }, { status: response.status });\n }\n\n return stream ? passthroughResponse(response) : jsonResponse(response);\n }\n\n default:\n return errorResponse(400, \\`Unsupported provider: \\${provider}\\`);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing\") ? 400 : 500;\n return errorResponse(status, error);\n }\n}\n\n`;\nconst NEXT_HEALTH_ROUTE_TEMPLATE = `import { NextResponse } from \"next/server\";\n\nexport const dynamic = \"force-dynamic\";\n\nconst QUICKSTART_VERSION = \"0.1.0\";\nconst OLLAMA_URL = (process.env.OLLAMA_URL ?? \"http://localhost:11434\").replace(/\\\\/$/, \"\");\nconst OPENAI_API_KEY = process.env.OPENAI_API_KEY;\nconst AZURE_OPENAI_ENDPOINT = process.env.AZURE_OPENAI_ENDPOINT ? process.env.AZURE_OPENAI_ENDPOINT.replace(/\\\\/$/, \"\") : undefined;\nconst AZURE_OPENAI_API_KEY = process.env.AZURE_OPENAI_API_KEY;\nconst AZURE_OPENAI_API_VERSION = process.env.AZURE_OPENAI_API_VERSION ?? \"2024-08-01-preview\";\nconst ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;\nconst ANTHROPIC_BASE_URL = (process.env.ANTHROPIC_BASE_URL ?? \"https://api.anthropic.com\").replace(/\\\\/$/, \"\");\nconst ANTHROPIC_API_VERSION = process.env.ANTHROPIC_API_VERSION ?? \"2023-06-01\";\nconst XAI_API_KEY = process.env.XAI_API_KEY;\nconst XAI_BASE_URL = (process.env.XAI_BASE_URL ?? \"https://api.x.ai/v1\").replace(/\\\\/$/, \"\");\n\nconst isAzureConfigured = () => Boolean(AZURE_OPENAI_ENDPOINT && AZURE_OPENAI_API_KEY);\n\nconst buildAzurePath = (path: string) => {\n const normalized = path.replace(/^\\\\/+/, \"\");\n if (!AZURE_OPENAI_ENDPOINT) {\n throw new Error(\"Missing AZURE_OPENAI_ENDPOINT. Add it to your .env file to route requests to Azure OpenAI.\");\n }\n return \\`\\${AZURE_OPENAI_ENDPOINT}/openai/\\${normalized}?api-version=\\${AZURE_OPENAI_API_VERSION}\\`;\n};\n\nexport async function GET() {\n const providers: Array<Record<string, unknown>> = [];\n\n // OpenAI\n try {\n if (OPENAI_API_KEY) {\n const response = await fetch(\"https://api.openai.com/v1/models\", {\n headers: { Authorization: \\`Bearer \\${OPENAI_API_KEY}\\` },\n });\n providers.push({\n name: \"openai\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"openai\",\n });\n } else {\n providers.push({\n name: \"openai\",\n status: \"unconfigured\",\n provider: \"openai\",\n error: \"API key not configured\",\n });\n }\n } catch (error) {\n providers.push({\n name: \"openai\",\n status: \"unhealthy\",\n provider: \"openai\",\n error: error instanceof Error ? error.message : String(error),\n });\n }\n\n // Azure\n if (AZURE_OPENAI_ENDPOINT || AZURE_OPENAI_API_KEY) {\n if (!isAzureConfigured()) {\n providers.push({\n name: \"azure\",\n status: \"unconfigured\",\n provider: \"azure\",\n error: \"Endpoint or API key not configured\",\n endpoint: AZURE_OPENAI_ENDPOINT,\n });\n } else {\n try {\n const response = await fetch(buildAzurePath(\"deployments\"), {\n headers: { \"api-key\": AZURE_OPENAI_API_KEY ?? \"\" },\n });\n providers.push({\n name: \"azure\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"azure\",\n endpoint: AZURE_OPENAI_ENDPOINT,\n });\n } catch (error) {\n providers.push({\n name: \"azure\",\n status: \"unhealthy\",\n provider: \"azure\",\n endpoint: AZURE_OPENAI_ENDPOINT,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n } else {\n providers.push({\n name: \"azure\",\n status: \"unconfigured\",\n provider: \"azure\",\n error: \"Endpoint or API key not configured\",\n });\n }\n\n // Anthropic\n if (ANTHROPIC_API_KEY) {\n try {\n const response = await fetch(\\`\\${ANTHROPIC_BASE_URL}/v1/models\\`, {\n headers: {\n \"x-api-key\": ANTHROPIC_API_KEY,\n \"anthropic-version\": ANTHROPIC_API_VERSION,\n },\n });\n providers.push({\n name: \"anthropic\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"anthropic\",\n endpoint: ANTHROPIC_BASE_URL,\n });\n } catch (error) {\n providers.push({\n name: \"anthropic\",\n status: \"unhealthy\",\n provider: \"anthropic\",\n endpoint: ANTHROPIC_BASE_URL,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n } else {\n providers.push({\n name: \"anthropic\",\n status: \"unconfigured\",\n provider: \"anthropic\",\n error: \"API key not configured\",\n });\n }\n\n // xAI\n if (XAI_API_KEY) {\n try {\n const response = await fetch(XAI_BASE_URL + \"/models\", {\n headers: { Authorization: \"Bearer \" + XAI_API_KEY },\n });\n providers.push({\n name: \"xai\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"xai\",\n endpoint: XAI_BASE_URL,\n });\n } catch (error) {\n providers.push({\n name: \"xai\",\n status: \"unhealthy\",\n provider: \"xai\",\n endpoint: XAI_BASE_URL,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n } else {\n providers.push({\n name: \"xai\",\n status: \"unconfigured\",\n provider: \"xai\",\n error: \"API key not configured\",\n endpoint: XAI_BASE_URL,\n });\n }\n\n // Ollama\n try {\n const response = await fetch(\\`\\${OLLAMA_URL}/api/tags\\`);\n providers.push({\n name: \"ollama\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"ollama\",\n url: OLLAMA_URL,\n });\n } catch (error) {\n providers.push({\n name: \"ollama\",\n status: \"offline\",\n provider: \"ollama\",\n url: OLLAMA_URL,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n\n const overallHealthy = providers.some((provider) => provider.status === \"healthy\");\n\n return NextResponse.json({\n status: overallHealthy ? \"healthy\" : \"unhealthy\",\n version: QUICKSTART_VERSION,\n uptime: Math.round(process.uptime()),\n providers,\n });\n}\n\n`;\nconst NEXT_MODELS_ROUTE_TEMPLATE = `import { NextResponse } from \"next/server\";\n\nexport const dynamic = \"force-dynamic\";\n\nconst BASE_GATEWAY_MODELS = __GATEWAY_MODELS__;\n\nexport function toGatewayModels() {\n return BASE_GATEWAY_MODELS.map((model) => ({\n ...model,\n created: Date.now(),\n modified_at: new Date().toISOString(),\n size: 0,\n digest: \"\",\n details: {\n format: \"chat\",\n family: model.provider,\n families: [model.provider],\n parameter_size: \"\",\n quantization_level: \"\",\n },\n }));\n}\n\nexport async function GET() {\n return NextResponse.json({ models: toGatewayModels() });\n}\n\n`;\nconst NEXT_GATEWAY_README_TEMPLATE = `# Next.js Gateway API\n\nThis directory contains a minimal Next.js App Router implementation of the Bandit gateway API. It mirrors the Express gateway in\n\\`server/gateway.js\\` but is ready to drop into a Next.js project.\n\n## Routes\n\n- \\`app/api/health/route.ts\\` – provider health and availability checks\n- \\`app/api/chat/completions/route.ts\\` – provider-aware chat completions endpoint (Bandit AI, OpenAI, Azure OpenAI, Anthropic, xAI, Ollama)\n- \\`app/api/models/route.ts\\` – exposes the scaffolded gateway model metadata used by the frontend\n\n## Usage\n\n1. Copy the contents of \\`server/next-app/\\` into the \\`app/\\` directory of a Next.js project.\n2. Ensure the environment variables listed in \\`.env.example\\` are available to the Next.js runtime. At minimum you will want the\n provider API keys you plan to use (Bandit AI, OpenAI, Azure OpenAI, Anthropic, xAI, or Ollama).\n3. Start Next.js with \\`npm run dev\\` (or your project’s equivalent). The routes are server-only (\\`export const dynamic = \"force-dynamic\"\\`)\n and can coexist with any frontend pages.\n\nThe generated routes favour clarity over cleverness so you can extend them with custom auth, logging, and provider routing logic.\n`;\n\nexport const buildNextChatRoute = (ctx: QuickstartTemplateContext): string => {\n const fallbackModel = ctx.fallbackModelId ? `\"${ctx.fallbackModelId}\"` : \"undefined\";\n return ensureTrailingNewline(\n normalizeLineEndings(\n NEXT_CHAT_ROUTE_TEMPLATE\n .replace(/__DEFAULT_PROVIDER__/g, ctx.defaultProvider)\n .replace(/__DEFAULT_MODEL__/g, ctx.defaultModelId)\n .replace(/__FALLBACK_MODEL__/g, fallbackModel)\n )\n );\n};\n\nexport const buildNextHealthRoute = (): string =>\n ensureTrailingNewline(normalizeLineEndings(NEXT_HEALTH_ROUTE_TEMPLATE));\n\nexport const buildNextModelsRoute = (ctx: QuickstartTemplateContext): string => {\n const modelsDefinition = JSON.stringify(ctx.gatewayModels, null, 2);\n return ensureTrailingNewline(\n normalizeLineEndings(\n NEXT_MODELS_ROUTE_TEMPLATE.replace('__GATEWAY_MODELS__', modelsDefinition)\n )\n );\n};\n\nexport const buildNextGatewayReadme = (): string =>\n ensureTrailingNewline(normalizeLineEndings(NEXT_GATEWAY_README_TEMPLATE));\n\nexport const buildGatewayServer = (ctx: QuickstartTemplateContext): string => {\n const modelsDefinition = JSON.stringify(ctx.gatewayModels, null, 2);\n\n const gatewaySource = `import express from \"express\";\nimport cors from \"cors\";\nimport dotenv from \"dotenv\";\n\ndotenv.config();\n\nconst app = express();\napp.use(cors());\napp.use(express.json({ limit: '50mb' }));\napp.use(express.urlencoded({ limit: '50mb', extended: true }));\n\nconst QUICKSTART_VERSION = \"0.1.0\";\nconst DEFAULT_PROVIDER = \"${ctx.defaultProvider}\";\nconst BASE_GATEWAY_MODELS = ${modelsDefinition};\nconst BANDIT_API_KEY = process.env.BANDIT_API_KEY;\nconst BANDIT_BASE_URL = (process.env.BANDIT_BASE_URL ?? \"https://api.burtson.ai\").replace(/\\\\/$/, \"\");\nconst OLLAMA_BASE_URL = (process.env.OLLAMA_URL ?? \"http://localhost:11434\").replace(/\\\\/$/, \"\");\nconst AZURE_OPENAI_ENDPOINT = process.env.AZURE_OPENAI_ENDPOINT ? process.env.AZURE_OPENAI_ENDPOINT.replace(/\\\\/$/, \"\") : undefined;\nconst AZURE_OPENAI_API_KEY = process.env.AZURE_OPENAI_API_KEY;\nconst AZURE_OPENAI_API_VERSION = process.env.AZURE_OPENAI_API_VERSION ?? \"2024-08-01-preview\";\nconst AZURE_OPENAI_CHAT_DEPLOYMENT = process.env.AZURE_OPENAI_CHAT_DEPLOYMENT;\nconst AZURE_OPENAI_COMPLETIONS_DEPLOYMENT = process.env.AZURE_OPENAI_COMPLETIONS_DEPLOYMENT ?? AZURE_OPENAI_CHAT_DEPLOYMENT;\nconst AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT = process.env.AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT;\nconst ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;\nconst ANTHROPIC_BASE_URL = (process.env.ANTHROPIC_BASE_URL ?? \"https://api.anthropic.com\").replace(/\\\\/$/, \"\");\nconst ANTHROPIC_API_VERSION = process.env.ANTHROPIC_API_VERSION ?? \"2023-06-01\";\nconst ANTHROPIC_MAX_TOKENS = Number.isFinite(Number(process.env.ANTHROPIC_MAX_TOKENS))\n ? Number(process.env.ANTHROPIC_MAX_TOKENS)\n : 1024;\nconst XAI_API_KEY = process.env.XAI_API_KEY;\nconst XAI_BASE_URL = (process.env.XAI_BASE_URL ?? \"https://api.x.ai/v1\").replace(/\\\\/$/, \"\");\n\nconst toGatewayModels = () =>\n BASE_GATEWAY_MODELS.map((model) => ({\n ...model,\n created: Date.now(),\n modified_at: new Date().toISOString(),\n size: 0,\n digest: \"\",\n details: {\n format: \"chat\",\n family: model.provider,\n families: [model.provider],\n parameter_size: \"\",\n quantization_level: \"\",\n },\n }));\n\nconst stripAzureModelPrefix = (value) =>\n typeof value === \"string\" ? value.replace(/^azure:/, \"\") : undefined;\n\nconst requireBanditKey = () => {\n if (!BANDIT_API_KEY) {\n throw new Error(\"Missing BANDIT_API_KEY. Add it to your .env file to route requests to Bandit AI.\");\n }\n return BANDIT_API_KEY;\n};\n\nconst isAzureConfigured = () => Boolean(AZURE_OPENAI_ENDPOINT && AZURE_OPENAI_API_KEY);\n\nconst requireAzureBaseConfig = () => {\n if (!AZURE_OPENAI_ENDPOINT) {\n throw new Error(\"Missing AZURE_OPENAI_ENDPOINT. Add it to your .env file to route requests to Azure OpenAI.\");\n }\n if (!AZURE_OPENAI_API_KEY) {\n throw new Error(\"Missing AZURE_OPENAI_API_KEY. Add it to your .env file to route requests to Azure OpenAI.\");\n }\n return {\n endpoint: AZURE_OPENAI_ENDPOINT,\n apiKey: AZURE_OPENAI_API_KEY,\n apiVersion: AZURE_OPENAI_API_VERSION,\n };\n};\n\nconst resolveAzureDeployment = (explicitValue, fallbackValue, kind) => {\n const fromRequest = stripAzureModelPrefix(explicitValue);\n if (fromRequest) {\n return fromRequest;\n }\n if (fallbackValue) {\n return fallbackValue;\n }\n throw new Error(\\`Missing Azure OpenAI \\${kind} deployment name. Set AZURE_OPENAI_\\${kind.toUpperCase()}_DEPLOYMENT in your .env file.\\`);\n};\n\nconst buildAzureDeploymentUrl = (deployment, suffix) => {\n const { endpoint } = requireAzureBaseConfig();\n const normalizedSuffix = suffix.replace(/^\\\\//, \"\");\n return \\`\\${endpoint}/openai/deployments/\\${deployment}/\\${normalizedSuffix}?api-version=\\${AZURE_OPENAI_API_VERSION}\\`;\n};\n\nconst buildAzurePath = (suffix) => {\n const { endpoint } = requireAzureBaseConfig();\n const normalizedSuffix = suffix.replace(/^\\\\//, \"\");\n const hasQuery = normalizedSuffix.includes(\"?\");\n const separator = hasQuery ? \"&\" : \"?\";\n return \\`\\${endpoint}/openai/\\${normalizedSuffix}\\${separator}api-version=\\${AZURE_OPENAI_API_VERSION}\\`;\n};\n\nconst stripAnthropicModelPrefix = (value) =>\n typeof value === \"string\" ? value.replace(/^anthropic:/, \"\") : undefined;\n\nconst isAnthropicConfigured = () => Boolean(ANTHROPIC_API_KEY);\n\nconst requireAnthropicKey = () => {\n if (!ANTHROPIC_API_KEY) {\n throw new Error(\"Missing ANTHROPIC_API_KEY. Add it to your .env file to route requests to Anthropic.\");\n }\n return ANTHROPIC_API_KEY;\n};\n\nconst buildAnthropicUrl = (path) => {\n const normalized = path.replace(/^\\\\//, \"\");\n return \\`\\${ANTHROPIC_BASE_URL}/v1/\\${normalized}\\`;\n};\n\nconst buildAnthropicHeaders = () => ({\n \"Content-Type\": \"application/json\",\n \"x-api-key\": requireAnthropicKey(),\n \"anthropic-version\": ANTHROPIC_API_VERSION,\n});\n\nconst flattenGatewayContent = (content) => {\n if (typeof content === \"string\") {\n return content;\n }\n if (Array.isArray(content)) {\n return content\n .map((part) => {\n if (typeof part === \"string\") {\n return part;\n }\n if (part?.type === \"text\" && typeof part.text === \"string\") {\n return part.text;\n }\n if (part?.type === \"image_url\" && part.image_url?.url) {\n return \\`[Image: \\${part.image_url.url}]\\`;\n }\n return JSON.stringify(part ?? {});\n })\n .join(\"\\\\n\");\n }\n if (content && typeof content === \"object\") {\n return JSON.stringify(content);\n }\n return \"\";\n};\n\nconst normalizeGatewayImageUrl = (value) => {\n if (!value) {\n return \"\";\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (!trimmed) {\n return \"\";\n }\n if (/^data:/i.test(trimmed) || /^https?:/i.test(trimmed)) {\n return trimmed;\n }\n return 'data:image/jpeg;base64,' + trimmed;\n }\n if (typeof value === \"object\") {\n const possibleUrl =\n typeof value.url === \"string\"\n ? value.url\n : value.image_url && typeof value.image_url.url === \"string\"\n ? value.image_url.url\n : \"\";\n return normalizeGatewayImageUrl(possibleUrl);\n }\n return \"\";\n};\n\nconst extractGatewayImageDetail = (value) => {\n if (value && typeof value === \"object\") {\n const record = value;\n if (typeof record.detail === \"string\" && record.detail.trim()) {\n return record.detail;\n }\n if (record.image_url && typeof record.image_url.detail === \"string\" && record.image_url.detail.trim()) {\n return record.image_url.detail;\n }\n }\n return undefined;\n};\n\nconst toAnthropicMessages = (messages = []) => {\n const anthropicMessages = [];\n let systemPrompt = \"\";\n\n for (const message of messages) {\n if (!message) continue;\n const text = flattenGatewayContent(message.content);\n\n if (message.role === \"system\") {\n systemPrompt = systemPrompt ? \\`\\${systemPrompt}\\\\n\\\\n\\${text}\\` : text;\n continue;\n }\n\n const role = message.role === \"assistant\" ? \"assistant\" : \"user\";\n anthropicMessages.push({\n role,\n content: [{ type: \"text\", text }],\n });\n }\n\n return { messages: anthropicMessages, system: systemPrompt || undefined };\n};\n\nconst convertAnthropicResponseToGateway = (responseBody, modelName) => {\n if (!responseBody) {\n return {\n id: \\`anthropic-\\${Date.now()}\\`,\n object: \"chat.completion\",\n created: Math.floor(Date.now() / 1000),\n model: modelName.startsWith(\"anthropic:\") ? modelName : \\`anthropic:\\${modelName}\\`,\n choices: [],\n };\n }\n\n const textContent = Array.isArray(responseBody.content)\n ? responseBody.content\n .filter((item) => item && item.type === \"text\" && typeof item.text === \"string\")\n .map((item) => item.text)\n .join(\"\\\\n\")\n : typeof responseBody.content === \"string\"\n ? responseBody.content\n : \"\";\n\n const promptTokens = responseBody.usage?.input_tokens ?? 0;\n const completionTokens = responseBody.usage?.output_tokens ?? 0;\n\n return {\n id: responseBody.id ?? \\`anthropic-\\${Date.now()}\\`,\n object: \"chat.completion\",\n created: Math.floor(Date.now() / 1000),\n model: modelName.startsWith(\"anthropic:\") ? modelName : \\`anthropic:\\${modelName}\\`,\n choices: [\n {\n index: 0,\n message: {\n role: responseBody.role ?? \"assistant\",\n content: textContent,\n },\n finish_reason: responseBody.stop_reason ?? responseBody.stop_sequence ?? null,\n },\n ],\n usage: responseBody.usage\n ? {\n prompt_tokens: promptTokens,\n completion_tokens: completionTokens,\n total_tokens: promptTokens + completionTokens,\n }\n : undefined,\n };\n};\n\nconst convertAnthropicResponseToGenerate = (responseBody, modelName) => {\n const gatewayResponse = convertAnthropicResponseToGateway(responseBody, modelName);\n const content = gatewayResponse.choices?.[0]?.message?.content ?? \"\";\n return {\n model: gatewayResponse.model,\n created_at: new Date().toISOString(),\n response: content,\n done: true,\n total_duration: 0,\n load_duration: 0,\n prompt_eval_count: gatewayResponse.usage?.prompt_tokens ?? 0,\n prompt_eval_duration: 0,\n eval_count: gatewayResponse.usage?.completion_tokens ?? 0,\n eval_duration: 0,\n };\n};\n\nconst requireOpenAIKey = () => {\n const key = process.env.OPENAI_API_KEY;\n if (!key) {\n throw new Error(\"Missing OPENAI_API_KEY. Add it to your .env file to route requests to OpenAI.\");\n }\n return key;\n};\n\nconst requireXAIKey = () => {\n const key = XAI_API_KEY;\n if (!key) {\n throw new Error(\"Missing XAI_API_KEY. Add it to your .env file to route requests to xAI.\");\n }\n return key;\n};\n\n// Utility function to handle streaming responses\nconst handleStreamingResponse = async (upstreamResponse, res) => {\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('Access-Control-Allow-Origin', '*');\n \n try {\n // Get the readable stream from the response\n const reader = upstreamResponse.body.getReader();\n \n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n \n // Write the chunk to the response\n res.write(value);\n }\n \n res.end();\n } catch (error) {\n console.error('Streaming error:', error);\n // Fallback to non-streaming\n const text = await upstreamResponse.text();\n res.send(text);\n }\n};\n\nconst relayAnthropicStream = async (upstreamResponse, res) => {\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('Access-Control-Allow-Origin', '*');\n\n const reader = upstreamResponse.body?.getReader();\n if (!reader) {\n const fallback = await upstreamResponse.text();\n res.write(\"data: \" + JSON.stringify({ choices: [{ delta: { content: fallback } }] }) + \"\\\\n\\\\n\");\n res.write(\"data: [DONE]\\\\n\\\\n\");\n return res.end();\n }\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n const sendChunk = (payload) => {\n res.write(\"data: \" + JSON.stringify(payload) + \"\\\\n\\\\n\");\n };\n\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n let delimiterIndex;\n while ((delimiterIndex = buffer.indexOf('\\\\n\\\\n')) >= 0) {\n const rawEvent = buffer.slice(0, delimiterIndex).trim();\n buffer = buffer.slice(delimiterIndex + 2);\n if (!rawEvent) continue;\n\n const lines = rawEvent.split('\\\\n');\n const eventLine = lines.find((line) => line.startsWith('event:')) ?? '';\n const dataLine = lines.find((line) => line.startsWith('data:')) ?? '';\n const event = eventLine.replace('event:', '').trim();\n const trimmedData = dataLine.replace('data:', '').trim();\n\n if (!trimmedData) {\n continue;\n }\n\n let parsed;\n try {\n parsed = JSON.parse(trimmedData);\n } catch (error) {\n console.error('Anthropic stream parse error', error, { rawEvent });\n continue;\n }\n\n if (event === 'content_block_delta') {\n const textChunk = parsed?.delta?.text ?? '';\n if (textChunk) {\n sendChunk({\n choices: [\n {\n delta: {\n content: textChunk,\n },\n },\n ],\n });\n }\n } else if (event === 'message_stop') {\n sendChunk({\n choices: [\n {\n delta: {},\n finish_reason: 'stop',\n },\n ],\n });\n }\n }\n }\n } catch (error) {\n console.error('Anthropic streaming relay error', error);\n sendChunk({\n error: error instanceof Error ? error.message : String(error),\n });\n } finally {\n res.write(\"data: [DONE]\\\\n\\\\n\");\n res.end();\n }\n};\n\n// ============================================================================\n// GENERAL HEALTH & MODELS\n// ============================================================================\n\napp.get(\"/api/health\", async (_req, res) => {\n const providers = [];\n \n // Check OpenAI\n try {\n const openaiKey = process.env.OPENAI_API_KEY;\n if (openaiKey) {\n const response = await fetch(\"https://api.openai.com/v1/models\", {\n headers: { \"Authorization\": \\`Bearer \\${openaiKey}\\` }\n });\n providers.push({\n name: \"openai\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"openai\"\n });\n } else {\n providers.push({\n name: \"openai\", \n status: \"unconfigured\",\n provider: \"openai\",\n error: \"API key not configured\"\n });\n }\n } catch (error) {\n providers.push({\n name: \"openai\",\n status: \"unhealthy\", \n provider: \"openai\",\n error: error.message\n });\n }\n\n // Check Azure OpenAI\n if (AZURE_OPENAI_ENDPOINT || AZURE_OPENAI_API_KEY) {\n if (!isAzureConfigured()) {\n providers.push({\n name: \"azure\",\n status: \"unconfigured\",\n provider: \"azure\",\n error: \"Endpoint or API key not configured\",\n endpoint: AZURE_OPENAI_ENDPOINT\n });\n } else {\n try {\n const { endpoint } = requireAzureBaseConfig();\n const deploymentsUrl = buildAzurePath(\"deployments\");\n const response = await fetch(deploymentsUrl, {\n headers: { \"api-key\": AZURE_OPENAI_API_KEY }\n });\n providers.push({\n name: \"azure\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"azure\",\n endpoint\n });\n } catch (error) {\n providers.push({\n name: \"azure\",\n status: \"unhealthy\",\n provider: \"azure\",\n error: error instanceof Error ? error.message : String(error),\n endpoint: AZURE_OPENAI_ENDPOINT\n });\n }\n }\n } else {\n providers.push({\n name: \"azure\",\n status: \"unconfigured\",\n provider: \"azure\",\n error: \"Endpoint or API key not configured\"\n });\n }\n\n // Check Anthropic\n if (ANTHROPIC_API_KEY) {\n try {\n const response = await fetch(buildAnthropicUrl(\"models\"), {\n headers: buildAnthropicHeaders(),\n method: \"GET\"\n });\n providers.push({\n name: \"anthropic\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"anthropic\",\n endpoint: ANTHROPIC_BASE_URL\n });\n } catch (error) {\n providers.push({\n name: \"anthropic\",\n status: \"unhealthy\",\n provider: \"anthropic\",\n error: error instanceof Error ? error.message : String(error),\n endpoint: ANTHROPIC_BASE_URL\n });\n }\n } else {\n providers.push({\n name: \"anthropic\",\n status: \"unconfigured\",\n provider: \"anthropic\",\n error: \"API key not configured\"\n });\n }\n\n // Check xAI\n if (XAI_API_KEY) {\n try {\n const response = await fetch(XAI_BASE_URL + \"/models\", {\n headers: { \"Authorization\": \"Bearer \" + XAI_API_KEY }\n });\n providers.push({\n name: \"xai\",\n status: response.ok ? \"healthy\" : \"unhealthy\",\n provider: \"xai\",\n endpoint: XAI_BASE_URL\n });\n } catch (error) {\n providers.push({\n name: \"xai\",\n status: \"unhealthy\",\n provider: \"xai\",\n error: error instanceof Error ? error.message : String(error),\n endpoint: XAI_BASE_URL\n });\n }\n } else {\n providers.push({\n name: \"xai\",\n status: \"unconfigured\",\n provider: \"xai\",\n error: \"API key not configured\",\n endpoint: XAI_BASE_URL\n });\n }\n\n // Check Ollama\n try {\n console.log(\\`Checking Ollama health at: \\${OLLAMA_BASE_URL}/api/tags\\`);\n const response = await fetch(\\`\\${OLLAMA_BASE_URL}/api/tags\\`);\n const status = response.ok ? \"healthy\" : \"unhealthy\";\n console.log(\\`Ollama health check result: \\${status}\\`);\n providers.push({\n name: \"ollama\",\n status: status,\n provider: \"ollama\",\n url: OLLAMA_BASE_URL\n });\n } catch (error) {\n console.log(\\`Ollama health check error: \\${error.message}\\`);\n providers.push({\n name: \"ollama\",\n status: \"offline\",\n provider: \"ollama\",\n error: error.message,\n url: OLLAMA_BASE_URL\n });\n }\n\n const overallHealthy = providers.some(p => p.status === \"healthy\");\n \n res.json({\n status: overallHealthy ? \"healthy\" : \"unhealthy\",\n version: QUICKSTART_VERSION,\n uptime: Math.round(process.uptime()),\n providers\n });\n});\n\napp.get(\"/api/models\", (_req, res) => {\n res.json({ models: toGatewayModels() });\n});\n\n// ============================================================================\n// ANTHROPIC ROUTES\n// ============================================================================\n\napp.get(\"/api/anthropic/health\", async (_req, res) => {\n try {\n requireAnthropicKey();\n const response = await fetch(buildAnthropicUrl(\"models\"), {\n method: \"GET\",\n headers: buildAnthropicHeaders()\n });\n const isHealthy = response.ok;\n res.json({\n status: isHealthy ? \"healthy\" : \"unhealthy\",\n anthropic_status: isHealthy,\n provider: \"anthropic\",\n endpoint: ANTHROPIC_BASE_URL\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n res.status(503).json({\n status: \"unhealthy\",\n anthropic_status: false,\n provider: \"anthropic\",\n error: message,\n endpoint: ANTHROPIC_BASE_URL\n });\n }\n});\n\napp.post(\"/api/anthropic/chat/completions\", async (req, res) => {\n try {\n requireAnthropicKey();\n const rawBody = req.body ?? {};\n const isStreaming = rawBody.stream === true;\n const requestedModel =\n stripAnthropicModelPrefix(rawBody.model) ??\n stripAnthropicModelPrefix(\"${ctx.defaultModelId}\") ??\n \"claude-3-5-haiku-latest\";\n\n const stopSequences = Array.isArray(rawBody.stop)\n ? rawBody.stop\n : Array.isArray(rawBody.stop_sequences)\n ? rawBody.stop_sequences\n : rawBody.stop\n ? [rawBody.stop]\n : undefined;\n\n const { messages: anthropicMessages, system } = toAnthropicMessages(\n Array.isArray(rawBody.messages) ? rawBody.messages : []\n );\n\n const fallbackText =\n typeof rawBody.prompt === \"string\" && rawBody.prompt.trim().length > 0\n ? rawBody.prompt\n : \"Hello from Bandit quickstart gateway\";\n\n const requestBody = {\n model: requestedModel,\n messages:\n anthropicMessages.length > 0\n ? anthropicMessages\n : [\n {\n role: \"user\",\n content: [{ type: \"text\", text: fallbackText }],\n },\n ],\n stream: isStreaming,\n max_tokens:\n typeof rawBody.max_tokens === \"number\" && rawBody.max_tokens > 0\n ? rawBody.max_tokens\n : ANTHROPIC_MAX_TOKENS,\n };\n\n if (system) {\n requestBody.system = system;\n }\n if (typeof rawBody.temperature === \"number\") {\n requestBody.temperature = rawBody.temperature;\n }\n if (typeof rawBody.top_p === \"number\") {\n requestBody.top_p = rawBody.top_p;\n }\n if (typeof rawBody.top_k === \"number\") {\n requestBody.top_k = rawBody.top_k;\n }\n if (stopSequences) {\n requestBody.stop_sequences = stopSequences;\n }\n if (rawBody.metadata) {\n requestBody.metadata = rawBody.metadata;\n }\n if (rawBody.tools) {\n requestBody.tools = rawBody.tools;\n }\n if (rawBody.tool_choice) {\n requestBody.tool_choice = rawBody.tool_choice;\n }\n if (rawBody.thinking) {\n requestBody.thinking = rawBody.thinking;\n }\n if (rawBody.extra_headers) {\n requestBody.extra_headers = rawBody.extra_headers;\n }\n\n const response = await fetch(buildAnthropicUrl(\"messages\"), {\n method: \"POST\",\n headers: buildAnthropicHeaders(),\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Anthropic chat failed: \\${response.status}\\`,\n details: errorText,\n });\n }\n\n if (isStreaming) {\n await relayAnthropicStream(response, res);\n } else {\n const data = await response.json();\n const normalized = convertAnthropicResponseToGateway(data, requestedModel);\n res.json(normalized);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing ANTHROPIC_API_KEY\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\napp.post(\"/api/anthropic/chat\", async (req, res) => {\n req.url = \"/api/anthropic/chat/completions\";\n return app._router.handle(req, res);\n});\n\napp.post(\"/api/anthropic/completions\", async (req, res) => {\n try {\n requireAnthropicKey();\n const rawBody = req.body ?? {};\n const isStreaming = rawBody.stream === true;\n const requestedModel =\n stripAnthropicModelPrefix(rawBody.model) ??\n stripAnthropicModelPrefix(\"${ctx.defaultModelId}\") ??\n \"claude-3-5-sonnet-latest\";\n\n const stopSequences = Array.isArray(rawBody.stop)\n ? rawBody.stop\n : Array.isArray(rawBody.stop_sequences)\n ? rawBody.stop_sequences\n : rawBody.stop\n ? [rawBody.stop]\n : undefined;\n\n const prompt =\n typeof rawBody.prompt === \"string\" && rawBody.prompt.trim().length > 0\n ? rawBody.prompt\n : \"Hello from Bandit quickstart gateway\";\n\n const { messages, system } = toAnthropicMessages([\n { role: \"user\", content: prompt },\n ]);\n\n const requestBody = {\n model: requestedModel,\n messages,\n stream: isStreaming,\n max_tokens:\n typeof rawBody.max_tokens === \"number\" && rawBody.max_tokens > 0\n ? rawBody.max_tokens\n : ANTHROPIC_MAX_TOKENS,\n };\n\n if (system) {\n requestBody.system = system;\n }\n if (typeof rawBody.temperature === \"number\") {\n requestBody.temperature = rawBody.temperature;\n }\n if (typeof rawBody.top_p === \"number\") {\n requestBody.top_p = rawBody.top_p;\n }\n if (typeof rawBody.top_k === \"number\") {\n requestBody.top_k = rawBody.top_k;\n }\n if (stopSequences) {\n requestBody.stop_sequences = stopSequences;\n }\n if (rawBody.metadata) {\n requestBody.metadata = rawBody.metadata;\n }\n if (rawBody.tools) {\n requestBody.tools = rawBody.tools;\n }\n if (rawBody.tool_choice) {\n requestBody.tool_choice = rawBody.tool_choice;\n }\n\n const response = await fetch(buildAnthropicUrl(\"messages\"), {\n method: \"POST\",\n headers: buildAnthropicHeaders(),\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Anthropic completions failed: \\${response.status}\\`,\n details: errorText,\n });\n }\n\n if (isStreaming) {\n await relayAnthropicStream(response, res);\n } else {\n const data = await response.json();\n const formatted = convertAnthropicResponseToGenerate(data, requestedModel);\n res.json(formatted);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing ANTHROPIC_API_KEY\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\napp.post(\"/api/anthropic/generate\", async (req, res) => {\n req.url = \"/api/anthropic/completions\";\n return app._router.handle(req, res);\n});\n\napp.get(\"/api/anthropic/models\", async (_req, res) => {\n try {\n requireAnthropicKey();\n const response = await fetch(buildAnthropicUrl(\"models\"), {\n method: \"GET\",\n headers: buildAnthropicHeaders(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Anthropic models failed: \\${response.status}\\`,\n details: errorText,\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing ANTHROPIC_API_KEY\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\napp.post(\"/api/anthropic/embed\", async (_req, res) => {\n res.status(501).json({\n error: \"Anthropic embeddings not implemented\",\n message: \"Add support for the Anthropic embeddings endpoint if your use case requires it.\"\n });\n});\n\n// ============================================================================\n// AZURE OPENAI ROUTES\n// ============================================================================\n\napp.get(\"/api/azure/health\", async (_req, res) => {\n try {\n const { endpoint } = requireAzureBaseConfig();\n const deploymentsUrl = buildAzurePath(\"deployments\");\n const response = await fetch(deploymentsUrl, {\n headers: { \"api-key\": AZURE_OPENAI_API_KEY }\n });\n const isHealthy = response.ok;\n res.json({\n status: isHealthy ? \"healthy\" : \"unhealthy\",\n azure_status: isHealthy,\n provider: \"azure\",\n endpoint\n });\n } catch (error) {\n res.status(503).json({\n status: \"unhealthy\",\n azure_status: false,\n provider: \"azure\",\n error: error instanceof Error ? error.message : String(error),\n endpoint: AZURE_OPENAI_ENDPOINT\n });\n }\n});\n\napp.post(\"/api/azure/chat/completions\", async (req, res) => {\n try {\n const { apiKey } = requireAzureBaseConfig();\n const deployment = resolveAzureDeployment(req.body?.model, AZURE_OPENAI_CHAT_DEPLOYMENT, \"chat\");\n const isStreaming = req.body?.stream === true;\n const { provider, model, ...cleanBody } = req.body ?? {};\n const requestBody = { ...cleanBody };\n\n const response = await fetch(buildAzureDeploymentUrl(deployment, \"chat/completions\"), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"api-key\": apiKey\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Azure OpenAI chat failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing Azure OpenAI\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\napp.post(\"/api/azure/chat\", async (req, res) => {\n req.url = \"/api/azure/chat/completions\";\n return app._router.handle(req, res);\n});\n\napp.post(\"/api/azure/completions\", async (req, res) => {\n try {\n const { apiKey } = requireAzureBaseConfig();\n const deployment = resolveAzureDeployment(req.body?.model, AZURE_OPENAI_COMPLETIONS_DEPLOYMENT, \"completions\");\n const isStreaming = req.body?.stream === true;\n const { provider, model, ...cleanBody } = req.body ?? {};\n const requestBody = { ...cleanBody };\n\n const response = await fetch(buildAzureDeploymentUrl(deployment, \"completions\"), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"api-key\": apiKey\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Azure OpenAI completions failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing Azure OpenAI\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\napp.post(\"/api/azure/generate\", async (req, res) => {\n try {\n const { apiKey } = requireAzureBaseConfig();\n const deployment = resolveAzureDeployment(req.body?.model, AZURE_OPENAI_CHAT_DEPLOYMENT, \"chat\");\n const prompt = req.body?.prompt || \"\";\n const isStreaming = req.body?.stream === true;\n\n const chatBody = {\n messages: [\n {\n role: \"user\",\n content: prompt\n }\n ],\n stream: isStreaming,\n max_tokens: req.body?.max_tokens ?? 150,\n temperature: req.body?.temperature ?? 0.7\n };\n\n const response = await fetch(buildAzureDeploymentUrl(deployment, \"chat/completions\"), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"api-key\": apiKey\n },\n body: JSON.stringify(chatBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Azure OpenAI generate failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing Azure OpenAI\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\napp.get(\"/api/azure/models\", async (_req, res) => {\n try {\n requireAzureBaseConfig();\n\n const response = await fetch(buildAzurePath(\"deployments\"), {\n headers: { \"api-key\": AZURE_OPENAI_API_KEY }\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Azure OpenAI models failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing Azure OpenAI\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\napp.post(\"/api/azure/embed\", async (req, res) => {\n try {\n const { apiKey } = requireAzureBaseConfig();\n const deployment = resolveAzureDeployment(req.body?.model, AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT, \"embeddings\");\n const { provider, model, ...cleanBody } = req.body ?? {};\n const requestBody = { ...cleanBody };\n\n const response = await fetch(buildAzureDeploymentUrl(deployment, \"embeddings\"), {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"api-key\": apiKey\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Azure OpenAI embed failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.startsWith(\"Missing Azure OpenAI\") ? 400 : 500;\n res.status(status).json({ error: message });\n }\n});\n\n// ============================================================================\n// XAI ROUTES\n// ============================================================================\n\n// xAI Health Check\napp.get(\"/api/xai/health\", async (_req, res) => {\n try {\n const xaiKey = requireXAIKey();\n const response = await fetch(XAI_BASE_URL + \"/models\", {\n headers: { \"Authorization\": \"Bearer \" + xaiKey }\n });\n const isHealthy = response.ok;\n res.json({\n status: isHealthy ? \"healthy\" : \"unhealthy\",\n xai_status: isHealthy,\n provider: \"xai\"\n });\n } catch (error) {\n res.status(503).json({\n status: \"unhealthy\",\n xai_status: false,\n error: error instanceof Error ? error.message : String(error),\n provider: \"xai\"\n });\n }\n});\n\n// xAI Chat Completions\napp.post(\"/api/xai/chat/completions\", async (req, res) => {\n try {\n const xaiKey = requireXAIKey();\n const isStreaming = req.body?.stream === true;\n const { provider, ...cleanBody } = req.body ?? {};\n const requestBody = {\n ...cleanBody,\n model: req.body?.model?.replace(/^xai:/, \"\") || \"grok-2-latest\"\n };\n\n const response = await fetch(XAI_BASE_URL + \"/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \"Bearer \" + xaiKey\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \"xAI chat failed: \" + response.status,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\napp.post(\"/api/xai/chat\", async (req, res) => {\n req.url = \"/api/xai/chat/completions\";\n return app._router.handle(req, res);\n});\n\n// xAI Completions\napp.post(\"/api/xai/completions\", async (req, res) => {\n try {\n const xaiKey = requireXAIKey();\n const isStreaming = req.body?.stream === true;\n const { provider, ...cleanBody } = req.body ?? {};\n const requestBody = {\n ...cleanBody,\n model: req.body?.model?.replace(/^xai:/, \"\") || \"grok-2-mini\"\n };\n\n const response = await fetch(XAI_BASE_URL + \"/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \"Bearer \" + xaiKey\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \"xAI completions failed: \" + response.status,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\n// xAI Generate\napp.post(\"/api/xai/generate\", async (req, res) => {\n try {\n const xaiKey = requireXAIKey();\n const prompt = req.body?.prompt || \"\";\n const model = req.body?.model?.replace(/^xai:/, \"\") || \"grok-2-latest\";\n const isStreaming = req.body?.stream === true;\n\n const chatBody = {\n model,\n messages: [\n { role: \"user\", content: prompt }\n ],\n stream: isStreaming,\n max_tokens: req.body?.max_tokens || 150,\n temperature: req.body?.temperature ?? 0.7\n };\n\n const response = await fetch(XAI_BASE_URL + \"/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \"Bearer \" + xaiKey\n },\n body: JSON.stringify(chatBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \"xAI generate failed: \" + response.status,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const data = await response.json();\n const generateResponse = {\n model,\n created_at: new Date().toISOString(),\n response: data.choices?.[0]?.message?.content || \"\",\n done: true,\n context: [],\n total_duration: 0,\n load_duration: 0,\n prompt_eval_count: data.usage?.prompt_tokens || 0,\n prompt_eval_duration: 0,\n eval_count: data.usage?.completion_tokens || 0,\n eval_duration: 0\n };\n res.json(generateResponse);\n }\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\n// xAI Models\napp.get(\"/api/xai/models\", async (_req, res) => {\n try {\n const xaiKey = requireXAIKey();\n const response = await fetch(XAI_BASE_URL + \"/models\", {\n headers: { \"Authorization\": \"Bearer \" + xaiKey }\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \"xAI models failed: \" + response.status,\n details: errorText\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\n// ============================================================================\n// BANDIT AI ROUTES\n// ============================================================================\n\napp.get(\"/api/bandit/health\", async (_req, res) => {\n try {\n const banditKey = requireBanditKey();\n const response = await fetch(BANDIT_BASE_URL + \"/models\", {\n headers: { \"Authorization\": \\`Bearer \\${banditKey}\\` }\n });\n const isHealthy = response.ok;\n res.json({\n status: isHealthy ? \"healthy\" : \"unhealthy\",\n bandit_status: isHealthy,\n provider: \"bandit\",\n endpoint: BANDIT_BASE_URL\n });\n } catch (error) {\n res.status(503).json({\n status: \"unhealthy\",\n bandit_status: false,\n error: error instanceof Error ? error.message : String(error),\n provider: \"bandit\",\n endpoint: BANDIT_BASE_URL\n });\n }\n});\n\napp.post(\"/api/bandit/chat/completions\", async (req, res) => {\n try {\n const banditKey = requireBanditKey();\n const isStreaming = req.body?.stream === true;\n const { provider, ...cleanBody } = req.body ?? {};\n const providerName = typeof provider === \"string\" ? provider : \"bandit\";\n const requestBody = {\n ...cleanBody,\n model: req.body?.model?.replace(/^bandit:/, \"\") || \"bandit-core-1\"\n };\n\n if (\n providerName !== \"ollama\" &&\n Array.isArray(requestBody.images) &&\n requestBody.images.length > 0 &&\n Array.isArray(requestBody.messages)\n ) {\n const lastUserIndex = requestBody.messages.map((message) => message?.role).lastIndexOf(\"user\");\n if (lastUserIndex !== -1) {\n const targetMessage = requestBody.messages[lastUserIndex] ?? {};\n const baseContent = Array.isArray(targetMessage.content)\n ? targetMessage.content.filter(Boolean)\n : typeof targetMessage.content === \"string\" && targetMessage.content.trim().length > 0\n ? [{ type: \"text\", text: targetMessage.content }]\n : [];\n\n const imageContent = requestBody.images\n .map((entry) => {\n const url = normalizeGatewayImageUrl(entry);\n if (!url) {\n return null;\n }\n return {\n type: \"image_url\",\n image_url: {\n url,\n detail: extractGatewayImageDetail(entry) ?? \"auto\"\n }\n };\n })\n .filter(Boolean);\n\n if (imageContent.length > 0) {\n requestBody.messages[lastUserIndex] = {\n ...targetMessage,\n content: [...baseContent, ...imageContent]\n };\n }\n }\n delete requestBody.images;\n }\n\n const response = await fetch(BANDIT_BASE_URL + \"/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \\`Bearer \\${banditKey}\\`\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Bandit chat failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\napp.post(\"/api/bandit/chat\", async (req, res) => {\n req.url = \"/api/bandit/chat/completions\";\n return app._router.handle(req, res);\n});\n\napp.post(\"/api/bandit/completions\", async (req, res) => {\n try {\n const banditKey = requireBanditKey();\n const isStreaming = req.body?.stream === true;\n const { provider, ...cleanBody } = req.body ?? {};\n const requestBody = {\n ...cleanBody,\n model: req.body?.model?.replace(/^bandit:/, \"\") || \"bandit-core-1\"\n };\n\n const response = await fetch(BANDIT_BASE_URL + \"/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \\`Bearer \\${banditKey}\\`\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Bandit completions failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\napp.post(\"/api/bandit/generate\", async (req, res) => {\n try {\n const banditKey = requireBanditKey();\n const prompt = req.body?.prompt || \"\";\n const model = req.body?.model?.replace(/^bandit:/, \"\") || \"bandit-core-1\";\n const isStreaming = req.body?.stream === true;\n\n const chatBody = {\n model,\n messages: [\n { role: \"user\", content: prompt }\n ],\n stream: isStreaming,\n max_tokens: req.body?.max_tokens || 256,\n temperature: req.body?.temperature ?? 0.7\n };\n\n const response = await fetch(BANDIT_BASE_URL + \"/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \\`Bearer \\${banditKey}\\`\n },\n body: JSON.stringify(chatBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Bandit generate failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const data = await response.json();\n const generateResponse = {\n model,\n created_at: new Date().toISOString(),\n response: data.choices?.[0]?.message?.content || \"\",\n done: true,\n context: [],\n total_duration: 0,\n load_duration: 0,\n prompt_eval_count: data.usage?.prompt_tokens || 0,\n prompt_eval_duration: 0,\n eval_count: data.usage?.completion_tokens || 0,\n eval_duration: 0\n };\n res.json(generateResponse);\n }\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\napp.get(\"/api/bandit/models\", async (_req, res) => {\n try {\n const banditKey = requireBanditKey();\n const response = await fetch(BANDIT_BASE_URL + \"/models\", {\n headers: { \"Authorization\": \\`Bearer \\${banditKey}\\` }\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Bandit models failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n res.status(500).json({ error: error instanceof Error ? error.message : String(error) });\n }\n});\n\n// ============================================================================\n// OPENAI ROUTES\n// ============================================================================\n\n// OpenAI Health Check\napp.get(\"/api/openai/health\", async (_req, res) => {\n try {\n const openaiKey = process.env.OPENAI_API_KEY;\n if (!openaiKey) {\n return res.status(503).json({\n status: \"unhealthy\",\n openai_status: false,\n error: \"OpenAI API key not configured\",\n provider: \"openai\"\n });\n }\n\n const response = await fetch(\"https://api.openai.com/v1/models\", {\n headers: { \"Authorization\": \\`Bearer \\${openaiKey}\\` }\n });\n\n const isHealthy = response.ok;\n res.json({\n status: isHealthy ? \"healthy\" : \"unhealthy\",\n openai_status: isHealthy,\n provider: \"openai\"\n });\n } catch (error) {\n res.status(503).json({\n status: \"unhealthy\",\n openai_status: false,\n error: error.message,\n provider: \"openai\"\n });\n }\n});\n\n// OpenAI Chat Completions\napp.post(\"/api/openai/chat/completions\", async (req, res) => {\n try {\n const openaiKey = requireOpenAIKey();\n const isStreaming = req.body?.stream === true;\n\n // Strip the openai: prefix from model name and remove provider field\n const { provider, ...cleanBody } = req.body;\n const requestBody = {\n ...cleanBody,\n model: req.body?.model?.replace(/^openai:/, \"\") || \"gpt-4o\"\n };\n\n const response = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \\`Bearer \\${openaiKey}\\`\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`OpenAI chat failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// OpenAI Chat (alternative route)\napp.post(\"/api/openai/chat\", async (req, res) => {\n // Route to the completions endpoint for compatibility\n req.url = \"/api/openai/chat/completions\";\n return app._router.handle(req, res);\n});\n\n// OpenAI Completions\napp.post(\"/api/openai/completions\", async (req, res) => {\n try {\n const openaiKey = requireOpenAIKey();\n const isStreaming = req.body?.stream === true;\n\n // Strip the openai: prefix from model name and remove provider field\n const { provider, ...cleanBody } = req.body;\n const requestBody = {\n ...cleanBody,\n model: req.body?.model?.replace(/^openai:/, \"\") || \"gpt-3.5-turbo-instruct\"\n };\n\n const response = await fetch(\"https://api.openai.com/v1/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \\`Bearer \\${openaiKey}\\`\n },\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`OpenAI completions failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// OpenAI Generate (converts to chat format for conversation starters)\napp.post(\"/api/openai/generate\", async (req, res) => {\n try {\n const openaiKey = requireOpenAIKey();\n const prompt = req.body?.prompt || \"\";\n const model = req.body?.model?.replace(/^openai:/, \"\") || \"gpt-4o\";\n const isStreaming = req.body?.stream === true;\n\n // Convert generate request to chat format\n const chatBody = {\n model: model,\n messages: [\n {\n role: \"user\",\n content: prompt\n }\n ],\n stream: isStreaming,\n max_tokens: req.body?.max_tokens || 150,\n temperature: req.body?.temperature || 0.7\n };\n\n const response = await fetch(\"https://api.openai.com/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \\`Bearer \\${openaiKey}\\`\n },\n body: JSON.stringify(chatBody)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`OpenAI generate failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const data = await response.json();\n // Convert chat response back to generate format\n const generateResponse = {\n model: model,\n created_at: new Date().toISOString(),\n response: data.choices?.[0]?.message?.content || \"\",\n done: true,\n context: [],\n total_duration: 0,\n load_duration: 0,\n prompt_eval_count: 0,\n prompt_eval_duration: 0,\n eval_count: data.usage?.completion_tokens || 0,\n eval_duration: 0\n };\n res.json(generateResponse);\n }\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// OpenAI Models\napp.get(\"/api/openai/models\", async (_req, res) => {\n try {\n const openaiKey = requireOpenAIKey();\n\n const response = await fetch(\"https://api.openai.com/v1/models\", {\n headers: { \"Authorization\": \\`Bearer \\${openaiKey}\\` }\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`OpenAI models failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// ============================================================================\n// OLLAMA ROUTES\n// ============================================================================\n\n// Ollama Health Check\napp.get(\"/api/ollama/health\", async (_req, res) => {\n try {\n console.log(\\`Ollama health check at: \\${OLLAMA_BASE_URL}/api/tags\\`);\n const response = await fetch(\\`\\${OLLAMA_BASE_URL}/api/tags\\`);\n const isHealthy = response.ok;\n\n res.json({\n status: isHealthy ? \"healthy\" : \"unhealthy\",\n ollama_status: isHealthy,\n provider: \"ollama\",\n url: OLLAMA_BASE_URL\n });\n } catch (error) {\n console.log(\\`Ollama health check error: \\${error.message}\\`);\n res.status(503).json({\n status: \"offline\",\n ollama_status: false,\n error: error.message,\n provider: \"ollama\",\n url: OLLAMA_BASE_URL\n });\n }\n});\n\n// Ollama Chat\napp.post(\"/api/ollama/chat\", async (req, res) => {\n try {\n const response = await fetch(\\`\\${OLLAMA_BASE_URL}/api/chat\\`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(req.body)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Ollama chat failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const isStreaming = req.body?.stream === true;\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// Ollama Generate\napp.post(\"/api/ollama/generate\", async (req, res) => {\n try {\n const response = await fetch(\\`\\${OLLAMA_BASE_URL}/api/generate\\`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(req.body)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Ollama generate failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const isStreaming = req.body?.stream === true;\n if (isStreaming) {\n await handleStreamingResponse(response, res);\n } else {\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n }\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// Ollama Models\napp.get(\"/api/ollama/models\", async (_req, res) => {\n try {\n const response = await fetch(\\`\\${OLLAMA_BASE_URL}/api/tags\\`);\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Ollama models failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// Ollama Embedding\napp.post(\"/api/ollama/embed\", async (req, res) => {\n try {\n const response = await fetch(\\`\\${OLLAMA_BASE_URL}/api/embeddings\\`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(req.body)\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return res.status(response.status).json({\n error: \\`Ollama embed failed: \\${response.status}\\`,\n details: errorText\n });\n }\n\n const text = await response.text();\n res.setHeader('Content-Type', 'application/json');\n res.send(text);\n } catch (error) {\n res.status(500).json({ error: error.message });\n }\n});\n\n// ============================================================================\n// TTS ROUTES (not implemented - placeholder for compatibility)\n// ============================================================================\n\n// TTS Main endpoint\napp.post(\"/api/tts\", async (_req, res) => {\n res.status(501).json({\n error: \"TTS integration not implemented\",\n message: \"Text-to-speech functionality is not available in this quickstart gateway\"\n });\n});\n\n// TTS Stream endpoint\napp.post(\"/api/tts/stream\", async (_req, res) => {\n res.status(501).json({\n error: \"TTS streaming not implemented\", \n message: \"Text-to-speech streaming functionality is not available in this quickstart gateway\"\n });\n});\n\n// TTS Real-time stream endpoint\napp.post(\"/api/tts/stream-realtime\", async (_req, res) => {\n res.status(501).json({\n error: \"TTS real-time streaming not implemented\",\n message: \"Text-to-speech real-time streaming functionality is not available in this quickstart gateway\"\n });\n});\n\n// TTS Models endpoint - returns empty models\napp.get(\"/api/tts/models\", async (_req, res) => {\n res.json({\n models: []\n });\n});\n\n// TTS Available models endpoint - returns empty models with defaults\napp.get(\"/api/tts/available-models\", async (_req, res) => {\n res.json({\n models: [],\n defaultModel: null,\n fallbackModel: null\n });\n});\n\n// ============================================================================\n// MCP (Model Context Protocol) TOOL ROUTES\n// ============================================================================\n\n// MCP Health Check\napp.get(\"/api/mcp/health\", async (_req, res) => {\n res.json({\n status: \"healthy\",\n timestamp: new Date().toISOString(),\n totalTools: 0,\n enabledTools: 0,\n availableTools: [],\n message: \"MCP tools are not implemented in this quickstart gateway\"\n });\n});\n\n// Get available MCP tools\napp.get(\"/api/mcp/tools\", async (_req, res) => {\n res.json([]);\n});\n\n// News endpoint - placeholder implementation\napp.get(\"/api/mcp/news\", async (req, res) => {\n const { topic = \"general\", count = 10, headlines = false } = req.query;\n \n res.status(501).json({\n error: \"News service not implemented\",\n message: \"MCP news functionality is not available in this quickstart gateway\",\n suggestion: \"Implement this endpoint to connect to a news API service\",\n requestedParams: { topic, count, headlines }\n });\n});\n\n// Weather endpoint - placeholder implementation \napp.get(\"/api/mcp/weather\", async (req, res) => {\n const { zip, latitude, longitude } = req.query;\n \n res.status(501).json({\n error: \"Weather service not implemented\", \n message: \"MCP weather functionality is not available in this quickstart gateway\",\n suggestion: \"Implement this endpoint to connect to a weather API service\",\n requestedParams: { zip, latitude, longitude }\n });\n});\n\n// Documentation search endpoint - placeholder implementation\napp.get(\"/api/mcp/docs\", async (req, res) => {\n const { query, framework, count = 10 } = req.query;\n \n if (!query) {\n return res.status(400).json({\n error: \"Query parameter is required\",\n message: \"Please provide a search query\"\n });\n }\n \n res.status(501).json({\n error: \"Documentation search not implemented\",\n message: \"MCP docs functionality is not available in this quickstart gateway\", \n suggestion: \"Implement this endpoint to connect to a documentation search service\",\n requestedParams: { query, framework, count }\n });\n});\n\n// Get supported documentation frameworks\napp.get(\"/api/mcp/docs/frameworks\", async (_req, res) => {\n res.json({\n frameworks: [],\n message: \"Documentation frameworks not configured in this quickstart gateway\"\n });\n});\n\n// Sports scores endpoint - placeholder implementation\napp.get(\"/api/mcp/sports\", async (req, res) => {\n const { league, date } = req.query;\n \n res.status(501).json({\n error: \"Sports service not implemented\",\n message: \"MCP sports functionality is not available in this quickstart gateway\",\n suggestion: \"Implement this endpoint to connect to a sports API service\", \n requestedParams: { league, date }\n });\n});\n\n// Get supported sports leagues\napp.get(\"/api/mcp/sports/leagues\", async (_req, res) => {\n res.json({\n leagues: [],\n message: \"Sports leagues not configured in this quickstart gateway\"\n });\n});\n\n// Image generation endpoint - placeholder implementation\napp.post(\"/api/mcp/generate-image\", async (req, res) => {\n const { prompt, size, quality, style } = req.body;\n \n if (!prompt) {\n return res.status(400).json({\n error: \"Prompt is required\",\n message: \"Please provide a prompt for image generation\"\n });\n }\n \n res.status(501).json({\n success: false,\n error: \"Image generation not implemented\",\n message: \"MCP image generation functionality is not available in this quickstart gateway\",\n suggestion: \"Implement this endpoint to connect to an image generation API service\",\n requestedParams: { prompt, size, quality, style }\n });\n});\n\n// ============================================================================\n// NOT IMPLEMENTED ROUTES (for graceful degradation)\n// ============================================================================\n\napp.all(\"/api/anthropic/*\", (_req, res) => {\n res.status(501).json({\n error: \"Anthropic route not implemented\",\n message: \"Extend the quickstart gateway if you need additional Anthropic endpoints beyond the defaults.\"\n });\n});\n\nconst port = Number(process.env.PORT ?? ${ctx.gatewayPort});\napp.listen(port, () => {\n console.log(\"⚡ Bandit quickstart gateway ready on http://localhost:\" + port);\n console.log(\"📡 Supported providers: Bandit AI, OpenAI, Azure OpenAI, Anthropic, XAI, Ollama\");\n console.log(\"🔗 Provider-specific routes:\");\n console.log(\" • /api/bandit/* - Bandit AI endpoints\");\n console.log(\" • /api/openai/* - OpenAI endpoints\");\n console.log(\" • /api/azure/* - Azure OpenAI endpoints\");\n console.log(\" • /api/anthropic/* - Anthropic endpoints\");\n console.log(\" • /api/xai/* - XAI endpoints\");\n console.log(\" • /api/ollama/* - Ollama endpoints\");\n console.log(\" • /api/health - Overall health check\");\n});\n`;\n\n return ensureTrailingNewline(normalizeLineEndings(gatewaySource));\n};\n\nexport const buildGitignore = (): string =>\n ensureTrailingNewline(\n normalizeLineEndings(\n `node_modules\\n.env\\n.vite\\n.idea\\n.DS_Store\\ncoverage\\ndist\\n`\n )\n );\n\nexport const buildReadme = (ctx: QuickstartTemplateContext): string =>\n ensureTrailingNewline(\n normalizeLineEndings(\n `# ${ctx.projectTitle} — Bandit Quickstart\\n\\nThis project was generated by the Bandit Engine CLI. It ships with a React + Vite frontend that consumes \\`@burtson-labs/bandit-engine\\`, a lightweight Express gateway you can adapt for production, and a Next.js App Router API scaffold in \\`server/next-app/\\`.\\n\\n## 🚀 Next steps\\n- \\`npm install\\`\\n- \\`cp .env.example .env\\`\\n- Fill in your Bandit AI, OpenAI, Azure OpenAI, Anthropic, or xAI credentials (or point \\`OLLAMA_URL\\` at your local server)\\n- \\`npm run dev\\`\\n\\nThe command runs the gateway and the frontend together. Visit http://localhost:${ctx.frontendPort} to see the chat and modal in action.\\n\\n## 🔧 Customizing your assistant\\n- **Branding & personas**: edit \\`public/config.json\\` to tweak logos, colors, and starter models.\\n- **Provider defaults**: update \\`.env\\` to switch providers or change the default upstream model IDs.\\n- **Gateway routes**: open \\`server/gateway.js\\` to add auth, logging, or connect additional providers.\\n\\n## 📦 What’s inside\\n- React + Vite 5 with Material UI theming\\n- Bandit chat surface + modal wired via \\`ChatProvider\\`\\n- Express gateway proxying Bandit AI, OpenAI, Azure OpenAI, Anthropic, XAI, or Ollama to keep API keys server-side\\n- Next.js App Router gateway scaffold in 'server/next-app/' for projects that prefer Next\\n- Friendly defaults you can evolve into your production stack\\n\\nNeed more? Run \\`npx @burtson-labs/bandit-engine create --help\\` to explore additional options.\\n`\n )\n );\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAmBA,IAAAA,oBAAiB;AACjB,uBAAwB;;;ACpBxB;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,QAAU;AAAA,EACV,OAAS;AAAA,EACT,KAAO;AAAA,IACL,QAAU;AAAA,EACZ;AAAA,EACA,OAAS;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,YAAc;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,UAAY;AAAA,EACZ,QAAU;AAAA,EACV,aAAe;AAAA,IACb;AAAA,MACE,MAAQ;AAAA,MACR,OAAS;AAAA,IACX;AAAA,EACF;AAAA,EACA,UAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,KAAO;AAAA,IACP,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,uBAAuB;AAAA,EACzB;AAAA,EACA,WAAa;AAAA,IACX,UAAU;AAAA,EACZ;AAAA,EACA,cAAgB;AAAA,IACd,kBAAkB;AAAA,IAClB,mBAAmB;AAAA,IACnB,uBAAuB;AAAA,IACvB,iBAAiB;AAAA,IACjB,yBAAyB;AAAA,IACzB,OAAS;AAAA,IACT,WAAa;AAAA,IACb,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,KAAO;AAAA,IACP,UAAY;AAAA,IACZ,SAAW;AAAA,IACX,cAAc;AAAA,IACd,SAAW;AAAA,IACX,OAAS;AAAA,IACT,aAAa;AAAA,IACb,kBAAkB;AAAA,IAClB,oBAAoB;AAAA,IACpB,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,cAAc;AAAA,IACd,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,SAAW;AAAA,EACb;AAAA,EACA,iBAAmB;AAAA,IACjB,6BAA6B;AAAA,IAC7B,0BAA0B;AAAA,IAC1B,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,kBAAkB;AAAA,IAClB,gBAAgB;AAAA,IAChB,oBAAoB;AAAA,IACpB,eAAe;AAAA,IACf,wBAAwB;AAAA,IACxB,QAAU;AAAA,IACV,uBAAuB;AAAA,IACvB,OAAS;AAAA,IACT,MAAQ;AAAA,IACR,SAAW;AAAA,IACX,YAAc;AAAA,IACd,QAAU;AAAA,EACZ;AAAA,EACA,kBAAoB;AAAA,IAClB,iBAAiB;AAAA,IACjB,OAAS;AAAA,IACT,SAAW;AAAA,EACb;AAAA,EACA,SAAW;AAAA,IACT,KAAK;AAAA,MACH,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,WAAW;AAAA,MACT,OAAS;AAAA,MACT,SAAW;AAAA,IACb;AAAA,EACF;AACF;;;AC9FA,IAAAC,oBAAiB;AACjB,sBAAe;AACf,qBAAsC;;;ACFtC,uBAAiB;AAEV,IAAM,cAAc,CAAC,UAA0B;AACpD,SAAO,MACJ,QAAQ,sBAAsB,OAAO,EACrC,QAAQ,aAAa,GAAG,EACxB,QAAQ,mBAAmB,EAAE,EAC7B,QAAQ,UAAU,GAAG,EACrB,QAAQ,YAAY,EAAE,EACtB,YAAY;AACjB;AAEO,IAAM,cAAc,CAAC,UAA0B;AACpD,QAAM,UAAU,MACb,QAAQ,WAAW,GAAG,EACtB,QAAQ,QAAQ,GAAG,EACnB,KAAK;AAER,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,SAAO,QAAQ,QAAQ,SAAS,CAAC,SAAS,KAAK,YAAY,CAAC;AAC9D;AAEO,IAAM,aAAa,CAAC,UACzB,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA;AA2BnC,IAAM,kBAAkB,oBAAI,IAAI,CAAC,UAAU,SAAS,gBAAgB,eAAe,aAAa,OAAO,QAAQ,CAAC;AAEzG,IAAM,0BAA0B,CAAC,UAA0B;AAChE,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAQ,SAAS,GAAG,GAAG;AAC1B,WAAO,QAAQ,YAAY;AAAA,EAC7B;AAEA,QAAM,WAAW,QAAQ,MAAM,OAAO,EAAE,OAAO,OAAO;AACtD,MAAI,SAAS,SAAS,GAAG;AACvB,WAAO,QAAQ,YAAY;AAAA,EAC7B;AAEA,QAAM,CAAC,mBAAmB,IAAI,IAAI;AAClC,QAAM,WAAW,kBAAkB,YAAY;AAC/C,QAAM,YAAY,KACf,KAAK,EACL,QAAQ,qBAAqB,GAAG,EAChC,QAAQ,OAAO,GAAG,EAClB,YAAY;AAEf,MAAI,gBAAgB,IAAI,QAAQ,GAAG;AACjC,QAAI,aAAa,kBAAkB,aAAa,eAAe;AAC7D,aAAO,SAAS,SAAS;AAAA,IAC3B;AACA,QAAI,aAAa,UAAU;AACzB,aAAO;AAAA,IACT;AACA,WAAO,GAAG,QAAQ,IAAI,SAAS;AAAA,EACjC;AAEA,SAAO,CAAC,mBAAmB,IAAI,EAC5B,OAAO,OAAO,EACd,KAAK,GAAG,EACR,QAAQ,qBAAqB,GAAG,EAChC,QAAQ,OAAO,GAAG,EAClB,YAAY;AACjB;AAEO,IAAM,uBAAuB,CAAC,YACnC,QAAQ,QAAQ,SAAS,IAAI;AAExB,IAAM,wBAAwB,CAAC,YACpC,QAAQ,SAAS,IAAI,IAAI,UAAU,GAAG,OAAO;AAAA;;;ACvE/C,IAAM,QAAQ;AAEP,IAAM,mBAAmB,CAAC,QAC/B,WAAW;AAAA,EACT,MAAM,IAAI;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AAAA,IACP,KAAK;AAAA,IACL,WAAW;AAAA,IACX,eAAe;AAAA,IACf,OAAO;AAAA,IACP,SAAS;AAAA,EACX;AAAA,EACA,cAAc;AAAA,IACZ,+BAA+B,IAAI,IAAI,aAAa;AAAA,IACpD,kBAAkB;AAAA,IAClB,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,yBAAyB;AAAA,IACzB,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,WAAW;AAAA,IACX,SAAS;AAAA,IACT,aAAa;AAAA,IACb,oBAAoB;AAAA,IACpB,WAAW;AAAA,EACb;AAAA,EACA,iBAAiB;AAAA,IACf,kBAAkB;AAAA,IAClB,eAAe;AAAA,IACf,gBAAgB;AAAA,IAChB,oBAAoB;AAAA,IACpB,wBAAwB;AAAA,IACxB,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,EACV;AACF,CAAC;AAEI,IAAM,kBAAkB,CAAC,QAA2C;AACzE,QAAM,QAAkB;AAAA,IACtB;AAAA,IACA,iBAAiB,IAAI,YAAY;AAAA,IACjC,oBAAoB,IAAI,iBAAiB;AAAA,IACzC,sBAAsB,IAAI,cAAc;AAAA,IACxC,uBAAuB,IAAI,mBAAmB,EAAE;AAAA,IAChD,yBAAyB,IAAI,eAAe;AAAA,IAC5C,sBAAsB,IAAI,YAAY;AAAA,IACtC;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,UAAQ,IAAI,iBAAiB;AAAA,IAC3B,KAAK;AACH,YAAM,KAAK,iBAAiB;AAC5B;AAAA,IACF,KAAK;AACH,YAAM,KAAK,8DAA8D;AACzE,YAAM,KAAK,uBAAuB;AAClC,YAAM,KAAK,6CAA6C;AACxD,YAAM,KAAK,qCAAqC;AAChD,YAAM,KAAK,2DAA2D;AACtE,YAAM,KAAK,2DAA2D;AACtE;AAAA,IACF,KAAK;AACH,YAAM,KAAK,oBAAoB;AAC/B,YAAM,KAAK,8CAA8C;AACzD,YAAM,KAAK,kCAAkC;AAC7C,YAAM,KAAK,2BAA2B;AACtC;AAAA,IACF,KAAK;AACH,YAAM,KAAK,cAAc;AACzB,YAAM,KAAK,kCAAkC;AAC7C;AAAA,IACF,KAAK;AACH,YAAM,KAAK,iBAAiB;AAC5B,YAAM,KAAK,wCAAwC;AACnD;AAAA,IACF,KAAK;AAAA,IACL;AACE,YAAM,KAAK,mCAAmC;AAC9C;AAAA,EACJ;AAEA,QAAM,KAAK,QAAQ,IAAI,WAAW,EAAE;AACpC,QAAM;AAAA,IACJ;AAAA,EACF;AAEA,SAAO,sBAAsB,qBAAqB,MAAM,KAAK,IAAI,CAAC,CAAC;AACrE;AAEO,IAAM,gBAAgB,MAC3B,WAAW;AAAA,EACT,iBAAiB;AAAA,IACf,QAAQ;AAAA,IACR,yBAAyB;AAAA,IACzB,KAAK,CAAC,OAAO,gBAAgB,QAAQ;AAAA,IACrC,SAAS;AAAA,IACT,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB,8BAA8B;AAAA,IAC9B,QAAQ;AAAA,IACR,kCAAkC;AAAA,IAClC,QAAQ;AAAA,IACR,kBAAkB;AAAA,IAClB,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,QAAQ;AAAA,IACR,KAAK;AAAA,EACP;AAAA,EACA,SAAS,CAAC,KAAK;AACjB,CAAC;AAEI,IAAM,cAAc,MACzB;AAAA,EACE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACF;AAEK,IAAM,kBAAkB,CAAC,QAC9B;AAAA,EACE;AAAA,IACE;AAAA;AAAA;AAAA;AAAA;AAAA,+DAA0P,IAAI,YAAY;AAAA,4DAAiE,IAAI,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAE7V;AACF;AAEK,IAAM,eAAe,MAC1B;AAAA,EACE;AAAA,IACE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EACF;AACF;AAEK,IAAM,gBAAgB,MAC3B;AAAA,EACE;AAAA,IACE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EACF;AACF;AAEK,IAAM,iBAAiB,MAC5B;AAAA,EACE;AAAA,IACE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EACF;AACF;AAEK,IAAM,eAAe,MAC1B;AAAA,EACE;AAAA,IACE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EACF;AACF;AAEK,IAAM,cAAc,CAAC,QAA2C;AACrE,QAAM,qBAAqB;AAC3B,QAAM,mBAAmB;AAEzB,QAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+DAsB4C,IAAI,iBAAiB;AAAA,gEACpB,IAAI,cAAc;AAAA,iEACjB,IAAI,kBAAkB,GAAG,KAAK,GAAG,IAAI,eAAe,GAAG,KAAK,KAAK,WAAW;AAAA,8DAC/E,IAAI,YAAY;AAAA,8DAChB,IAAI,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBA8EzD,IAAI,0BAA0B,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6SxD,QAAM,eAAe,SAAS,QAAQ,wBAAwB,kBAAkB;AAChF,QAAM,mBAAmB,aAAa,QAAQ,sBAAsB,gBAAgB;AAEpF,SAAO,sBAAsB,qBAAqB,gBAAgB,CAAC;AACrE;AAEO,IAAM,sBAAsB,CAAC,QAClC,WAAW;AAAA,EACT,UAAU;AAAA,IACR,YAAY,IAAI,gBAAgB,OAAO,IAAI;AAAA,IAC3C,cAAc,IAAI;AAAA,IAClB,OAAO;AAAA,IACP,oBAAoB,IAAI,gBAAgB,OAAO,IAAI;AAAA,EACrD;AAAA,EACA,eAAe,CAAC;AAClB,CAAC;AAEH,IAAM,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAogBjC,IAAM,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+LnC,IAAM,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BnC,IAAM,+BAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsB9B,IAAM,qBAAqB,CAAC,QAA2C;AAC5E,QAAM,gBAAgB,IAAI,kBAAkB,IAAI,IAAI,eAAe,MAAM;AACzE,SAAO;AAAA,IACL;AAAA,MACE,yBACG,QAAQ,yBAAyB,IAAI,eAAe,EACpD,QAAQ,sBAAsB,IAAI,cAAc,EAChD,QAAQ,uBAAuB,aAAa;AAAA,IACjD;AAAA,EACF;AACF;AAEO,IAAM,uBAAuB,MAClC,sBAAsB,qBAAqB,0BAA0B,CAAC;AAEjE,IAAM,uBAAuB,CAAC,QAA2C;AAC9E,QAAM,mBAAmB,KAAK,UAAU,IAAI,eAAe,MAAM,CAAC;AAClE,SAAO;AAAA,IACL;AAAA,MACE,2BAA2B,QAAQ,sBAAsB,gBAAgB;AAAA,IAC3E;AAAA,EACF;AACF;AAEO,IAAM,yBAAyB,MACpC,sBAAsB,qBAAqB,4BAA4B,CAAC;AAEnE,IAAM,qBAAqB,CAAC,QAA2C;AAC5E,QAAM,mBAAmB,KAAK,UAAU,IAAI,eAAe,MAAM,CAAC;AAElE,QAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAYI,IAAI,eAAe;AAAA,8BACjB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCA+lBX,IAAI,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCA6GlB,IAAI,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":["import_node_path","import_node_path","path","fs","prompts","path"]}
|