@nospt/backstage-plugin-librechat-backend 0.1.0
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/README.md +34 -0
- package/config.d.ts +34 -0
- package/dist/index.cjs.js +10 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/plugin.cjs.js +30 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/router.cjs.js +157 -0
- package/dist/router.cjs.js.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @nospt/backstage-plugin-librechat-backend
|
|
2
|
+
|
|
3
|
+
> Backend plugin that proxies chat requests from Backstage to a [LibreChat](https://www.librechat.ai/) instance, keeping API keys server-side.
|
|
4
|
+
|
|
5
|
+
This is the **backend** half of the LibreChat plugin. It exposes an HTTP route that the frontend plugin calls, forwards requests to the LibreChat Agents API, and streams the response back. Your default API key and agent ID stay on the server and are never exposed to the browser.
|
|
6
|
+
|
|
7
|
+
It must be paired with the frontend plugin, [`@nospt/backstage-plugin-librechat`](../librechat/README.md), which renders the chat bubble UI **and holds the full configuration reference**.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Install the package in your Backstage backend package:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
yarn --cwd packages/backend add @nospt/backstage-plugin-librechat-backend
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
Add the plugin to your backend:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// packages/backend/src/index.ts
|
|
23
|
+
import {createBackend} from "@backstage/backend-defaults";
|
|
24
|
+
|
|
25
|
+
const backend = createBackend();
|
|
26
|
+
|
|
27
|
+
// ...other plugins
|
|
28
|
+
backend.add(import("@nospt/backstage-plugin-librechat-backend"));
|
|
29
|
+
|
|
30
|
+
backend.start();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
> [!NOTE]
|
|
34
|
+
> Configuration, endpoints, and architecture are documented in the [frontend plugin README](../librechat/README.md).
|
package/config.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration schema for the LibreChat backend plugin.
|
|
3
|
+
*
|
|
4
|
+
* @param librechat - LibreChat plugin configuration
|
|
5
|
+
*/
|
|
6
|
+
export interface Config {
|
|
7
|
+
librechat: {
|
|
8
|
+
/**
|
|
9
|
+
* Whether the chat bubble is enabled by default.
|
|
10
|
+
* When true, the bubble shows without needing the feature flag.
|
|
11
|
+
* @visibility frontend
|
|
12
|
+
*/
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Base URL of the LibreChat instance.
|
|
17
|
+
* @visibility backend
|
|
18
|
+
*/
|
|
19
|
+
baseUrl: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default API key for authenticating with LibreChat.
|
|
23
|
+
* Users can override this in the Backstage UI settings.
|
|
24
|
+
* @visibility secret
|
|
25
|
+
*/
|
|
26
|
+
apiKey?: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Agent ID to use for chat completions.
|
|
30
|
+
* @visibility backend
|
|
31
|
+
*/
|
|
32
|
+
agentId: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The LibreChat backend plugin.
|
|
5
|
+
*
|
|
6
|
+
* Proxies streaming chat requests to a LibreChat instance,
|
|
7
|
+
* keeping API keys server-side.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
declare const libreChatPlugin: _backstage_backend_plugin_api.BackendFeature;
|
|
12
|
+
|
|
13
|
+
export { libreChatPlugin as default };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var router = require('./router.cjs.js');
|
|
5
|
+
|
|
6
|
+
const libreChatPlugin = backendPluginApi.createBackendPlugin({
|
|
7
|
+
pluginId: "librechat",
|
|
8
|
+
register(env) {
|
|
9
|
+
env.registerInit({
|
|
10
|
+
deps: {
|
|
11
|
+
logger: backendPluginApi.coreServices.logger,
|
|
12
|
+
config: backendPluginApi.coreServices.rootConfig,
|
|
13
|
+
httpRouter: backendPluginApi.coreServices.httpRouter
|
|
14
|
+
},
|
|
15
|
+
async init({ logger, config, httpRouter }) {
|
|
16
|
+
const baseUrl = config.getString("librechat.baseUrl");
|
|
17
|
+
logger.info(`LibreChat plugin initialized, proxying to ${baseUrl}`);
|
|
18
|
+
const router$1 = router.createRouter({ logger, config });
|
|
19
|
+
httpRouter.use(router$1);
|
|
20
|
+
httpRouter.addAuthPolicy({
|
|
21
|
+
path: "/",
|
|
22
|
+
allow: "unauthenticated"
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
exports.libreChatPlugin = libreChatPlugin;
|
|
30
|
+
//# sourceMappingURL=plugin.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["import {createBackendPlugin, coreServices} from \"@backstage/backend-plugin-api\";\nimport {createRouter} from \"./router\";\n\n/**\n * The LibreChat backend plugin.\n *\n * Proxies streaming chat requests to a LibreChat instance,\n * keeping API keys server-side.\n *\n * @public\n */\nexport const libreChatPlugin = createBackendPlugin({\n pluginId: \"librechat\",\n register(env) {\n env.registerInit({\n deps: {\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n httpRouter: coreServices.httpRouter,\n },\n async init({logger, config, httpRouter}) {\n const baseUrl = config.getString(\"librechat.baseUrl\");\n logger.info(`LibreChat plugin initialized, proxying to ${baseUrl}`);\n\n const router = createRouter({logger, config});\n httpRouter.use(router);\n httpRouter.addAuthPolicy({\n path: \"/\",\n allow: \"unauthenticated\",\n });\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","router","createRouter"],"mappings":";;;;;AAWO,MAAM,kBAAkBA,oCAAA,CAAoB;AAAA,EACjD,QAAA,EAAU,WAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,QAAQC,6BAAA,CAAa,MAAA;AAAA,QACrB,QAAQA,6BAAA,CAAa,UAAA;AAAA,QACrB,YAAYA,6BAAA,CAAa;AAAA,OAC3B;AAAA,MACA,MAAM,IAAA,CAAK,EAAC,MAAA,EAAQ,MAAA,EAAQ,YAAU,EAAG;AACvC,QAAA,MAAM,OAAA,GAAU,MAAA,CAAO,SAAA,CAAU,mBAAmB,CAAA;AACpD,QAAA,MAAA,CAAO,IAAA,CAAK,CAAA,0CAAA,EAA6C,OAAO,CAAA,CAAE,CAAA;AAElE,QAAA,MAAMC,QAAA,GAASC,mBAAA,CAAa,EAAC,MAAA,EAAQ,QAAO,CAAA;AAC5C,QAAA,UAAA,CAAW,IAAID,QAAM,CAAA;AACrB,QAAA,UAAA,CAAW,aAAA,CAAc;AAAA,UACvB,IAAA,EAAM,GAAA;AAAA,UACN,KAAA,EAAO;AAAA,SACR,CAAA;AAAA,MACH;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var express = require('express');
|
|
4
|
+
var Router = require('express-promise-router');
|
|
5
|
+
var fetch = require('node-fetch');
|
|
6
|
+
|
|
7
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var express__default = /*#__PURE__*/_interopDefaultCompat(express);
|
|
10
|
+
var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
11
|
+
var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
|
|
12
|
+
|
|
13
|
+
const MAX_MESSAGE_LENGTH = 32e3;
|
|
14
|
+
const ALLOWED_ROLES = /* @__PURE__ */ new Set(["user", "assistant", "system"]);
|
|
15
|
+
function validateMessages(messages) {
|
|
16
|
+
if (!Array.isArray(messages)) return false;
|
|
17
|
+
return messages.every(
|
|
18
|
+
(m) => typeof m === "object" && m !== null && typeof m.role === "string" && ALLOWED_ROLES.has(m.role) && typeof m.content === "string" && m.content.length > 0 && m.content.length <= MAX_MESSAGE_LENGTH
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
function sanitizeAgentId(agentId) {
|
|
22
|
+
return /^[a-zA-Z0-9_-]+$/.test(agentId);
|
|
23
|
+
}
|
|
24
|
+
function createRouter(options) {
|
|
25
|
+
const { logger, config } = options;
|
|
26
|
+
const router = Router__default.default();
|
|
27
|
+
router.use(express__default.default.json());
|
|
28
|
+
router.post("/chat", async (req, res) => {
|
|
29
|
+
const baseUrl = config.getString("librechat.baseUrl");
|
|
30
|
+
const defaultApiKey = config.getOptionalString("librechat.apiKey");
|
|
31
|
+
const agentId = config.getString("librechat.agentId");
|
|
32
|
+
const apiKey = req.headers["x-librechat-api-key"] || defaultApiKey;
|
|
33
|
+
if (!apiKey) {
|
|
34
|
+
res.status(400).json({
|
|
35
|
+
error: "No API key configured. Set librechat.apiKey in app-config.yaml or provide one in Settings."
|
|
36
|
+
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!sanitizeAgentId(agentId)) {
|
|
40
|
+
res.status(400).json({ error: "Invalid agent ID format in configuration." });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const { messages } = req.body;
|
|
44
|
+
if (!validateMessages(messages)) {
|
|
45
|
+
res.status(400).json({
|
|
46
|
+
error: "Invalid messages format. Expected array of {role, content} objects."
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const targetUrl = `${baseUrl.replace(/\/+$/, "")}/api/agents/v1/chat/completions`;
|
|
51
|
+
logger.debug(`Proxying chat to ${targetUrl} with agent ${agentId}`);
|
|
52
|
+
try {
|
|
53
|
+
const upstream = await fetch__default.default(targetUrl, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: {
|
|
56
|
+
Authorization: `Bearer ${apiKey}`,
|
|
57
|
+
"Content-Type": "application/json"
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
model: agentId,
|
|
61
|
+
messages,
|
|
62
|
+
stream: true
|
|
63
|
+
})
|
|
64
|
+
});
|
|
65
|
+
if (!upstream.ok) {
|
|
66
|
+
const errorText = await upstream.text();
|
|
67
|
+
logger.error(
|
|
68
|
+
`LibreChat upstream error ${upstream.status}: ${errorText}`
|
|
69
|
+
);
|
|
70
|
+
res.status(upstream.status).json({
|
|
71
|
+
error: `LibreChat returned ${upstream.status}`,
|
|
72
|
+
details: errorText
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
77
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
78
|
+
res.setHeader("Connection", "keep-alive");
|
|
79
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
80
|
+
if (upstream.body) {
|
|
81
|
+
upstream.body.on("data", (chunk) => {
|
|
82
|
+
res.write(chunk);
|
|
83
|
+
});
|
|
84
|
+
upstream.body.on("end", () => {
|
|
85
|
+
res.end();
|
|
86
|
+
});
|
|
87
|
+
upstream.body.on("error", (err) => {
|
|
88
|
+
logger.error(`Stream error: ${err.message}`);
|
|
89
|
+
res.end();
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
res.status(502).json({ error: "No response body from LibreChat" });
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
96
|
+
logger.error(`Failed to proxy to LibreChat: ${message}`);
|
|
97
|
+
res.status(502).json({ error: "Failed to connect to LibreChat", details: message });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
router.post("/check", async (req, res) => {
|
|
101
|
+
const baseUrl = config.getString("librechat.baseUrl");
|
|
102
|
+
const agentId = config.getString("librechat.agentId");
|
|
103
|
+
const defaultApiKey = config.getOptionalString("librechat.apiKey");
|
|
104
|
+
const apiKey = req.headers["x-librechat-api-key"] || defaultApiKey;
|
|
105
|
+
if (!apiKey) {
|
|
106
|
+
res.status(400).json({
|
|
107
|
+
error: "No API key provided. Enter your API key and try again."
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const targetUrl = `${baseUrl.replace(/\/+$/, "")}/api/agents/v1/chat/completions`;
|
|
112
|
+
logger.debug(`Checking API key via ${targetUrl}`);
|
|
113
|
+
try {
|
|
114
|
+
const upstream = await fetch__default.default(targetUrl, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: {
|
|
117
|
+
Authorization: `Bearer ${apiKey}`,
|
|
118
|
+
"Content-Type": "application/json"
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
model: agentId,
|
|
122
|
+
messages: [
|
|
123
|
+
{ role: "user", content: "Say hello in one short sentence." }
|
|
124
|
+
],
|
|
125
|
+
stream: false
|
|
126
|
+
})
|
|
127
|
+
});
|
|
128
|
+
if (!upstream.ok) {
|
|
129
|
+
const errorText = await upstream.text();
|
|
130
|
+
logger.error(`LibreChat check error ${upstream.status}: ${errorText}`);
|
|
131
|
+
if (upstream.status === 401 || upstream.status === 403) {
|
|
132
|
+
res.status(401).json({ error: "Invalid API key" });
|
|
133
|
+
} else {
|
|
134
|
+
res.status(upstream.status).json({
|
|
135
|
+
error: `LibreChat returned ${upstream.status}`,
|
|
136
|
+
details: errorText
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const data = await upstream.json();
|
|
142
|
+
const reply = data?.choices?.[0]?.message?.content ?? "";
|
|
143
|
+
res.json({ ok: true, reply });
|
|
144
|
+
} catch (err) {
|
|
145
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
146
|
+
logger.error(`Failed to check API key: ${message}`);
|
|
147
|
+
res.status(502).json({ error: "Failed to connect to LibreChat", details: message });
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
router.get("/health", (_req, res) => {
|
|
151
|
+
res.json({ status: "ok" });
|
|
152
|
+
});
|
|
153
|
+
return router;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
exports.createRouter = createRouter;
|
|
157
|
+
//# sourceMappingURL=router.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.cjs.js","sources":["../src/router.ts"],"sourcesContent":["import {LoggerService} from \"@backstage/backend-plugin-api\";\nimport {Config} from \"@backstage/config\";\nimport express from \"express\";\nimport Router from \"express-promise-router\";\nimport fetch from \"node-fetch\";\n\n/** @internal */\nexport interface RouterOptions {\n logger: LoggerService;\n config: Config;\n}\n\n/** Maximum allowed message content length (characters). */\nconst MAX_MESSAGE_LENGTH = 32_000;\n\n/** Allowed roles for chat messages. */\nconst ALLOWED_ROLES = new Set([\"user\", \"assistant\", \"system\"]);\n\ninterface ChatMessage {\n role: string;\n content: string;\n}\n\nfunction validateMessages(messages: unknown): messages is ChatMessage[] {\n if (!Array.isArray(messages)) return false;\n return messages.every(\n (m) =>\n typeof m === \"object\" &&\n m !== null &&\n typeof m.role === \"string\" &&\n ALLOWED_ROLES.has(m.role) &&\n typeof m.content === \"string\" &&\n m.content.length > 0 &&\n m.content.length <= MAX_MESSAGE_LENGTH,\n );\n}\n\nfunction sanitizeAgentId(agentId: string): boolean {\n // Agent IDs should be alphanumeric with underscores/hyphens\n return /^[a-zA-Z0-9_-]+$/.test(agentId);\n}\n\n/** @internal */\nexport function createRouter(options: RouterOptions): express.Router {\n const {logger, config} = options;\n const router = Router();\n\n router.use(express.json());\n\n /**\n * POST /chat\n *\n * Proxies a chat completion request to LibreChat's Agents API.\n * Streams the SSE response back to the client.\n *\n * Body: { messages: Array<{ role: string, content: string }> }\n * Headers (optional overrides):\n * x-librechat-api-key — Override the default API key\n * x-librechat-agent-id — Override the default agent ID\n */\n router.post(\"/chat\", async (req, res) => {\n const baseUrl = config.getString(\"librechat.baseUrl\");\n const defaultApiKey = config.getOptionalString(\"librechat.apiKey\");\n const agentId = config.getString(\"librechat.agentId\");\n\n // Resolve API key: header override > config default\n const apiKey =\n (req.headers[\"x-librechat-api-key\"] as string) || defaultApiKey;\n if (!apiKey) {\n res.status(400).json({\n error:\n \"No API key configured. Set librechat.apiKey in app-config.yaml or provide one in Settings.\",\n });\n return;\n }\n\n if (!sanitizeAgentId(agentId)) {\n res\n .status(400)\n .json({error: \"Invalid agent ID format in configuration.\"});\n return;\n }\n\n // Validate messages\n const {messages} = req.body;\n if (!validateMessages(messages)) {\n res.status(400).json({\n error:\n \"Invalid messages format. Expected array of {role, content} objects.\",\n });\n return;\n }\n\n const targetUrl = `${baseUrl.replace(/\\/+$/, \"\")}/api/agents/v1/chat/completions`;\n\n logger.debug(`Proxying chat to ${targetUrl} with agent ${agentId}`);\n\n try {\n const upstream = await fetch(targetUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model: agentId,\n messages,\n stream: true,\n }),\n });\n\n if (!upstream.ok) {\n const errorText = await upstream.text();\n logger.error(\n `LibreChat upstream error ${upstream.status}: ${errorText}`,\n );\n res.status(upstream.status).json({\n error: `LibreChat returned ${upstream.status}`,\n details: errorText,\n });\n return;\n }\n\n // Stream the SSE response back to the client\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n res.setHeader(\"X-Accel-Buffering\", \"no\");\n\n if (upstream.body) {\n upstream.body.on(\"data\", (chunk: Buffer) => {\n res.write(chunk);\n });\n\n upstream.body.on(\"end\", () => {\n res.end();\n });\n\n upstream.body.on(\"error\", (err: Error) => {\n logger.error(`Stream error: ${err.message}`);\n res.end();\n });\n } else {\n res.status(502).json({error: \"No response body from LibreChat\"});\n }\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : \"Unknown error\";\n logger.error(`Failed to proxy to LibreChat: ${message}`);\n res\n .status(502)\n .json({error: \"Failed to connect to LibreChat\", details: message});\n }\n });\n\n /**\n * GET /agents\n *\n * POST /check\n *\n * Validates that the provided API key works by sending a short test\n * message to LibreChat. Returns the assistant reply as plain text.\n * Headers (optional overrides):\n * x-librechat-api-key — Override the default API key\n */\n router.post(\"/check\", async (req, res) => {\n const baseUrl = config.getString(\"librechat.baseUrl\");\n const agentId = config.getString(\"librechat.agentId\");\n const defaultApiKey = config.getOptionalString(\"librechat.apiKey\");\n\n const apiKey =\n (req.headers[\"x-librechat-api-key\"] as string) || defaultApiKey;\n if (!apiKey) {\n res.status(400).json({\n error: \"No API key provided. Enter your API key and try again.\",\n });\n return;\n }\n\n const targetUrl = `${baseUrl.replace(/\\/+$/, \"\")}/api/agents/v1/chat/completions`;\n logger.debug(`Checking API key via ${targetUrl}`);\n\n try {\n const upstream = await fetch(targetUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model: agentId,\n messages: [\n {role: \"user\", content: \"Say hello in one short sentence.\"},\n ],\n stream: false,\n }),\n });\n\n if (!upstream.ok) {\n const errorText = await upstream.text();\n logger.error(`LibreChat check error ${upstream.status}: ${errorText}`);\n if (upstream.status === 401 || upstream.status === 403) {\n res.status(401).json({error: \"Invalid API key\"});\n } else {\n res.status(upstream.status).json({\n error: `LibreChat returned ${upstream.status}`,\n details: errorText,\n });\n }\n return;\n }\n\n const data = (await upstream.json()) as {\n choices?: Array<{message?: {content?: string}}>;\n };\n const reply = data?.choices?.[0]?.message?.content ?? \"\";\n res.json({ok: true, reply});\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : \"Unknown error\";\n logger.error(`Failed to check API key: ${message}`);\n res\n .status(502)\n .json({error: \"Failed to connect to LibreChat\", details: message});\n }\n });\n\n /**\n * GET /health\n * Simple health check endpoint.\n */\n router.get(\"/health\", (_req, res) => {\n res.json({status: \"ok\"});\n });\n\n return router;\n}\n"],"names":["Router","express","fetch"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nospt/backstage-plugin-librechat-backend",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Backend plugin for Backstage that proxies requests to LibreChat Agents API",
|
|
5
|
+
"main": "dist/index.cjs.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"backstage": {
|
|
12
|
+
"role": "backend-plugin",
|
|
13
|
+
"pluginId": "librechat",
|
|
14
|
+
"pluginPackages": [
|
|
15
|
+
"@nospt/backstage-plugin-librechat",
|
|
16
|
+
"@nospt/backstage-plugin-librechat-backend"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"start": "backstage-cli package start",
|
|
21
|
+
"build": "backstage-cli package build",
|
|
22
|
+
"tsc": "tsc",
|
|
23
|
+
"lint": "backstage-cli package lint",
|
|
24
|
+
"clean": "backstage-cli package clean",
|
|
25
|
+
"test": "backstage-cli package test"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@backstage/backend-plugin-api": "^1.8.0",
|
|
29
|
+
"@backstage/config": "^1.3.0",
|
|
30
|
+
"express": "^4.21.0",
|
|
31
|
+
"express-promise-router": "^4.1.1",
|
|
32
|
+
"node-fetch": "^2.7.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@backstage/backend-defaults": "^0.16.0",
|
|
36
|
+
"@backstage/cli": "^0.36.0",
|
|
37
|
+
"@types/express": "^4.17.21",
|
|
38
|
+
"@types/node-fetch": "^2.6.11",
|
|
39
|
+
"jest": "^29.0.0 || ^30.0.0",
|
|
40
|
+
"typescript": "~5.4.0"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"config.d.ts"
|
|
45
|
+
],
|
|
46
|
+
"configSchema": "config.d.ts"
|
|
47
|
+
}
|