@acarmisc/backstage-plugin-litellm 0.13.0 → 0.14.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/dist/index.cjs.js +78 -17
- package/dist/index.cjs.js.map +3 -3
- package/dist/index.esm.js +78 -17
- package/dist/index.esm.js.map +2 -2
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -1353,10 +1353,18 @@ var init_format = __esm({
|
|
|
1353
1353
|
function trimSlash(url) {
|
|
1354
1354
|
return url.replace(/\/+$/, "");
|
|
1355
1355
|
}
|
|
1356
|
+
function isModelAllowedByTeam(model, teamModels) {
|
|
1357
|
+
if (!teamModels || teamModels.length === 0) return true;
|
|
1358
|
+
if (teamModels.includes(ALL_PROXY_MODELS)) return true;
|
|
1359
|
+
if (teamModels.includes(model.model_name)) return true;
|
|
1360
|
+
return !!model.access_groups?.some((group) => teamModels.includes(group));
|
|
1361
|
+
}
|
|
1356
1362
|
function buildSnippets(baseUrl, key, model) {
|
|
1357
1363
|
const base = trimSlash(baseUrl);
|
|
1364
|
+
const apiBase = `${base}/v1`;
|
|
1358
1365
|
return {
|
|
1359
|
-
|
|
1366
|
+
publicEndpoint: apiBase,
|
|
1367
|
+
curl: `curl ${apiBase}/chat/completions \\
|
|
1360
1368
|
-H "Authorization: Bearer ${key}" \\
|
|
1361
1369
|
-H "Content-Type: application/json" \\
|
|
1362
1370
|
-d '{
|
|
@@ -1367,14 +1375,40 @@ function buildSnippets(baseUrl, key, model) {
|
|
|
1367
1375
|
|
|
1368
1376
|
client = OpenAI(
|
|
1369
1377
|
api_key="${key}",
|
|
1370
|
-
base_url="${
|
|
1378
|
+
base_url="${apiBase}",
|
|
1371
1379
|
)
|
|
1372
1380
|
|
|
1373
1381
|
response = client.chat.completions.create(
|
|
1374
1382
|
model="${model}",
|
|
1375
1383
|
messages=[{ "role": "user", "content": "Hello!" }],
|
|
1376
1384
|
)
|
|
1377
|
-
print(response.choices[0].message.content)
|
|
1385
|
+
print(response.choices[0].message.content)`,
|
|
1386
|
+
opencode: `{
|
|
1387
|
+
"$schema": "https://opencode.ai/config.json",
|
|
1388
|
+
"provider": {
|
|
1389
|
+
"litellm": {
|
|
1390
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
1391
|
+
"name": "LiteLLM",
|
|
1392
|
+
"options": {
|
|
1393
|
+
"baseURL": "${apiBase}",
|
|
1394
|
+
"apiKey": "${key}"
|
|
1395
|
+
},
|
|
1396
|
+
"models": {
|
|
1397
|
+
"${model}": {}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}`,
|
|
1402
|
+
pi: `{
|
|
1403
|
+
"litellm": {
|
|
1404
|
+
"baseUrl": "${apiBase}",
|
|
1405
|
+
"apiKey": "${key}",
|
|
1406
|
+
"api": "openai-completions",
|
|
1407
|
+
"models": [
|
|
1408
|
+
{ "id": "${model}", "name": "${model}" }
|
|
1409
|
+
]
|
|
1410
|
+
}
|
|
1411
|
+
}`
|
|
1378
1412
|
};
|
|
1379
1413
|
}
|
|
1380
1414
|
function aliasHelperText(aliasError, aliasDuplicate) {
|
|
@@ -1412,7 +1446,7 @@ function formatContextWindow2(maxInput, maxOutput) {
|
|
|
1412
1446
|
if (outPart) return `ctx ${outPart} out`;
|
|
1413
1447
|
return null;
|
|
1414
1448
|
}
|
|
1415
|
-
var import_react4, import_Box4, import_Typography4, import_Dialog2, import_DialogTitle2, import_DialogContent2, import_DialogActions2, import_TextField2, import_MenuItem, import_Button3, import_IconButton2, import_CircularProgress2, import_Autocomplete2, import_Checkbox, import_FormControlLabel, import_Alert, import_Tabs, import_Tab, import_icons_material3, generateDefaultAlias, emptyForm, SnippetTabs, GenerateKeyDialog;
|
|
1449
|
+
var import_react4, import_Box4, import_Typography4, import_Dialog2, import_DialogTitle2, import_DialogContent2, import_DialogActions2, import_TextField2, import_MenuItem, import_Button3, import_IconButton2, import_CircularProgress2, import_Autocomplete2, import_Checkbox, import_FormControlLabel, import_Alert, import_Tabs, import_Tab, import_icons_material3, generateDefaultAlias, emptyForm, ALL_PROXY_MODELS, SNIPPET_FILE_HINTS, SnippetTabs, GenerateKeyDialog;
|
|
1416
1450
|
var init_GenerateKeyDialog = __esm({
|
|
1417
1451
|
"src/components/GenerateKeyDialog.tsx"() {
|
|
1418
1452
|
"use strict";
|
|
@@ -1451,10 +1485,16 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1451
1485
|
team_id: void 0,
|
|
1452
1486
|
key_type: "llm_api"
|
|
1453
1487
|
});
|
|
1488
|
+
ALL_PROXY_MODELS = "all-proxy-models";
|
|
1489
|
+
SNIPPET_FILE_HINTS = {
|
|
1490
|
+
opencode: "Add to ~/.config/opencode/opencode.json",
|
|
1491
|
+
pi: "Add to ~/.pi/agent/models.json"
|
|
1492
|
+
};
|
|
1454
1493
|
SnippetTabs = ({ snippets, model, onCopy }) => {
|
|
1455
1494
|
const [tab, setTab] = (0, import_react4.useState)("curl");
|
|
1456
|
-
const code = tab
|
|
1457
|
-
|
|
1495
|
+
const code = snippets[tab];
|
|
1496
|
+
const fileHint = SNIPPET_FILE_HINTS[tab];
|
|
1497
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Tabs.default, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 }, variant: "scrollable" }, /* @__PURE__ */ import_react4.default.createElement(import_Tab.default, { label: "curl", value: "curl" }), /* @__PURE__ */ import_react4.default.createElement(import_Tab.default, { label: "OpenAI SDK", value: "openai" }), /* @__PURE__ */ import_react4.default.createElement(import_Tab.default, { label: "opencode", value: "opencode" }), /* @__PURE__ */ import_react4.default.createElement(import_Tab.default, { label: "pi", value: "pi" })), fileHint && /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, fileHint), /* @__PURE__ */ import_react4.default.createElement(
|
|
1458
1498
|
import_Box4.default,
|
|
1459
1499
|
{
|
|
1460
1500
|
position: "relative",
|
|
@@ -1521,12 +1561,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1521
1561
|
const teamRequired = keyGenerationSettings?.teamRequired ?? true;
|
|
1522
1562
|
const selectedTeam = teams.find((t) => t.team_id === formData.team_id) ?? null;
|
|
1523
1563
|
const availableModels = (0, import_react4.useMemo)(() => {
|
|
1524
|
-
|
|
1525
|
-
if (teamModels && teamModels.length > 0) {
|
|
1526
|
-
const allowed = new Set(teamModels);
|
|
1527
|
-
return models.filter((m) => allowed.has(m.model_name));
|
|
1528
|
-
}
|
|
1529
|
-
return models;
|
|
1564
|
+
return models.filter((m) => isModelAllowedByTeam(m, selectedTeam?.models));
|
|
1530
1565
|
}, [models, selectedTeam]);
|
|
1531
1566
|
const selectedModels = availableModels.filter((m) => (formData.models || []).includes(m.model_name));
|
|
1532
1567
|
const aliasError = !(formData.alias || "").trim();
|
|
@@ -1550,12 +1585,12 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1550
1585
|
max_budget: unlimitedBudget ? null : formData.max_budget
|
|
1551
1586
|
};
|
|
1552
1587
|
const response = await onGenerateKey(request);
|
|
1588
|
+
const model = formData.models?.[0] ?? availableModels[0]?.model_name ?? "";
|
|
1553
1589
|
setNewKeyValue(response.key);
|
|
1554
|
-
setNewKeyModel(
|
|
1590
|
+
setNewKeyModel(model);
|
|
1555
1591
|
setNewKeySnippets(null);
|
|
1556
1592
|
try {
|
|
1557
1593
|
const config = await onGetConfig();
|
|
1558
|
-
const model = formData.models?.[0] ?? "";
|
|
1559
1594
|
setNewKeySnippets(buildSnippets(config.baseUrl, response.key, model));
|
|
1560
1595
|
} catch {
|
|
1561
1596
|
}
|
|
@@ -1593,8 +1628,10 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1593
1628
|
getOptionLabel: (t) => t.team_alias || t.team_id,
|
|
1594
1629
|
value: selectedTeam,
|
|
1595
1630
|
onChange: (_e, team) => {
|
|
1596
|
-
const
|
|
1597
|
-
|
|
1631
|
+
const restrictedModels = (formData.models || []).filter((name) => {
|
|
1632
|
+
const model = models.find((m) => m.model_name === name);
|
|
1633
|
+
return model ? isModelAllowedByTeam(model, team?.models) : false;
|
|
1634
|
+
});
|
|
1598
1635
|
setFormData({ ...formData, team_id: team?.team_id, models: restrictedModels });
|
|
1599
1636
|
},
|
|
1600
1637
|
renderInput: (params) => /* @__PURE__ */ import_react4.default.createElement(
|
|
@@ -1641,7 +1678,31 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1641
1678
|
newKeyValue
|
|
1642
1679
|
),
|
|
1643
1680
|
/* @__PURE__ */ import_react4.default.createElement(import_IconButton2.default, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.ContentCopy, null))
|
|
1644
|
-
), newKeySnippets && /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { mt:
|
|
1681
|
+
), newKeySnippets && /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { mt: 2 }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", display: "block", gutterBottom: true }, "Public endpoint \u2014 paste into any tool's base URL / API base field"), /* @__PURE__ */ import_react4.default.createElement(
|
|
1682
|
+
import_Box4.default,
|
|
1683
|
+
{
|
|
1684
|
+
display: "flex",
|
|
1685
|
+
alignItems: "center",
|
|
1686
|
+
gap: 1,
|
|
1687
|
+
p: 1.5,
|
|
1688
|
+
sx: {
|
|
1689
|
+
backgroundColor: "action.hover",
|
|
1690
|
+
border: "1px solid",
|
|
1691
|
+
borderColor: "divider",
|
|
1692
|
+
borderRadius: 1
|
|
1693
|
+
}
|
|
1694
|
+
},
|
|
1695
|
+
/* @__PURE__ */ import_react4.default.createElement(
|
|
1696
|
+
import_Typography4.default,
|
|
1697
|
+
{
|
|
1698
|
+
component: "code",
|
|
1699
|
+
color: "text.primary",
|
|
1700
|
+
sx: { fontFamily: "monospace", fontSize: 13, wordBreak: "break-all", flex: 1 }
|
|
1701
|
+
},
|
|
1702
|
+
newKeySnippets.publicEndpoint
|
|
1703
|
+
),
|
|
1704
|
+
/* @__PURE__ */ import_react4.default.createElement(import_IconButton2.default, { size: "small", onClick: () => copyToClipboard(newKeySnippets.publicEndpoint) }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.ContentCopy, { fontSize: "small" }))
|
|
1705
|
+
)), newKeySnippets && /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { mt: 3 }, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ import_react4.default.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, generateError && /* @__PURE__ */ import_react4.default.createElement(import_Alert.default, { severity: "error", onClose: () => setGenerateError(null) }, generateError), /* @__PURE__ */ import_react4.default.createElement(
|
|
1645
1706
|
import_TextField2.default,
|
|
1646
1707
|
{
|
|
1647
1708
|
label: "Alias",
|