pi-provider-litellm 1.2.7 → 1.2.9
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 +80 -10
- package/dist/cost.d.ts +5 -1
- package/dist/cost.js +0 -0
- package/dist/discover.d.ts +5 -0
- package/dist/discover.js +71 -29
- package/dist/index.js +720 -171
- package/dist/mcp-tools.d.ts +3 -3
- package/dist/mcp-tools.js +76 -64
- package/dist/skills.d.ts +7 -6
- package/dist/skills.js +112 -32
- package/dist/types.d.ts +10 -0
- package/package.json +6 -8
package/dist/mcp-tools.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { LiteLLMMcpTool } from "./types.js";
|
|
3
|
-
export declare function discoverMcpTools(baseUrl: string, apiKey: string): Promise<LiteLLMMcpTool[]>;
|
|
4
|
-
export declare function executeMcpTool(baseUrl: string, apiKey: string, serverId: string, toolName: string, args: Record<string, unknown>): Promise<string>;
|
|
5
|
-
export declare function createMcpToolDefinitions(baseUrl: string, getApiKey: () => Promise<string>): Promise<ToolDefinition[]>;
|
|
3
|
+
export declare function discoverMcpTools(baseUrl: string, apiKey: string, headers?: Record<string, string>): Promise<LiteLLMMcpTool[]>;
|
|
4
|
+
export declare function executeMcpTool(baseUrl: string, apiKey: string, serverId: string, toolName: string, args: Record<string, unknown>, headers?: Record<string, string>): Promise<string>;
|
|
5
|
+
export declare function createMcpToolDefinitions(baseUrl: string, getApiKey: () => Promise<string>, headers?: Record<string, string>): Promise<ToolDefinition[]>;
|
package/dist/mcp-tools.js
CHANGED
|
@@ -3,6 +3,9 @@ import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
import { normalizeBaseUrl } from "./discover.js";
|
|
4
4
|
const LIST_TIMEOUT_MS = 10_000;
|
|
5
5
|
const CALL_TIMEOUT_MS = 30_000;
|
|
6
|
+
const MCP_RETRY_DELAY_MS = 350;
|
|
7
|
+
const MCP_RETRY_ATTEMPTS = 1;
|
|
8
|
+
const RETRYABLE_HTTP_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
6
9
|
function withTimeout(timeoutMs) {
|
|
7
10
|
const controller = new AbortController();
|
|
8
11
|
const timer = setTimeout(() => controller.abort(new Error(`Timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
@@ -14,6 +17,23 @@ function withTimeout(timeoutMs) {
|
|
|
14
17
|
function asRecord(value) {
|
|
15
18
|
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
16
19
|
}
|
|
20
|
+
function sleep(ms) {
|
|
21
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
|
+
}
|
|
23
|
+
function formatMcpToolError(toolName, serverId, error) {
|
|
24
|
+
return `Error calling ${toolName} on ${serverId}: ${error instanceof Error ? error.message : String(error)}`;
|
|
25
|
+
}
|
|
26
|
+
function isRetryableTransportError(error) {
|
|
27
|
+
if (!(error instanceof Error))
|
|
28
|
+
return false;
|
|
29
|
+
if (error.name === "AbortError" || error.name === "TimeoutError")
|
|
30
|
+
return true;
|
|
31
|
+
if (error.message.startsWith("Timed out after "))
|
|
32
|
+
return true;
|
|
33
|
+
if (error.name !== "TypeError")
|
|
34
|
+
return false;
|
|
35
|
+
return /fetch failed|failed to fetch|network|terminated|ECONNRESET|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|socket/i.test(error.message);
|
|
36
|
+
}
|
|
17
37
|
function stringValue(value) {
|
|
18
38
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
19
39
|
}
|
|
@@ -37,11 +57,12 @@ function normalizeMcpTool(value) {
|
|
|
37
57
|
input_schema: inputSchema,
|
|
38
58
|
};
|
|
39
59
|
}
|
|
40
|
-
export async function discoverMcpTools(baseUrl, apiKey) {
|
|
60
|
+
export async function discoverMcpTools(baseUrl, apiKey, headers) {
|
|
41
61
|
const { signal, cancel } = withTimeout(LIST_TIMEOUT_MS);
|
|
42
62
|
try {
|
|
43
63
|
const response = await fetch(`${normalizeBaseUrl(baseUrl)}/mcp-rest/tools/list`, {
|
|
44
64
|
headers: {
|
|
65
|
+
...headers,
|
|
45
66
|
Authorization: `Bearer ${apiKey}`,
|
|
46
67
|
"Content-Type": "application/json",
|
|
47
68
|
},
|
|
@@ -61,25 +82,59 @@ export async function discoverMcpTools(baseUrl, apiKey) {
|
|
|
61
82
|
cancel();
|
|
62
83
|
}
|
|
63
84
|
}
|
|
64
|
-
export async function executeMcpTool(baseUrl, apiKey, serverId, toolName, args) {
|
|
85
|
+
export async function executeMcpTool(baseUrl, apiKey, serverId, toolName, args, headers) {
|
|
86
|
+
for (let attempt = 0; attempt <= MCP_RETRY_ATTEMPTS; attempt++) {
|
|
87
|
+
const result = await executeMcpToolOnce(baseUrl, apiKey, serverId, toolName, args, headers);
|
|
88
|
+
if (attempt < MCP_RETRY_ATTEMPTS && result.retryable) {
|
|
89
|
+
await sleep(MCP_RETRY_DELAY_MS);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
return result.text;
|
|
93
|
+
}
|
|
94
|
+
return `Error calling ${toolName} on ${serverId}: retry attempts exhausted`;
|
|
95
|
+
}
|
|
96
|
+
async function executeMcpToolOnce(baseUrl, apiKey, serverId, toolName, args, headers) {
|
|
65
97
|
const { signal, cancel } = withTimeout(CALL_TIMEOUT_MS);
|
|
66
98
|
try {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
99
|
+
let response;
|
|
100
|
+
try {
|
|
101
|
+
response = await fetch(`${normalizeBaseUrl(baseUrl)}/mcp-rest/tools/call`, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers: {
|
|
104
|
+
...headers,
|
|
105
|
+
Authorization: `Bearer ${apiKey}`,
|
|
106
|
+
"Content-Type": "application/json",
|
|
107
|
+
},
|
|
108
|
+
body: JSON.stringify({ server_id: serverId, name: toolName, arguments: args }),
|
|
109
|
+
signal,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
return {
|
|
114
|
+
text: formatMcpToolError(toolName, serverId, error),
|
|
115
|
+
retryable: isRetryableTransportError(error),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
return {
|
|
120
|
+
text: `Error calling ${toolName} on ${serverId}: HTTP ${response.status}`,
|
|
121
|
+
retryable: RETRYABLE_HTTP_STATUS.has(response.status),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const body = (await response.json());
|
|
126
|
+
const bodyRecord = asRecord(body);
|
|
127
|
+
return {
|
|
128
|
+
text: JSON.stringify(bodyRecord && "result" in bodyRecord ? bodyRecord.result : body, null, 2),
|
|
129
|
+
retryable: false,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
return { text: formatMcpToolError(toolName, serverId, error), retryable: false };
|
|
134
|
+
}
|
|
80
135
|
}
|
|
81
136
|
catch (error) {
|
|
82
|
-
return
|
|
137
|
+
return { text: formatMcpToolError(toolName, serverId, error), retryable: false };
|
|
83
138
|
}
|
|
84
139
|
finally {
|
|
85
140
|
cancel();
|
|
@@ -92,37 +147,6 @@ function sanitizeName(name) {
|
|
|
92
147
|
.replace(/^_+|_+$/g, "");
|
|
93
148
|
return sanitized || "tool";
|
|
94
149
|
}
|
|
95
|
-
function isMappableSchema(value) {
|
|
96
|
-
if (!value || typeof value !== "object")
|
|
97
|
-
return false;
|
|
98
|
-
const schema = value;
|
|
99
|
-
if ("$ref" in schema || "anyOf" in schema || "oneOf" in schema || "allOf" in schema)
|
|
100
|
-
return false;
|
|
101
|
-
if (["string", "number", "integer", "boolean"].includes(String(schema.type)))
|
|
102
|
-
return true;
|
|
103
|
-
if (schema.type === "array") {
|
|
104
|
-
const items = schema.items;
|
|
105
|
-
return items?.type === "string";
|
|
106
|
-
}
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
function mapPropertySchema(name, schema) {
|
|
110
|
-
const description = typeof schema.description === "string" ? schema.description : name;
|
|
111
|
-
switch (schema.type) {
|
|
112
|
-
case "string":
|
|
113
|
-
return Type.String({ description });
|
|
114
|
-
case "number":
|
|
115
|
-
return Type.Number({ description });
|
|
116
|
-
case "integer":
|
|
117
|
-
return Type.Integer({ description });
|
|
118
|
-
case "boolean":
|
|
119
|
-
return Type.Boolean({ description });
|
|
120
|
-
case "array":
|
|
121
|
-
return Type.Array(Type.String(), { description });
|
|
122
|
-
default:
|
|
123
|
-
return Type.Unknown({ description });
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
150
|
function buildParameters(inputSchema) {
|
|
127
151
|
const properties = inputSchema.properties;
|
|
128
152
|
if (!properties || typeof properties !== "object") {
|
|
@@ -130,24 +154,11 @@ function buildParameters(inputSchema) {
|
|
|
130
154
|
args: Type.Record(Type.String(), Type.Unknown(), { description: "Tool arguments as key-value pairs" }),
|
|
131
155
|
});
|
|
132
156
|
}
|
|
133
|
-
|
|
134
|
-
? inputSchema.required.filter((key) => typeof key === "string")
|
|
135
|
-
: [];
|
|
136
|
-
const mapped = {};
|
|
137
|
-
for (const [key, property] of Object.entries(properties)) {
|
|
138
|
-
if (!isMappableSchema(property)) {
|
|
139
|
-
return Type.Object({
|
|
140
|
-
args: Type.Record(Type.String(), Type.Unknown(), { description: "Tool arguments as key-value pairs" }),
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
const field = mapPropertySchema(key, property);
|
|
144
|
-
mapped[key] = required.includes(key) ? field : Type.Optional(field);
|
|
145
|
-
}
|
|
146
|
-
return Type.Object(mapped);
|
|
157
|
+
return Type.Unsafe(inputSchema);
|
|
147
158
|
}
|
|
148
|
-
export async function createMcpToolDefinitions(baseUrl, getApiKey) {
|
|
159
|
+
export async function createMcpToolDefinitions(baseUrl, getApiKey, headers) {
|
|
149
160
|
const discoveryApiKey = await getApiKey();
|
|
150
|
-
const tools = await discoverMcpTools(baseUrl, discoveryApiKey);
|
|
161
|
+
const tools = await discoverMcpTools(baseUrl, discoveryApiKey, headers);
|
|
151
162
|
return tools.map((mcpTool) => {
|
|
152
163
|
const safeServer = sanitizeName(mcpTool.server_name);
|
|
153
164
|
const safeTool = sanitizeName(mcpTool.name);
|
|
@@ -157,6 +168,7 @@ export async function createMcpToolDefinitions(baseUrl, getApiKey) {
|
|
|
157
168
|
label: `${mcpTool.server_name}: ${mcpTool.name}`,
|
|
158
169
|
description: `${mcpTool.description} (via ${mcpTool.server_name} MCP server)`,
|
|
159
170
|
promptSnippet: `${mcpTool.description} via ${mcpTool.server_name} MCP server`,
|
|
171
|
+
executionMode: "parallel",
|
|
160
172
|
parameters,
|
|
161
173
|
async execute(_toolCallId, params) {
|
|
162
174
|
const apiKey = await getApiKey();
|
|
@@ -164,7 +176,7 @@ export async function createMcpToolDefinitions(baseUrl, getApiKey) {
|
|
|
164
176
|
const args = Object.keys(rawParams).length === 1 && rawParams.args && typeof rawParams.args === "object"
|
|
165
177
|
? rawParams.args
|
|
166
178
|
: rawParams;
|
|
167
|
-
const text = await executeMcpTool(baseUrl, apiKey, mcpTool.server_id ?? mcpTool.server_name, mcpTool.name, args);
|
|
179
|
+
const text = await executeMcpTool(baseUrl, apiKey, mcpTool.server_id ?? mcpTool.server_name, mcpTool.name, args, headers);
|
|
168
180
|
return {
|
|
169
181
|
content: [{ type: "text", text }],
|
|
170
182
|
details: { server: mcpTool.server_name, serverId: mcpTool.server_id, tool: mcpTool.name },
|
package/dist/skills.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { LiteLLMSkill } from "./types.js";
|
|
3
|
-
export declare function listSkills(baseUrl: string, apiKey: string): Promise<LiteLLMSkill[]>;
|
|
3
|
+
export declare function listSkills(baseUrl: string, apiKey: string, headers?: Record<string, string>): Promise<LiteLLMSkill[]>;
|
|
4
4
|
export declare function createSkill(baseUrl: string, apiKey: string, input: {
|
|
5
5
|
name: string;
|
|
6
|
-
description
|
|
7
|
-
|
|
6
|
+
description?: string;
|
|
7
|
+
source?: Record<string, unknown>;
|
|
8
|
+
code?: string;
|
|
8
9
|
inputSchema?: Record<string, unknown>;
|
|
9
|
-
}): Promise<unknown>;
|
|
10
|
-
export declare function deleteSkill(baseUrl: string, apiKey: string, skillId: string): Promise<void>;
|
|
10
|
+
}, headers?: Record<string, string>): Promise<unknown>;
|
|
11
|
+
export declare function deleteSkill(baseUrl: string, apiKey: string, skillId: string, headers?: Record<string, string>): Promise<void>;
|
|
11
12
|
export declare function createSkillsPromptSection(skills: LiteLLMSkill[]): string | undefined;
|
|
12
|
-
export declare function createSkillToolDefinitions(baseUrl: string, getApiKey: () => Promise<string>): ToolDefinition[];
|
|
13
|
+
export declare function createSkillToolDefinitions(baseUrl: string, getApiKey: () => Promise<string>, headers?: Record<string, string>): ToolDefinition[];
|
|
13
14
|
export declare function resetSkillsCache(): void;
|
package/dist/skills.js
CHANGED
|
@@ -8,6 +8,8 @@ function getSkillsFromBody(body) {
|
|
|
8
8
|
return body;
|
|
9
9
|
if (body && typeof body === "object") {
|
|
10
10
|
const record = body;
|
|
11
|
+
if (Array.isArray(record.plugins))
|
|
12
|
+
return record.plugins;
|
|
11
13
|
if (Array.isArray(record.data))
|
|
12
14
|
return record.data;
|
|
13
15
|
if (Array.isArray(record.skills))
|
|
@@ -15,7 +17,7 @@ function getSkillsFromBody(body) {
|
|
|
15
17
|
}
|
|
16
18
|
return [];
|
|
17
19
|
}
|
|
18
|
-
export async function listSkills(baseUrl, apiKey) {
|
|
20
|
+
export async function listSkills(baseUrl, apiKey, headers) {
|
|
19
21
|
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
20
22
|
if (skillsCache &&
|
|
21
23
|
skillsCache.baseUrl === normalizedBaseUrl &&
|
|
@@ -23,47 +25,110 @@ export async function listSkills(baseUrl, apiKey) {
|
|
|
23
25
|
Date.now() - skillsCache.fetchedAt < SKILLS_CACHE_TTL_MS) {
|
|
24
26
|
return skillsCache.skills;
|
|
25
27
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
const fetchSkills = async (path) => {
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetch(`${normalizedBaseUrl}${path}`, {
|
|
31
|
+
headers: { ...headers, Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
32
|
+
signal: AbortSignal.timeout(10_000),
|
|
33
|
+
});
|
|
34
|
+
return response.ok ? getSkillsFromBody(await response.json()) : [];
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
32
37
|
return [];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const [skillHub, legacy] = await Promise.all([
|
|
41
|
+
fetchSkills("/claude-code/marketplace.json"),
|
|
42
|
+
fetchSkills("/v1/skills"),
|
|
43
|
+
]);
|
|
44
|
+
const skillHubNames = new Set(skillHub.map((skill) => skill.name));
|
|
45
|
+
const skills = [...skillHub, ...legacy.filter((skill) => !skillHubNames.has(skill.name))];
|
|
46
|
+
skillsCache = { baseUrl: normalizedBaseUrl, apiKey, fetchedAt: Date.now(), skills };
|
|
47
|
+
return skills;
|
|
40
48
|
}
|
|
41
|
-
export async function createSkill(baseUrl, apiKey, input) {
|
|
42
|
-
|
|
49
|
+
export async function createSkill(baseUrl, apiKey, input, headers) {
|
|
50
|
+
if (!input.source && !input.code)
|
|
51
|
+
throw new Error("code is required when source is omitted");
|
|
52
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
53
|
+
let usedSkillHub = input.source !== undefined;
|
|
54
|
+
const skillHubPayload = input.source
|
|
55
|
+
? {
|
|
56
|
+
name: input.name,
|
|
57
|
+
description: input.description,
|
|
58
|
+
source: input.source,
|
|
59
|
+
}
|
|
60
|
+
: {
|
|
61
|
+
name: input.name,
|
|
62
|
+
description: input.description,
|
|
63
|
+
code: input.code,
|
|
64
|
+
input_schema: input.inputSchema ?? { type: "object", properties: {} },
|
|
65
|
+
};
|
|
66
|
+
let response = await fetch(`${normalizedBaseUrl}${input.source ? "/claude-code/plugins" : "/v1/skills"}`, {
|
|
43
67
|
method: "POST",
|
|
44
68
|
headers: {
|
|
69
|
+
...headers,
|
|
45
70
|
Authorization: `Bearer ${apiKey}`,
|
|
46
71
|
"Content-Type": "application/json",
|
|
47
72
|
},
|
|
48
|
-
body: JSON.stringify(
|
|
49
|
-
name: input.name,
|
|
50
|
-
description: input.description,
|
|
51
|
-
code: input.code,
|
|
52
|
-
input_schema: input.inputSchema ?? { type: "object", properties: {} },
|
|
53
|
-
}),
|
|
73
|
+
body: JSON.stringify(skillHubPayload),
|
|
54
74
|
signal: AbortSignal.timeout(10_000),
|
|
55
75
|
});
|
|
76
|
+
if (input.source && response.status === 404) {
|
|
77
|
+
usedSkillHub = false;
|
|
78
|
+
response = await fetch(`${normalizedBaseUrl}/v1/skills`, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: {
|
|
81
|
+
...headers,
|
|
82
|
+
Authorization: `Bearer ${apiKey}`,
|
|
83
|
+
"Content-Type": "application/json",
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify({
|
|
86
|
+
name: input.name,
|
|
87
|
+
description: input.description,
|
|
88
|
+
code: input.code,
|
|
89
|
+
input_schema: input.inputSchema ?? { type: "object", properties: {} },
|
|
90
|
+
}),
|
|
91
|
+
signal: AbortSignal.timeout(10_000),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
56
94
|
if (!response.ok)
|
|
57
95
|
throw new Error(`LiteLLM skill create failed: HTTP ${response.status}`);
|
|
58
96
|
skillsCache = undefined;
|
|
97
|
+
if (usedSkillHub) {
|
|
98
|
+
const enableResponse = await fetch(`${normalizedBaseUrl}/claude-code/plugins/${encodeURIComponent(input.name)}/enable`, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: { ...headers, Authorization: `Bearer ${apiKey}` },
|
|
101
|
+
signal: AbortSignal.timeout(10_000),
|
|
102
|
+
});
|
|
103
|
+
if (!enableResponse.ok)
|
|
104
|
+
throw new Error(`LiteLLM skill enable failed: HTTP ${enableResponse.status}`);
|
|
105
|
+
}
|
|
59
106
|
return response.json().catch(() => ({}));
|
|
60
107
|
}
|
|
61
|
-
export async function deleteSkill(baseUrl, apiKey, skillId) {
|
|
62
|
-
const
|
|
108
|
+
export async function deleteSkill(baseUrl, apiKey, skillId, headers) {
|
|
109
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
110
|
+
let skillHubError;
|
|
111
|
+
const skillHubResponse = await fetch(`${normalizedBaseUrl}/claude-code/plugins/${encodeURIComponent(skillId)}`, {
|
|
63
112
|
method: "DELETE",
|
|
64
|
-
headers: { Authorization: `Bearer ${apiKey}
|
|
113
|
+
headers: { ...headers, Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
65
114
|
signal: AbortSignal.timeout(10_000),
|
|
115
|
+
}).catch((error) => {
|
|
116
|
+
skillHubError = error;
|
|
117
|
+
return undefined;
|
|
66
118
|
});
|
|
119
|
+
let response = skillHubResponse;
|
|
120
|
+
if (!response || response.status === 404 || response.status >= 500) {
|
|
121
|
+
response = await fetch(`${normalizedBaseUrl}/v1/skills/${encodeURIComponent(skillId)}`, {
|
|
122
|
+
method: "DELETE",
|
|
123
|
+
headers: { ...headers, Authorization: `Bearer ${apiKey}` },
|
|
124
|
+
signal: AbortSignal.timeout(10_000),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (response.status === 404 && skillHubResponse?.status !== 404) {
|
|
128
|
+
if (skillHubError instanceof Error)
|
|
129
|
+
throw skillHubError;
|
|
130
|
+
throw new Error(`LiteLLM skill delete failed: HTTP ${skillHubResponse?.status}`);
|
|
131
|
+
}
|
|
67
132
|
if (!response.ok && response.status !== 404)
|
|
68
133
|
throw new Error(`LiteLLM skill delete failed: HTTP ${response.status}`);
|
|
69
134
|
skillsCache = undefined;
|
|
@@ -92,14 +157,22 @@ function formatSkills(skills) {
|
|
|
92
157
|
}
|
|
93
158
|
const CreateSkillParams = Type.Object({
|
|
94
159
|
name: Type.String({ description: "Skill name" }),
|
|
95
|
-
description: Type.String({ description: "Skill description" }),
|
|
96
|
-
|
|
160
|
+
description: Type.Optional(Type.String({ description: "Skill description" })),
|
|
161
|
+
sourceJson: Type.Optional(Type.String({ description: "Optional Skill Hub source metadata JSON object" })),
|
|
162
|
+
code: Type.Optional(Type.String({ description: "Legacy Skills Gateway implementation or prompt code" })),
|
|
97
163
|
inputSchemaJson: Type.Optional(Type.String({ description: "Optional JSON Schema string for skill inputs" })),
|
|
98
164
|
});
|
|
99
165
|
const DeleteSkillParams = Type.Object({
|
|
100
166
|
skillId: Type.String({ description: "LiteLLM skill id" }),
|
|
101
167
|
});
|
|
102
|
-
|
|
168
|
+
function parseJsonObject(value, fieldName) {
|
|
169
|
+
const parsed = JSON.parse(value);
|
|
170
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
171
|
+
throw new Error(`${fieldName} must be a JSON object`);
|
|
172
|
+
}
|
|
173
|
+
return parsed;
|
|
174
|
+
}
|
|
175
|
+
export function createSkillToolDefinitions(baseUrl, getApiKey, headers) {
|
|
103
176
|
return [
|
|
104
177
|
defineTool({
|
|
105
178
|
name: "litellm_skill_list",
|
|
@@ -109,7 +182,7 @@ export function createSkillToolDefinitions(baseUrl, getApiKey) {
|
|
|
109
182
|
parameters: Type.Object({}),
|
|
110
183
|
async execute() {
|
|
111
184
|
const apiKey = await getApiKey();
|
|
112
|
-
const skills = await listSkills(baseUrl, apiKey);
|
|
185
|
+
const skills = await listSkills(baseUrl, apiKey, headers);
|
|
113
186
|
return { content: [{ type: "text", text: formatSkills(skills) }], details: { count: skills.length } };
|
|
114
187
|
},
|
|
115
188
|
}),
|
|
@@ -120,10 +193,17 @@ export function createSkillToolDefinitions(baseUrl, getApiKey) {
|
|
|
120
193
|
parameters: CreateSkillParams,
|
|
121
194
|
async execute(_toolCallId, params) {
|
|
122
195
|
const apiKey = await getApiKey();
|
|
196
|
+
const source = params.sourceJson ? parseJsonObject(params.sourceJson, "sourceJson") : undefined;
|
|
123
197
|
const inputSchema = params.inputSchemaJson
|
|
124
|
-
?
|
|
198
|
+
? parseJsonObject(params.inputSchemaJson, "inputSchemaJson")
|
|
125
199
|
: undefined;
|
|
126
|
-
const result = await createSkill(baseUrl, apiKey, {
|
|
200
|
+
const result = await createSkill(baseUrl, apiKey, {
|
|
201
|
+
name: params.name,
|
|
202
|
+
description: params.description,
|
|
203
|
+
source,
|
|
204
|
+
code: params.code,
|
|
205
|
+
inputSchema,
|
|
206
|
+
}, headers);
|
|
127
207
|
return { content: [{ type: "text", text: "LiteLLM skill created." }], details: { result } };
|
|
128
208
|
},
|
|
129
209
|
}),
|
|
@@ -134,7 +214,7 @@ export function createSkillToolDefinitions(baseUrl, getApiKey) {
|
|
|
134
214
|
parameters: DeleteSkillParams,
|
|
135
215
|
async execute(_toolCallId, params) {
|
|
136
216
|
const apiKey = await getApiKey();
|
|
137
|
-
await deleteSkill(baseUrl, apiKey, params.skillId);
|
|
217
|
+
await deleteSkill(baseUrl, apiKey, params.skillId, headers);
|
|
138
218
|
return { content: [{ type: "text", text: `LiteLLM skill deleted: ${params.skillId}` }], details: {} };
|
|
139
219
|
},
|
|
140
220
|
}),
|
package/dist/types.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type DiscoverySource = "model_info" | "models_list" | "health";
|
|
|
3
3
|
export interface CacheFile {
|
|
4
4
|
baseUrl: string;
|
|
5
5
|
apiKeyFingerprint: string;
|
|
6
|
+
headersFingerprint?: string;
|
|
6
7
|
fetchedAt: number;
|
|
7
8
|
source: DiscoverySource;
|
|
8
9
|
models: ProviderModelConfig[];
|
|
@@ -14,6 +15,7 @@ export interface DiscoveryResult {
|
|
|
14
15
|
export interface DiscoveryOptions {
|
|
15
16
|
timeoutMs?: number;
|
|
16
17
|
signal?: AbortSignal;
|
|
18
|
+
headers?: Record<string, string>;
|
|
17
19
|
}
|
|
18
20
|
export interface ModelInfoEntry {
|
|
19
21
|
model_name?: string;
|
|
@@ -75,6 +77,14 @@ export interface LiteLLMSkill {
|
|
|
75
77
|
name: string;
|
|
76
78
|
description?: string;
|
|
77
79
|
enabled?: boolean;
|
|
80
|
+
source?: Record<string, unknown>;
|
|
81
|
+
version?: string;
|
|
82
|
+
keywords?: string[];
|
|
83
|
+
domain?: string;
|
|
84
|
+
namespace?: string;
|
|
85
|
+
category?: string;
|
|
86
|
+
author?: string;
|
|
87
|
+
homepage?: string;
|
|
78
88
|
input_schema?: Record<string, unknown>;
|
|
79
89
|
code?: string;
|
|
80
90
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-provider-litellm",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.9",
|
|
4
4
|
"description": "LiteLLM proxy provider extension for Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"image": "https://raw.githubusercontent.com/balcsida/pi-provider-litellm/refs/heads/main/assets/pi_litellm_gallery.png"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
|
-
"clean": "
|
|
43
|
+
"clean": "rm -rf dist",
|
|
44
44
|
"build": "tsgo -p tsconfig.build.json",
|
|
45
45
|
"dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
|
|
46
46
|
"typecheck": "tsgo --noEmit",
|
|
@@ -64,14 +64,12 @@
|
|
|
64
64
|
"protobufjs": "8.2.1"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@earendil-works/pi-ai": "^0.
|
|
68
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
67
|
+
"@earendil-works/pi-ai": "^0.80.2",
|
|
68
|
+
"@earendil-works/pi-coding-agent": "^0.80.2",
|
|
69
69
|
"@biomejs/biome": "^2.4.15",
|
|
70
|
-
"@types/node": "^
|
|
71
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
72
|
-
"shx": "^0.4.0",
|
|
70
|
+
"@types/node": "^26.0.0",
|
|
71
|
+
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
73
72
|
"tsx": "^4.21.0",
|
|
74
|
-
"typescript": "^6.0.3",
|
|
75
73
|
"vitest": "^4.1.7"
|
|
76
74
|
}
|
|
77
75
|
}
|