pi-provider-litellm 2.0.1 → 2.0.3
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/discover.js +27 -2
- package/dist/index.js +94 -4
- package/package.json +3 -3
package/dist/discover.js
CHANGED
|
@@ -85,6 +85,8 @@ function catalogLookupIds(id) {
|
|
|
85
85
|
const match = /^(?:claude-)?(opus|sonnet|haiku)-(\d+)-(\d+)$/.exec(anthropicAlias);
|
|
86
86
|
if (match)
|
|
87
87
|
lookupIds.add(`claude-${match[1]}-${match[2]}-${match[3]}`);
|
|
88
|
+
if (anthropicAlias === "fable-5")
|
|
89
|
+
lookupIds.add("claude-fable-5");
|
|
88
90
|
return [...lookupIds];
|
|
89
91
|
}
|
|
90
92
|
function findCatalogModelInProvider(provider, lookupIds) {
|
|
@@ -439,8 +441,31 @@ export async function discoverModels(baseUrl, apiKey, options = {}) {
|
|
|
439
441
|
model_info: { ...previous?.model_info, ...entry.model_info },
|
|
440
442
|
});
|
|
441
443
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
+
let models = [...entries.values()].map(mapFromModelInfo).filter((m) => m !== undefined);
|
|
445
|
+
// LiteLLM's /model/info does NOT expand wildcard model_name entries (e.g.
|
|
446
|
+
// "lemonade/*" backed by model: openai/* + check_provider_endpoint: true)
|
|
447
|
+
// — it returns the literal wildcard only. The discovered ids live in
|
|
448
|
+
// /v1/models instead. When /model/info contains any wildcard id, also query
|
|
449
|
+
// /v1/models and merge the expanded (non-wildcard) entries in, dropping the
|
|
450
|
+
// raw wildcard row so it doesn't surface as a phantom model choice.
|
|
451
|
+
// Ref: docs.litellm.ai/docs/proxy/model_discovery
|
|
452
|
+
if (models.some((m) => m.id.includes("*"))) {
|
|
453
|
+
progress?.("/model/info has wildcard entries, expanding via /v1/models...");
|
|
454
|
+
let modelsDev;
|
|
455
|
+
if (options.modelsDev !== false) {
|
|
456
|
+
progress?.("Loading models.dev catalog for metadata enrichment...");
|
|
457
|
+
modelsDev = await getModelsDevCatalog(options);
|
|
458
|
+
}
|
|
459
|
+
const listResult = await fetchJson(`${base}/v1/models`, apiKey, options);
|
|
460
|
+
if (listResult.ok) {
|
|
461
|
+
const expanded = (listResult.data.data ?? [])
|
|
462
|
+
.map((entry) => mapFromModelsList(entry, modelsDev))
|
|
463
|
+
.filter((m) => m !== undefined && !m.id.includes("*"));
|
|
464
|
+
const seen = new Set(models.map((m) => m.id));
|
|
465
|
+
models = [...models.filter((m) => !m.id.includes("*")), ...expanded.filter((m) => !seen.has(m.id))];
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return { source: "model_info", models: deduplicateModels(models) };
|
|
444
469
|
}
|
|
445
470
|
if (![401, 403, 404].includes(infoResult.status)) {
|
|
446
471
|
throw new Error(`/model/info returned ${infoResult.status}`);
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { setupLiteLLMCostTracking } from "./cost.js";
|
|
6
|
-
import { discoverModels, isGpt55Model, normalizeBaseUrl, shouldSuppressReasoningContent } from "./discover.js";
|
|
6
|
+
import { discoverModels, isGpt55Model, isMoonshotModel, normalizeBaseUrl, shouldSuppressReasoningContent, } from "./discover.js";
|
|
7
7
|
import { getGcloudToken, getGcloudTokenCacheKey, getGcloudTokenCommand, isGcloudTokenAuthEnabled, } from "./gcloud-token.js";
|
|
8
8
|
import { getSessionIdFromFile } from "./litellm.js";
|
|
9
9
|
import { createMcpToolDefinitions } from "./mcp-tools.js";
|
|
@@ -579,6 +579,56 @@ function createProviderAuth(definition) {
|
|
|
579
579
|
function isReasoningItem(item) {
|
|
580
580
|
return typeof item === "object" && item !== null && item.type === "reasoning";
|
|
581
581
|
}
|
|
582
|
+
// Returns the original array reference when no normalization is needed.
|
|
583
|
+
function normalizeStrictToolMessages(messages) {
|
|
584
|
+
const normalized = [];
|
|
585
|
+
let imageParts = [];
|
|
586
|
+
let changed = false;
|
|
587
|
+
const flushImages = () => {
|
|
588
|
+
if (imageParts.length === 0)
|
|
589
|
+
return;
|
|
590
|
+
normalized.push({
|
|
591
|
+
role: "user",
|
|
592
|
+
content: [{ type: "text", text: "Attached image(s) from tool result:" }, ...imageParts],
|
|
593
|
+
});
|
|
594
|
+
imageParts = [];
|
|
595
|
+
};
|
|
596
|
+
for (const message of messages) {
|
|
597
|
+
if (message.role !== "tool")
|
|
598
|
+
flushImages();
|
|
599
|
+
if (message.role === "assistant" &&
|
|
600
|
+
Array.isArray(message.tool_calls) &&
|
|
601
|
+
message.tool_calls.length > 0 &&
|
|
602
|
+
message.content == null) {
|
|
603
|
+
normalized.push({ ...message, content: "" });
|
|
604
|
+
changed = true;
|
|
605
|
+
}
|
|
606
|
+
else if (message.role === "tool" && Array.isArray(message.content)) {
|
|
607
|
+
const previousImageCount = imageParts.length;
|
|
608
|
+
const content = message.content
|
|
609
|
+
.map((part) => {
|
|
610
|
+
if (typeof part === "string")
|
|
611
|
+
return part;
|
|
612
|
+
if (part?.type === "text")
|
|
613
|
+
return part.text || "";
|
|
614
|
+
if (part?.type === "image_url")
|
|
615
|
+
imageParts.push(part);
|
|
616
|
+
return "";
|
|
617
|
+
})
|
|
618
|
+
.join("");
|
|
619
|
+
normalized.push({
|
|
620
|
+
...message,
|
|
621
|
+
content: content || (imageParts.length > previousImageCount ? "(see attached image)" : "(no tool output)"),
|
|
622
|
+
});
|
|
623
|
+
changed = true;
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
normalized.push(message);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
flushImages();
|
|
630
|
+
return changed ? normalized : messages;
|
|
631
|
+
}
|
|
582
632
|
// Reasoning fields LiteLLM forwards to chat-completions providers. The Moonshot
|
|
583
633
|
// path defaults them off; the gpt-5.5 tool path strips them entirely.
|
|
584
634
|
const REASONING_SUPPRESSION_DEFAULTS = {
|
|
@@ -587,6 +637,7 @@ const REASONING_SUPPRESSION_DEFAULTS = {
|
|
|
587
637
|
merge_reasoning_content_in_choices: true,
|
|
588
638
|
thinking: { type: "disabled" },
|
|
589
639
|
};
|
|
640
|
+
const GEMINI_MODEL_PATTERN = /(?:^|[-_/.:])gemini(?=$|[-_/.:])/i;
|
|
590
641
|
function prepareLiteLLMRequestPayload(payload, modelId, api, sessionId) {
|
|
591
642
|
let next;
|
|
592
643
|
const update = (key, value) => {
|
|
@@ -596,8 +647,10 @@ function prepareLiteLLMRequestPayload(payload, modelId, api, sessionId) {
|
|
|
596
647
|
next[key] = value;
|
|
597
648
|
};
|
|
598
649
|
if (api !== "openai-responses" && modelId && shouldSuppressReasoningContent(modelId)) {
|
|
599
|
-
for (const [key, value] of Object.entries(REASONING_SUPPRESSION_DEFAULTS))
|
|
600
|
-
|
|
650
|
+
for (const [key, value] of Object.entries(REASONING_SUPPRESSION_DEFAULTS)) {
|
|
651
|
+
if (key !== "thinking")
|
|
652
|
+
update(key, value);
|
|
653
|
+
}
|
|
601
654
|
}
|
|
602
655
|
// LiteLLM still routes gpt-5.5 tool+reasoning requests through chat completions.
|
|
603
656
|
// Drop reasoning until the gateway honors /v1/responses for this route.
|
|
@@ -630,6 +683,39 @@ function prepareLiteLLMRequestPayload(payload, modelId, api, sessionId) {
|
|
|
630
683
|
next.input = input.filter((item) => !isReasoningItem(item));
|
|
631
684
|
}
|
|
632
685
|
}
|
|
686
|
+
// Moonshot/Kimi applies strict OpenAI schema validation: assistant tool calls
|
|
687
|
+
// must carry string content, and tool results must be plain text.
|
|
688
|
+
if (modelId && isMoonshotModel(modelId)) {
|
|
689
|
+
const messages = (next ?? payload).messages;
|
|
690
|
+
if (Array.isArray(messages)) {
|
|
691
|
+
const normalized = normalizeStrictToolMessages(messages);
|
|
692
|
+
if (normalized !== messages) {
|
|
693
|
+
next ??= { ...payload };
|
|
694
|
+
next.messages = normalized;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (modelId && GEMINI_MODEL_PATTERN.test(modelId)) {
|
|
699
|
+
const currentPayload = next ?? payload;
|
|
700
|
+
if (typeof currentPayload.reasoning_effort === "string") {
|
|
701
|
+
const lower = currentPayload.reasoning_effort.toLowerCase();
|
|
702
|
+
if (currentPayload.reasoning_effort !== lower) {
|
|
703
|
+
next ??= { ...payload };
|
|
704
|
+
next.reasoning_effort = lower;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
const reasoningPayload = next ?? payload;
|
|
708
|
+
if (isPlainObject(reasoningPayload.reasoning) && typeof reasoningPayload.reasoning.effort === "string") {
|
|
709
|
+
const lower = reasoningPayload.reasoning.effort.toLowerCase();
|
|
710
|
+
if (reasoningPayload.reasoning.effort !== lower) {
|
|
711
|
+
next ??= { ...payload };
|
|
712
|
+
next.reasoning = {
|
|
713
|
+
...next.reasoning,
|
|
714
|
+
effort: lower,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
633
719
|
if (sessionId) {
|
|
634
720
|
next ??= { ...payload };
|
|
635
721
|
next.litellm_session_id = sessionId;
|
|
@@ -862,7 +948,11 @@ export default async function (pi) {
|
|
|
862
948
|
context.credential) {
|
|
863
949
|
const auth = await authForCredential(definition, context.credential);
|
|
864
950
|
defaultRuntimeAuth = auth;
|
|
865
|
-
|
|
951
|
+
const registration = registerMcpTools(auth, context.signal);
|
|
952
|
+
if (context.signal)
|
|
953
|
+
await registration;
|
|
954
|
+
else
|
|
955
|
+
void registration.catch(() => undefined);
|
|
866
956
|
}
|
|
867
957
|
}
|
|
868
958
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-provider-litellm",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "LiteLLM proxy provider extension for Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@biomejs/biome": "^2.4.15",
|
|
68
|
-
"@earendil-works/pi-ai": "^0.
|
|
69
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
68
|
+
"@earendil-works/pi-ai": "^0.82.1",
|
|
69
|
+
"@earendil-works/pi-coding-agent": "^0.82.1",
|
|
70
70
|
"@kitlangton/terminal-control": "0.6.0",
|
|
71
71
|
"@types/node": "^26.0.0",
|
|
72
72
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|