pi-provider-litellm 3.1.0 → 3.3.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 +72 -14
- package/package.json +6 -6
- package/src/backend-identity.ts +31 -5
- package/src/discover.ts +220 -56
- package/src/index.ts +721 -197
- package/src/mcp-tools.ts +225 -30
- package/src/model-groups.ts +44 -13
- package/src/provider.ts +27 -12
- package/src/proxy-version.ts +68 -0
- package/src/public-catalog.ts +15 -8
- package/src/skills.ts +33 -76
- package/src/types.ts +16 -0
package/src/provider.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type AnyModel,
|
|
3
|
+
type Api,
|
|
2
4
|
type ApiStreamOptions,
|
|
3
|
-
type Context,
|
|
4
5
|
type Credential,
|
|
5
6
|
createProvider,
|
|
7
|
+
getCurrentTools,
|
|
6
8
|
type Model,
|
|
7
9
|
type Provider,
|
|
8
10
|
type ProviderAuth,
|
|
9
11
|
type SimpleStreamOptions,
|
|
12
|
+
type TranscriptContext,
|
|
10
13
|
} from "@earendil-works/pi-ai";
|
|
11
14
|
import { LITELLM_DISCOVERY_VERSION } from "./backend-identity.js";
|
|
12
15
|
import { enrichCachedModel, normalizeBaseUrl, restoreCachedModelPolicy } from "./discover.js";
|
|
@@ -26,6 +29,11 @@ export type LiteLLMProviderOptions = {
|
|
|
26
29
|
discover(credential: Credential, signal?: AbortSignal): Promise<DiscoveryResult & { baseUrl?: string }>;
|
|
27
30
|
};
|
|
28
31
|
|
|
32
|
+
// Chat models may omit `type`; Pi's `isModelType()` is newer than the peer floor, so keep the check local.
|
|
33
|
+
function isChatModel(model: AnyModel): model is Model<Api> {
|
|
34
|
+
return (model.type ?? "chat") === "chat";
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
export function toNativeModels(
|
|
30
38
|
provider: string,
|
|
31
39
|
baseUrl: string,
|
|
@@ -112,7 +120,7 @@ function modelRootError(
|
|
|
112
120
|
function requestModel(
|
|
113
121
|
provider: string,
|
|
114
122
|
model: Model<LiteLLMApi>,
|
|
115
|
-
context:
|
|
123
|
+
context: TranscriptContext,
|
|
116
124
|
credentialRoot: string | undefined,
|
|
117
125
|
allowInsecureHttp = false,
|
|
118
126
|
): Model<LiteLLMApi> {
|
|
@@ -120,14 +128,13 @@ function requestModel(
|
|
|
120
128
|
const active = activeCredentialRoot(credentialRoot, allowInsecureHttp);
|
|
121
129
|
const error = modelRootError(model, active, allowInsecureHttp);
|
|
122
130
|
if (error) throw error;
|
|
123
|
-
|
|
124
|
-
model.api === "openai-completions" &&
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
) {
|
|
131
|
+
const toolCount =
|
|
132
|
+
model.api === "openai-completions" && (model as LiteLLMModel).litellmBackendFamily === "openai"
|
|
133
|
+
? getCurrentTools(context.messages).length
|
|
134
|
+
: 0;
|
|
135
|
+
if (toolCount > CHAT_TOOL_CAP) {
|
|
129
136
|
throw new Error(
|
|
130
|
-
`LiteLLM model ${model.id} uses Chat Completions with ${
|
|
137
|
+
`LiteLLM model ${model.id} uses Chat Completions with ${toolCount} tools, exceeding the ` +
|
|
131
138
|
`${CHAT_TOOL_CAP}-tool cap; route this model via Responses or reduce enabled extensions`,
|
|
132
139
|
);
|
|
133
140
|
}
|
|
@@ -189,7 +196,7 @@ export function createLiteLLMProvider(options: LiteLLMProviderOptions): Provider
|
|
|
189
196
|
const refreshModels = provider.refreshModels;
|
|
190
197
|
const guardedProvider: Provider<LiteLLMApi> = {
|
|
191
198
|
...provider,
|
|
192
|
-
stream: <T extends LiteLLMApi>(model: Model<T>, context:
|
|
199
|
+
stream: <T extends LiteLLMApi>(model: Model<T>, context: TranscriptContext, requestOptions?: ApiStreamOptions<T>) =>
|
|
193
200
|
provider.stream(
|
|
194
201
|
requestModel(
|
|
195
202
|
options.id,
|
|
@@ -201,7 +208,7 @@ export function createLiteLLMProvider(options: LiteLLMProviderOptions): Provider
|
|
|
201
208
|
context,
|
|
202
209
|
requestOptions,
|
|
203
210
|
),
|
|
204
|
-
streamSimple: (model: Model<LiteLLMApi>, context:
|
|
211
|
+
streamSimple: (model: Model<LiteLLMApi>, context: TranscriptContext, requestOptions?: SimpleStreamOptions) =>
|
|
205
212
|
provider.streamSimple(
|
|
206
213
|
requestModel(
|
|
207
214
|
options.id,
|
|
@@ -215,13 +222,21 @@ export function createLiteLLMProvider(options: LiteLLMProviderOptions): Provider
|
|
|
215
222
|
),
|
|
216
223
|
};
|
|
217
224
|
if (!refreshModels) return guardedProvider;
|
|
225
|
+
const seedKey = (model: { id: string; baseUrl: string }) => `${model.id}\0${model.baseUrl}`;
|
|
226
|
+
const seeded = new Map((options.models ?? []).map((model) => [seedKey(model), model]));
|
|
218
227
|
return {
|
|
219
228
|
...guardedProvider,
|
|
220
229
|
refreshModels: async (context) => {
|
|
221
|
-
|
|
230
|
+
// Pi 0.99 persists models of every type; this provider only publishes chat models, and the
|
|
231
|
+
// policy helpers below read chat-only fields.
|
|
232
|
+
const storedModels = (context.stored?.models ?? []).filter(isChatModel);
|
|
222
233
|
let legacyCount = 0;
|
|
223
234
|
const models = storedModels.map((model) => {
|
|
224
235
|
if ((model as LiteLLMModel).litellmDiscoveryVersion !== LITELLM_DISCOVERY_VERSION) {
|
|
236
|
+
// Pi applies stored models over the seed, so a stale entry would otherwise mask what
|
|
237
|
+
// startup discovery just proved for the same route.
|
|
238
|
+
const fresh = seeded.get(seedKey(model));
|
|
239
|
+
if (fresh) return fresh;
|
|
225
240
|
legacyCount++;
|
|
226
241
|
return restoreCachedModelPolicy(model);
|
|
227
242
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { DiscoveryOptions } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export interface ProxyVersion {
|
|
4
|
+
major: number;
|
|
5
|
+
minor: number;
|
|
6
|
+
patch: number;
|
|
7
|
+
// Any suffix (rc, dev, post, local build). It orders before the release it names,
|
|
8
|
+
// so a pre-release never claims a fix that only the release is known to carry.
|
|
9
|
+
prerelease: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ProxyVersionFloor = readonly [major: number, minor: number, patch: number];
|
|
13
|
+
|
|
14
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
15
|
+
const MAX_VERSION_LENGTH = 64;
|
|
16
|
+
const VERSION_PATTERN = /^v?(\d{1,5})\.(\d{1,5})\.(\d{1,5})(.*)$/s;
|
|
17
|
+
const VERSION_HEADER = "x-litellm-version";
|
|
18
|
+
// LiteLLM stamps its version on inference routes only; `/model/info`, `/v1/models`
|
|
19
|
+
// and `/health/*` do not carry it. A made-up Responses id names no deployment, so
|
|
20
|
+
// the read is answered with an error that still carries the header and calls no model.
|
|
21
|
+
const VERSION_PROBE_PATH = "/v1/responses/resp_version_probe";
|
|
22
|
+
|
|
23
|
+
// The value is proxy-supplied: bound it before matching and keep only the numbers.
|
|
24
|
+
export function parseProxyVersion(value: unknown): ProxyVersion | undefined {
|
|
25
|
+
if (typeof value !== "string" || value.length > MAX_VERSION_LENGTH) return undefined;
|
|
26
|
+
const match = VERSION_PATTERN.exec(value.trim());
|
|
27
|
+
if (!match) return undefined;
|
|
28
|
+
return {
|
|
29
|
+
major: Number(match[1]),
|
|
30
|
+
minor: Number(match[2]),
|
|
31
|
+
patch: Number(match[3]),
|
|
32
|
+
prerelease: match[4] !== "",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// An unknown version satisfies no floor, so a version-gated workaround stays on
|
|
37
|
+
// whenever the proxy does not say what it is.
|
|
38
|
+
export function proxyVersionAtLeast(version: ProxyVersion | undefined, floor: ProxyVersionFloor): boolean {
|
|
39
|
+
if (!version) return false;
|
|
40
|
+
const actual = [version.major, version.minor, version.patch];
|
|
41
|
+
for (const [index, required] of floor.entries()) {
|
|
42
|
+
if (actual[index] !== required) return actual[index]! > required;
|
|
43
|
+
}
|
|
44
|
+
return !version.prerelease;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Best effort by design: the header rides on an error path LiteLLM does not
|
|
48
|
+
// document, so a missing header, an unreachable proxy or a timeout all mean
|
|
49
|
+
// "unknown" and never fail discovery.
|
|
50
|
+
export async function probeProxyVersion(
|
|
51
|
+
base: string,
|
|
52
|
+
apiKey: string,
|
|
53
|
+
options: Pick<DiscoveryOptions, "timeoutMs" | "signal" | "headers">,
|
|
54
|
+
): Promise<ProxyVersion | undefined> {
|
|
55
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch(`${base}${VERSION_PROBE_PATH}`, {
|
|
58
|
+
headers: { ...options.headers, Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
59
|
+
signal: options.signal
|
|
60
|
+
? AbortSignal.any([options.signal, AbortSignal.timeout(timeoutMs)])
|
|
61
|
+
: AbortSignal.timeout(timeoutMs),
|
|
62
|
+
});
|
|
63
|
+
await response.body?.cancel().catch(() => undefined);
|
|
64
|
+
return parseProxyVersion(response.headers.get(VERSION_HEADER));
|
|
65
|
+
} catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/public-catalog.ts
CHANGED
|
@@ -73,6 +73,7 @@ const PROVIDER_ALIASES: Readonly<Record<string, readonly string[]>> = {
|
|
|
73
73
|
const PI_PROVIDER_ALIASES: Readonly<Record<string, readonly string[]>> = {
|
|
74
74
|
"amazon-bedrock": ["amazon-bedrock"],
|
|
75
75
|
azure: ["azure-openai-responses"],
|
|
76
|
+
azure_ai: ["azure-openai-responses"],
|
|
76
77
|
chatgpt: ["openai-codex"],
|
|
77
78
|
"fireworks-ai": ["fireworks"],
|
|
78
79
|
openai: ["openai"],
|
|
@@ -245,6 +246,7 @@ export async function loadPublicCatalog(options: LoadPublicCatalogOptions = {}):
|
|
|
245
246
|
const providers = providerCandidates(provider);
|
|
246
247
|
const ids = lookupIds(id);
|
|
247
248
|
const vendor = providers.find((candidate) => candidate !== "azure") ?? providers[0];
|
|
249
|
+
const adapter = provider?.trim().toLowerCase();
|
|
248
250
|
for (const candidate of providers) {
|
|
249
251
|
const models = catalog?.[candidate]?.models;
|
|
250
252
|
for (const modelId of ids) {
|
|
@@ -252,20 +254,25 @@ export async function loadPublicCatalog(options: LoadPublicCatalogOptions = {}):
|
|
|
252
254
|
if (!model) continue;
|
|
253
255
|
const record = mapModelsDev(candidate, modelId, model);
|
|
254
256
|
if (candidate === providers[0] || !vendor) return record;
|
|
255
|
-
|
|
257
|
+
const fallback =
|
|
258
|
+
adapter && (PI_PROVIDER_ALIASES[adapter] ?? [adapter]).some((piProvider) => findPiModel(piProvider, ids))
|
|
259
|
+
? adapter
|
|
260
|
+
: vendor;
|
|
261
|
+
return { ...record, piProvider: (PI_PROVIDER_ALIASES[fallback] ?? [fallback])[0] };
|
|
256
262
|
}
|
|
257
263
|
}
|
|
258
|
-
|
|
259
|
-
|
|
264
|
+
// Provider-specific limits and pricing must not be replaced by the generic
|
|
265
|
+
// vendor's entry just because both catalogs recognize the model id.
|
|
266
|
+
if (adapter && adapter !== vendor) {
|
|
267
|
+
for (const piProvider of PI_PROVIDER_ALIASES[adapter] ?? [adapter]) {
|
|
260
268
|
const model = findPiModel(piProvider, ids);
|
|
261
|
-
if (model) return toPiRecord("pi-
|
|
269
|
+
if (model) return toPiRecord("pi-adapter", piProvider, model);
|
|
262
270
|
}
|
|
263
271
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
for (const piProvider of PI_PROVIDER_ALIASES[adapter] ?? [adapter]) {
|
|
272
|
+
if (vendor) {
|
|
273
|
+
for (const piProvider of PI_PROVIDER_ALIASES[vendor] ?? [vendor]) {
|
|
267
274
|
const model = findPiModel(piProvider, ids);
|
|
268
|
-
if (model) return toPiRecord("pi-
|
|
275
|
+
if (model) return toPiRecord("pi-vendor", piProvider, model);
|
|
269
276
|
}
|
|
270
277
|
}
|
|
271
278
|
return undefined;
|
package/src/skills.ts
CHANGED
|
@@ -42,97 +42,55 @@ export async function listSkills(
|
|
|
42
42
|
return skillsCache.skills;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const fetchSkills = async (path: string): Promise<LiteLLMSkill[]> => {
|
|
45
|
+
const fetchSkills = async (path: string): Promise<LiteLLMSkill[] | undefined> => {
|
|
46
46
|
try {
|
|
47
47
|
const response = await fetch(`${normalizedBaseUrl}${path}`, {
|
|
48
48
|
headers: { ...headers, Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
49
49
|
signal: AbortSignal.timeout(10_000),
|
|
50
50
|
});
|
|
51
|
-
return response.ok ? getSkillsFromBody(await response.json()) :
|
|
51
|
+
return response.ok ? getSkillsFromBody(await response.json()) : undefined;
|
|
52
52
|
} catch {
|
|
53
|
-
return
|
|
53
|
+
return undefined;
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
const
|
|
58
|
-
fetchSkills("/claude-code/marketplace.json"),
|
|
59
|
-
fetchSkills("/v1/skills"),
|
|
60
|
-
]);
|
|
61
|
-
const skillHubNames = new Set(skillHub.map((skill) => skill.name));
|
|
62
|
-
const skills = [...skillHub, ...legacy.filter((skill) => !skillHubNames.has(skill.name))];
|
|
57
|
+
const skills = (await fetchSkills("/claude-code/marketplace.json")) ?? (await fetchSkills("/v1/skills")) ?? [];
|
|
63
58
|
skillsCache = { baseUrl: normalizedBaseUrl, apiKey, fetchedAt: Date.now(), skills };
|
|
64
59
|
return skills;
|
|
65
60
|
}
|
|
66
61
|
|
|
62
|
+
// Only the Skill Hub creates skills from JSON. LiteLLM's POST /v1/skills is Anthropic's multipart
|
|
63
|
+
// Skills API, so a JSON code skill sent there never becomes a listable skill.
|
|
67
64
|
export async function createSkill(
|
|
68
65
|
baseUrl: string,
|
|
69
66
|
apiKey: string,
|
|
70
|
-
input: {
|
|
71
|
-
name: string;
|
|
72
|
-
description?: string;
|
|
73
|
-
source?: Record<string, unknown>;
|
|
74
|
-
code?: string;
|
|
75
|
-
inputSchema?: Record<string, unknown>;
|
|
76
|
-
},
|
|
67
|
+
input: { name: string; description?: string; source: Record<string, unknown> },
|
|
77
68
|
headers?: Record<string, string>,
|
|
78
69
|
allowInsecureHttp = false,
|
|
79
70
|
): Promise<unknown> {
|
|
80
|
-
if (!input.source
|
|
71
|
+
if (!input.source) throw new Error("source is required: skills are created through the LiteLLM Skill Hub");
|
|
81
72
|
const normalizedBaseUrl = normalizeBaseUrl(baseUrl, allowInsecureHttp);
|
|
82
|
-
|
|
83
|
-
const skillHubPayload = input.source
|
|
84
|
-
? {
|
|
85
|
-
name: input.name,
|
|
86
|
-
description: input.description,
|
|
87
|
-
source: input.source,
|
|
88
|
-
}
|
|
89
|
-
: {
|
|
90
|
-
name: input.name,
|
|
91
|
-
description: input.description,
|
|
92
|
-
code: input.code,
|
|
93
|
-
input_schema: input.inputSchema ?? { type: "object", properties: {} },
|
|
94
|
-
};
|
|
95
|
-
let response = await fetch(`${normalizedBaseUrl}${input.source ? "/claude-code/plugins" : "/v1/skills"}`, {
|
|
73
|
+
const response = await fetch(`${normalizedBaseUrl}/claude-code/plugins`, {
|
|
96
74
|
method: "POST",
|
|
97
75
|
headers: {
|
|
98
76
|
...headers,
|
|
99
77
|
Authorization: `Bearer ${apiKey}`,
|
|
100
78
|
"Content-Type": "application/json",
|
|
101
79
|
},
|
|
102
|
-
body: JSON.stringify(
|
|
80
|
+
body: JSON.stringify({ name: input.name, description: input.description, source: input.source }),
|
|
103
81
|
signal: AbortSignal.timeout(10_000),
|
|
104
82
|
});
|
|
105
|
-
if (input.source && response.status === 404) {
|
|
106
|
-
usedSkillHub = false;
|
|
107
|
-
response = await fetch(`${normalizedBaseUrl}/v1/skills`, {
|
|
108
|
-
method: "POST",
|
|
109
|
-
headers: {
|
|
110
|
-
...headers,
|
|
111
|
-
Authorization: `Bearer ${apiKey}`,
|
|
112
|
-
"Content-Type": "application/json",
|
|
113
|
-
},
|
|
114
|
-
body: JSON.stringify({
|
|
115
|
-
name: input.name,
|
|
116
|
-
description: input.description,
|
|
117
|
-
code: input.code,
|
|
118
|
-
input_schema: input.inputSchema ?? { type: "object", properties: {} },
|
|
119
|
-
}),
|
|
120
|
-
signal: AbortSignal.timeout(10_000),
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
83
|
if (!response.ok) throw new Error(`LiteLLM skill create failed: HTTP ${response.status}`);
|
|
124
84
|
skillsCache = undefined;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (!enableResponse.ok) throw new Error(`LiteLLM skill enable failed: HTTP ${enableResponse.status}`);
|
|
135
|
-
}
|
|
85
|
+
const enableResponse = await fetch(
|
|
86
|
+
`${normalizedBaseUrl}/claude-code/plugins/${encodeURIComponent(input.name)}/enable`,
|
|
87
|
+
{
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: { ...headers, Authorization: `Bearer ${apiKey}` },
|
|
90
|
+
signal: AbortSignal.timeout(10_000),
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
if (!enableResponse.ok) throw new Error(`LiteLLM skill enable failed: HTTP ${enableResponse.status}`);
|
|
136
94
|
return response.json().catch(() => ({}));
|
|
137
95
|
}
|
|
138
96
|
|
|
@@ -196,9 +154,7 @@ function formatSkills(skills: LiteLLMSkill[]): string {
|
|
|
196
154
|
const CreateSkillParams = Type.Object({
|
|
197
155
|
name: Type.String({ description: "Skill name" }),
|
|
198
156
|
description: Type.Optional(Type.String({ description: "Skill description" })),
|
|
199
|
-
sourceJson: Type.
|
|
200
|
-
code: Type.Optional(Type.String({ description: "Legacy Skills Gateway implementation or prompt code" })),
|
|
201
|
-
inputSchemaJson: Type.Optional(Type.String({ description: "Optional JSON Schema string for skill inputs" })),
|
|
157
|
+
sourceJson: Type.String({ description: "Skill Hub source metadata JSON object" }),
|
|
202
158
|
});
|
|
203
159
|
|
|
204
160
|
const DeleteSkillParams = Type.Object({
|
|
@@ -213,6 +169,10 @@ function parseJsonObject(value: string, fieldName: string): Record<string, unkno
|
|
|
213
169
|
return parsed as Record<string, unknown>;
|
|
214
170
|
}
|
|
215
171
|
|
|
172
|
+
// Pi 0.99 groups tools by namespace and lets permission extensions read annotation hints; older
|
|
173
|
+
// Pi ignores both fields.
|
|
174
|
+
const SKILLS_NAMESPACE = { name: "litellm_skills", description: "LiteLLM Skills Gateway" };
|
|
175
|
+
|
|
216
176
|
export function createSkillToolDefinitions(
|
|
217
177
|
getAuth: (ctx?: ExtensionContext) => Promise<LiteLLMRuntimeAuth>,
|
|
218
178
|
): ToolDefinition[] {
|
|
@@ -222,6 +182,8 @@ export function createSkillToolDefinitions(
|
|
|
222
182
|
label: "LiteLLM Skills",
|
|
223
183
|
description: "List skills registered on the LiteLLM proxy Skills Gateway.",
|
|
224
184
|
promptSnippet: "List LiteLLM Skills Gateway skills",
|
|
185
|
+
namespace: SKILLS_NAMESPACE,
|
|
186
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
225
187
|
parameters: Type.Object({}),
|
|
226
188
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
227
189
|
const auth = await getAuth(ctx);
|
|
@@ -232,24 +194,17 @@ export function createSkillToolDefinitions(
|
|
|
232
194
|
defineTool({
|
|
233
195
|
name: "litellm_skill_create",
|
|
234
196
|
label: "Create LiteLLM Skill",
|
|
235
|
-
description: "Create a skill on the LiteLLM proxy
|
|
197
|
+
description: "Create and enable a skill on the LiteLLM proxy Skill Hub from source metadata.",
|
|
198
|
+
namespace: SKILLS_NAMESPACE,
|
|
199
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
236
200
|
parameters: CreateSkillParams,
|
|
237
201
|
async execute(_toolCallId, params: Static<typeof CreateSkillParams>, _signal, _onUpdate, ctx) {
|
|
238
202
|
const auth = await getAuth(ctx);
|
|
239
|
-
const source =
|
|
240
|
-
const inputSchema = params.inputSchemaJson
|
|
241
|
-
? parseJsonObject(params.inputSchemaJson, "inputSchemaJson")
|
|
242
|
-
: undefined;
|
|
203
|
+
const source = parseJsonObject(params.sourceJson, "sourceJson");
|
|
243
204
|
const result = await createSkill(
|
|
244
205
|
auth.baseUrl,
|
|
245
206
|
auth.apiKey,
|
|
246
|
-
{
|
|
247
|
-
name: params.name,
|
|
248
|
-
description: params.description,
|
|
249
|
-
source,
|
|
250
|
-
code: params.code,
|
|
251
|
-
inputSchema,
|
|
252
|
-
},
|
|
207
|
+
{ name: params.name, description: params.description, source },
|
|
253
208
|
auth.headers,
|
|
254
209
|
auth.allowInsecureHttp,
|
|
255
210
|
);
|
|
@@ -260,6 +215,8 @@ export function createSkillToolDefinitions(
|
|
|
260
215
|
name: "litellm_skill_delete",
|
|
261
216
|
label: "Delete LiteLLM Skill",
|
|
262
217
|
description: "Delete a skill from the LiteLLM proxy Skills Gateway.",
|
|
218
|
+
namespace: SKILLS_NAMESPACE,
|
|
219
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
263
220
|
parameters: DeleteSkillParams,
|
|
264
221
|
async execute(_toolCallId, params: Static<typeof DeleteSkillParams>, _signal, _onUpdate, ctx) {
|
|
265
222
|
const auth = await getAuth(ctx);
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Model } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { BackendFamily, LITELLM_DISCOVERY_VERSION } from "./backend-identity.js";
|
|
3
|
+
import type { ProxyVersion } from "./proxy-version.js";
|
|
3
4
|
|
|
4
5
|
export type DiscoverySource = "model_info" | "models_list" | "health";
|
|
5
6
|
|
|
@@ -56,6 +57,9 @@ export type LiteLLMModel = Model<LiteLLMApi> & {
|
|
|
56
57
|
export interface DiscoveryResult {
|
|
57
58
|
models: DiscoveredModel[];
|
|
58
59
|
source: DiscoverySource;
|
|
60
|
+
// Present only when a `/model/info` discovery had to read it and the proxy
|
|
61
|
+
// answered. Absent means unknown, not old: discovery asks only when it must.
|
|
62
|
+
proxyVersion?: ProxyVersion;
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
export interface DiscoveryOptions {
|
|
@@ -138,6 +142,14 @@ export interface ResolvedCredentials {
|
|
|
138
142
|
apiKeyFromGcloudAdc?: boolean;
|
|
139
143
|
}
|
|
140
144
|
|
|
145
|
+
// The boolean MCP tool annotation hints, with the meaning Pi gives `ToolAnnotations`.
|
|
146
|
+
export interface McpToolAnnotations {
|
|
147
|
+
readOnlyHint?: boolean;
|
|
148
|
+
destructiveHint?: boolean;
|
|
149
|
+
idempotentHint?: boolean;
|
|
150
|
+
openWorldHint?: boolean;
|
|
151
|
+
}
|
|
152
|
+
|
|
141
153
|
export interface LiteLLMMcpTool {
|
|
142
154
|
name: string;
|
|
143
155
|
server_name: string;
|
|
@@ -147,6 +159,10 @@ export interface LiteLLMMcpTool {
|
|
|
147
159
|
input_schema: Record<string, unknown>;
|
|
148
160
|
// True when the proxy supplied an `inputSchema`/`input_schema` that was not a JSON object.
|
|
149
161
|
input_schema_malformed?: boolean;
|
|
162
|
+
// Boolean hints from the proxy-supplied `annotations`; every other annotation field is dropped.
|
|
163
|
+
annotations?: McpToolAnnotations;
|
|
164
|
+
// `mcp_info.description` of the server, when the proxy supplies one.
|
|
165
|
+
server_description?: string;
|
|
150
166
|
}
|
|
151
167
|
|
|
152
168
|
export interface LiteLLMSkill {
|