@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.esm.js
CHANGED
|
@@ -1350,10 +1350,18 @@ import { ContentCopy as ContentCopy2, Code } from "@mui/icons-material";
|
|
|
1350
1350
|
function trimSlash(url) {
|
|
1351
1351
|
return url.replace(/\/+$/, "");
|
|
1352
1352
|
}
|
|
1353
|
+
function isModelAllowedByTeam(model, teamModels) {
|
|
1354
|
+
if (!teamModels || teamModels.length === 0) return true;
|
|
1355
|
+
if (teamModels.includes(ALL_PROXY_MODELS)) return true;
|
|
1356
|
+
if (teamModels.includes(model.model_name)) return true;
|
|
1357
|
+
return !!model.access_groups?.some((group) => teamModels.includes(group));
|
|
1358
|
+
}
|
|
1353
1359
|
function buildSnippets(baseUrl, key, model) {
|
|
1354
1360
|
const base = trimSlash(baseUrl);
|
|
1361
|
+
const apiBase = `${base}/v1`;
|
|
1355
1362
|
return {
|
|
1356
|
-
|
|
1363
|
+
publicEndpoint: apiBase,
|
|
1364
|
+
curl: `curl ${apiBase}/chat/completions \\
|
|
1357
1365
|
-H "Authorization: Bearer ${key}" \\
|
|
1358
1366
|
-H "Content-Type: application/json" \\
|
|
1359
1367
|
-d '{
|
|
@@ -1364,14 +1372,40 @@ function buildSnippets(baseUrl, key, model) {
|
|
|
1364
1372
|
|
|
1365
1373
|
client = OpenAI(
|
|
1366
1374
|
api_key="${key}",
|
|
1367
|
-
base_url="${
|
|
1375
|
+
base_url="${apiBase}",
|
|
1368
1376
|
)
|
|
1369
1377
|
|
|
1370
1378
|
response = client.chat.completions.create(
|
|
1371
1379
|
model="${model}",
|
|
1372
1380
|
messages=[{ "role": "user", "content": "Hello!" }],
|
|
1373
1381
|
)
|
|
1374
|
-
print(response.choices[0].message.content)
|
|
1382
|
+
print(response.choices[0].message.content)`,
|
|
1383
|
+
opencode: `{
|
|
1384
|
+
"$schema": "https://opencode.ai/config.json",
|
|
1385
|
+
"provider": {
|
|
1386
|
+
"litellm": {
|
|
1387
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
1388
|
+
"name": "LiteLLM",
|
|
1389
|
+
"options": {
|
|
1390
|
+
"baseURL": "${apiBase}",
|
|
1391
|
+
"apiKey": "${key}"
|
|
1392
|
+
},
|
|
1393
|
+
"models": {
|
|
1394
|
+
"${model}": {}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}`,
|
|
1399
|
+
pi: `{
|
|
1400
|
+
"litellm": {
|
|
1401
|
+
"baseUrl": "${apiBase}",
|
|
1402
|
+
"apiKey": "${key}",
|
|
1403
|
+
"api": "openai-completions",
|
|
1404
|
+
"models": [
|
|
1405
|
+
{ "id": "${model}", "name": "${model}" }
|
|
1406
|
+
]
|
|
1407
|
+
}
|
|
1408
|
+
}`
|
|
1375
1409
|
};
|
|
1376
1410
|
}
|
|
1377
1411
|
function aliasHelperText(aliasError, aliasDuplicate) {
|
|
@@ -1409,7 +1443,7 @@ function formatContextWindow2(maxInput, maxOutput) {
|
|
|
1409
1443
|
if (outPart) return `ctx ${outPart} out`;
|
|
1410
1444
|
return null;
|
|
1411
1445
|
}
|
|
1412
|
-
var generateDefaultAlias, emptyForm, SnippetTabs, GenerateKeyDialog;
|
|
1446
|
+
var generateDefaultAlias, emptyForm, ALL_PROXY_MODELS, SNIPPET_FILE_HINTS, SnippetTabs, GenerateKeyDialog;
|
|
1413
1447
|
var init_GenerateKeyDialog = __esm({
|
|
1414
1448
|
"src/components/GenerateKeyDialog.tsx"() {
|
|
1415
1449
|
"use strict";
|
|
@@ -1429,10 +1463,16 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1429
1463
|
team_id: void 0,
|
|
1430
1464
|
key_type: "llm_api"
|
|
1431
1465
|
});
|
|
1466
|
+
ALL_PROXY_MODELS = "all-proxy-models";
|
|
1467
|
+
SNIPPET_FILE_HINTS = {
|
|
1468
|
+
opencode: "Add to ~/.config/opencode/opencode.json",
|
|
1469
|
+
pi: "Add to ~/.pi/agent/models.json"
|
|
1470
|
+
};
|
|
1432
1471
|
SnippetTabs = ({ snippets, model, onCopy }) => {
|
|
1433
1472
|
const [tab, setTab] = useState2("curl");
|
|
1434
|
-
const code = tab
|
|
1435
|
-
|
|
1473
|
+
const code = snippets[tab];
|
|
1474
|
+
const fileHint = SNIPPET_FILE_HINTS[tab];
|
|
1475
|
+
return /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 }, variant: "scrollable" }, /* @__PURE__ */ React4.createElement(Tab, { label: "curl", value: "curl" }), /* @__PURE__ */ React4.createElement(Tab, { label: "OpenAI SDK", value: "openai" }), /* @__PURE__ */ React4.createElement(Tab, { label: "opencode", value: "opencode" }), /* @__PURE__ */ React4.createElement(Tab, { label: "pi", value: "pi" })), fileHint && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, fileHint), /* @__PURE__ */ React4.createElement(
|
|
1436
1476
|
Box4,
|
|
1437
1477
|
{
|
|
1438
1478
|
position: "relative",
|
|
@@ -1499,12 +1539,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1499
1539
|
const teamRequired = keyGenerationSettings?.teamRequired ?? true;
|
|
1500
1540
|
const selectedTeam = teams.find((t) => t.team_id === formData.team_id) ?? null;
|
|
1501
1541
|
const availableModels = useMemo3(() => {
|
|
1502
|
-
|
|
1503
|
-
if (teamModels && teamModels.length > 0) {
|
|
1504
|
-
const allowed = new Set(teamModels);
|
|
1505
|
-
return models.filter((m) => allowed.has(m.model_name));
|
|
1506
|
-
}
|
|
1507
|
-
return models;
|
|
1542
|
+
return models.filter((m) => isModelAllowedByTeam(m, selectedTeam?.models));
|
|
1508
1543
|
}, [models, selectedTeam]);
|
|
1509
1544
|
const selectedModels = availableModels.filter((m) => (formData.models || []).includes(m.model_name));
|
|
1510
1545
|
const aliasError = !(formData.alias || "").trim();
|
|
@@ -1528,12 +1563,12 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1528
1563
|
max_budget: unlimitedBudget ? null : formData.max_budget
|
|
1529
1564
|
};
|
|
1530
1565
|
const response = await onGenerateKey(request);
|
|
1566
|
+
const model = formData.models?.[0] ?? availableModels[0]?.model_name ?? "";
|
|
1531
1567
|
setNewKeyValue(response.key);
|
|
1532
|
-
setNewKeyModel(
|
|
1568
|
+
setNewKeyModel(model);
|
|
1533
1569
|
setNewKeySnippets(null);
|
|
1534
1570
|
try {
|
|
1535
1571
|
const config = await onGetConfig();
|
|
1536
|
-
const model = formData.models?.[0] ?? "";
|
|
1537
1572
|
setNewKeySnippets(buildSnippets(config.baseUrl, response.key, model));
|
|
1538
1573
|
} catch {
|
|
1539
1574
|
}
|
|
@@ -1571,8 +1606,10 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1571
1606
|
getOptionLabel: (t) => t.team_alias || t.team_id,
|
|
1572
1607
|
value: selectedTeam,
|
|
1573
1608
|
onChange: (_e, team) => {
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1609
|
+
const restrictedModels = (formData.models || []).filter((name) => {
|
|
1610
|
+
const model = models.find((m) => m.model_name === name);
|
|
1611
|
+
return model ? isModelAllowedByTeam(model, team?.models) : false;
|
|
1612
|
+
});
|
|
1576
1613
|
setFormData({ ...formData, team_id: team?.team_id, models: restrictedModels });
|
|
1577
1614
|
},
|
|
1578
1615
|
renderInput: (params) => /* @__PURE__ */ React4.createElement(
|
|
@@ -1619,7 +1656,31 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1619
1656
|
newKeyValue
|
|
1620
1657
|
),
|
|
1621
1658
|
/* @__PURE__ */ React4.createElement(IconButton2, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ React4.createElement(ContentCopy2, null))
|
|
1622
|
-
), newKeySnippets && /* @__PURE__ */ React4.createElement(Box4, { mt:
|
|
1659
|
+
), newKeySnippets && /* @__PURE__ */ React4.createElement(Box4, { mt: 2 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", gutterBottom: true }, "Public endpoint \u2014 paste into any tool's base URL / API base field"), /* @__PURE__ */ React4.createElement(
|
|
1660
|
+
Box4,
|
|
1661
|
+
{
|
|
1662
|
+
display: "flex",
|
|
1663
|
+
alignItems: "center",
|
|
1664
|
+
gap: 1,
|
|
1665
|
+
p: 1.5,
|
|
1666
|
+
sx: {
|
|
1667
|
+
backgroundColor: "action.hover",
|
|
1668
|
+
border: "1px solid",
|
|
1669
|
+
borderColor: "divider",
|
|
1670
|
+
borderRadius: 1
|
|
1671
|
+
}
|
|
1672
|
+
},
|
|
1673
|
+
/* @__PURE__ */ React4.createElement(
|
|
1674
|
+
Typography4,
|
|
1675
|
+
{
|
|
1676
|
+
component: "code",
|
|
1677
|
+
color: "text.primary",
|
|
1678
|
+
sx: { fontFamily: "monospace", fontSize: 13, wordBreak: "break-all", flex: 1 }
|
|
1679
|
+
},
|
|
1680
|
+
newKeySnippets.publicEndpoint
|
|
1681
|
+
),
|
|
1682
|
+
/* @__PURE__ */ React4.createElement(IconButton2, { size: "small", onClick: () => copyToClipboard(newKeySnippets.publicEndpoint) }, /* @__PURE__ */ React4.createElement(ContentCopy2, { fontSize: "small" }))
|
|
1683
|
+
)), newKeySnippets && /* @__PURE__ */ React4.createElement(Box4, { mt: 3 }, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ React4.createElement(Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ React4.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ React4.createElement(Box4, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, generateError && /* @__PURE__ */ React4.createElement(Alert, { severity: "error", onClose: () => setGenerateError(null) }, generateError), /* @__PURE__ */ React4.createElement(
|
|
1623
1684
|
TextField2,
|
|
1624
1685
|
{
|
|
1625
1686
|
label: "Alias",
|