pi-provider-litellm 2.2.1 → 3.0.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 +173 -25
- package/package.json +14 -19
- package/src/backend-identity.ts +100 -0
- package/src/cost.ts +52 -0
- package/src/discover.ts +1318 -0
- package/src/gcloud-token.ts +160 -0
- package/src/index.ts +2005 -0
- package/src/mcp-tools.ts +1341 -0
- package/src/model-groups.ts +947 -0
- package/src/protocols.ts +42 -0
- package/src/provider.ts +248 -0
- package/src/public-catalog.ts +272 -0
- package/src/skills.ts +275 -0
- package/src/thinking-levels.ts +35 -0
- package/src/types.ts +162 -0
- package/dist/cost.d.ts +0 -2
- package/dist/cost.js +0 -49
- package/dist/discover.d.ts +0 -12
- package/dist/discover.js +0 -333
- package/dist/gcloud-token-cli.d.ts +0 -1
- package/dist/gcloud-token-cli.js +0 -8
- package/dist/gcloud-token.d.ts +0 -7
- package/dist/gcloud-token.js +0 -133
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -1184
- package/dist/litellm.d.ts +0 -1
- package/dist/litellm.js +0 -10
- package/dist/mcp-tools.d.ts +0 -5
- package/dist/mcp-tools.js +0 -181
- package/dist/provider.d.ts +0 -16
- package/dist/provider.js +0 -40
- package/dist/skills.d.ts +0 -14
- package/dist/skills.js +0 -225
- package/dist/types.d.ts +0 -100
- package/dist/types.js +0 -1
package/dist/litellm.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function getSessionIdFromFile(sessionFile?: string): string | undefined;
|
package/dist/litellm.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { basename } from "node:path";
|
|
2
|
-
export function getSessionIdFromFile(sessionFile) {
|
|
3
|
-
if (!sessionFile)
|
|
4
|
-
return undefined;
|
|
5
|
-
const filename = basename(sessionFile).replace(/\.jsonl$/i, "");
|
|
6
|
-
if (!filename)
|
|
7
|
-
return undefined;
|
|
8
|
-
const uuidMatch = filename.match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i);
|
|
9
|
-
return uuidMatch?.[1] ?? filename;
|
|
10
|
-
}
|
package/dist/mcp-tools.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { LiteLLMMcpTool, LiteLLMRuntimeAuth } from "./types.js";
|
|
3
|
-
export declare function discoverMcpTools(baseUrl: string, apiKey: string, headers?: Record<string, string>, onProgress?: (message: string) => void, parentSignal?: AbortSignal, allowInsecureHttp?: boolean): Promise<LiteLLMMcpTool[]>;
|
|
4
|
-
export declare function executeMcpTool(baseUrl: string, apiKey: string, serverId: string, toolName: string, args: Record<string, unknown>, headers?: Record<string, string>, allowInsecureHttp?: boolean): Promise<string>;
|
|
5
|
-
export declare function createMcpToolDefinitions(getAuth: (ctx?: ExtensionContext) => Promise<LiteLLMRuntimeAuth>, onProgress?: (message: string) => void, signal?: AbortSignal): Promise<ToolDefinition[]>;
|
package/dist/mcp-tools.js
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { Type } from "@earendil-works/pi-ai";
|
|
2
|
-
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { normalizeBaseUrl } from "./discover.js";
|
|
4
|
-
const LIST_TIMEOUT_MS = 10_000;
|
|
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]);
|
|
9
|
-
function asRecord(value) {
|
|
10
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
11
|
-
}
|
|
12
|
-
function sleep(ms) {
|
|
13
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
14
|
-
}
|
|
15
|
-
function formatMcpToolError(toolName, serverId, error) {
|
|
16
|
-
return `Error calling ${toolName} on ${serverId}: ${error instanceof Error ? error.message : String(error)}`;
|
|
17
|
-
}
|
|
18
|
-
function isRetryableTransportError(error) {
|
|
19
|
-
if (!(error instanceof Error))
|
|
20
|
-
return false;
|
|
21
|
-
if (error.name === "AbortError" || error.name === "TimeoutError")
|
|
22
|
-
return true;
|
|
23
|
-
if (error.message.startsWith("Timed out after "))
|
|
24
|
-
return true;
|
|
25
|
-
if (error.name !== "TypeError")
|
|
26
|
-
return false;
|
|
27
|
-
return /fetch failed|failed to fetch|network|terminated|ECONNRESET|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|socket/i.test(error.message);
|
|
28
|
-
}
|
|
29
|
-
function stringValue(value) {
|
|
30
|
-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
31
|
-
}
|
|
32
|
-
function normalizeMcpTool(value) {
|
|
33
|
-
const raw = asRecord(value);
|
|
34
|
-
if (!raw)
|
|
35
|
-
return undefined;
|
|
36
|
-
const name = stringValue(raw.name);
|
|
37
|
-
const serverName = stringValue(raw.mcp_info?.server_name) ??
|
|
38
|
-
stringValue(raw.server_name) ??
|
|
39
|
-
stringValue(raw.mcp_info?.server_id) ??
|
|
40
|
-
stringValue(raw.server_id);
|
|
41
|
-
if (!name || !serverName)
|
|
42
|
-
return undefined;
|
|
43
|
-
const inputSchema = asRecord(raw.inputSchema) ?? asRecord(raw.input_schema) ?? { type: "object", properties: {} };
|
|
44
|
-
return {
|
|
45
|
-
name,
|
|
46
|
-
server_name: serverName,
|
|
47
|
-
server_id: stringValue(raw.mcp_info?.server_id) ?? stringValue(raw.server_id) ?? serverName,
|
|
48
|
-
description: stringValue(raw.description) ?? name,
|
|
49
|
-
input_schema: inputSchema,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
export async function discoverMcpTools(baseUrl, apiKey, headers, onProgress, parentSignal, allowInsecureHttp = false) {
|
|
53
|
-
onProgress?.("Discovering MCP tools from server...");
|
|
54
|
-
try {
|
|
55
|
-
onProgress?.("Querying MCP tools/list endpoint...");
|
|
56
|
-
const response = await fetch(`${normalizeBaseUrl(baseUrl, allowInsecureHttp)}/mcp-rest/tools/list`, {
|
|
57
|
-
headers: {
|
|
58
|
-
...headers,
|
|
59
|
-
Authorization: `Bearer ${apiKey}`,
|
|
60
|
-
"Content-Type": "application/json",
|
|
61
|
-
},
|
|
62
|
-
signal: parentSignal
|
|
63
|
-
? AbortSignal.any([parentSignal, AbortSignal.timeout(LIST_TIMEOUT_MS)])
|
|
64
|
-
: AbortSignal.timeout(LIST_TIMEOUT_MS),
|
|
65
|
-
});
|
|
66
|
-
if (!response.ok) {
|
|
67
|
-
onProgress?.(`MCP tools discovery failed with status ${response.status}`);
|
|
68
|
-
throw new Error(`HTTP ${response.status}`);
|
|
69
|
-
}
|
|
70
|
-
const body = (await response.json());
|
|
71
|
-
const bodyRecord = asRecord(body);
|
|
72
|
-
const rawTools = Array.isArray(body) ? body : Array.isArray(bodyRecord?.tools) ? bodyRecord.tools : [];
|
|
73
|
-
onProgress?.(`Found ${rawTools.length} raw MCP tools, normalizing...`);
|
|
74
|
-
const tools = rawTools.map(normalizeMcpTool).filter((tool) => tool !== undefined);
|
|
75
|
-
onProgress?.(`Successfully discovered ${tools.length} MCP tools`);
|
|
76
|
-
return tools;
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
onProgress?.("MCP tools discovery encountered an error");
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
export async function executeMcpTool(baseUrl, apiKey, serverId, toolName, args, headers, allowInsecureHttp = false) {
|
|
84
|
-
for (let attempt = 0; attempt <= MCP_RETRY_ATTEMPTS; attempt++) {
|
|
85
|
-
const result = await executeMcpToolOnce(baseUrl, apiKey, serverId, toolName, args, headers, allowInsecureHttp);
|
|
86
|
-
if (attempt < MCP_RETRY_ATTEMPTS && result.retryable) {
|
|
87
|
-
await sleep(MCP_RETRY_DELAY_MS);
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
return result.text;
|
|
91
|
-
}
|
|
92
|
-
return `Error calling ${toolName} on ${serverId}: retry attempts exhausted`;
|
|
93
|
-
}
|
|
94
|
-
async function executeMcpToolOnce(baseUrl, apiKey, serverId, toolName, args, headers, allowInsecureHttp = false) {
|
|
95
|
-
try {
|
|
96
|
-
let response;
|
|
97
|
-
try {
|
|
98
|
-
response = await fetch(`${normalizeBaseUrl(baseUrl, allowInsecureHttp)}/mcp-rest/tools/call`, {
|
|
99
|
-
method: "POST",
|
|
100
|
-
headers: {
|
|
101
|
-
...headers,
|
|
102
|
-
Authorization: `Bearer ${apiKey}`,
|
|
103
|
-
"Content-Type": "application/json",
|
|
104
|
-
},
|
|
105
|
-
body: JSON.stringify({ server_id: serverId, name: toolName, arguments: args }),
|
|
106
|
-
signal: AbortSignal.timeout(CALL_TIMEOUT_MS),
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
return {
|
|
111
|
-
text: formatMcpToolError(toolName, serverId, error),
|
|
112
|
-
retryable: isRetryableTransportError(error),
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
if (!response.ok) {
|
|
116
|
-
return {
|
|
117
|
-
text: `Error calling ${toolName} on ${serverId}: HTTP ${response.status}`,
|
|
118
|
-
retryable: RETRYABLE_HTTP_STATUS.has(response.status),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
try {
|
|
122
|
-
const body = (await response.json());
|
|
123
|
-
const bodyRecord = asRecord(body);
|
|
124
|
-
return {
|
|
125
|
-
text: JSON.stringify(bodyRecord && "result" in bodyRecord ? bodyRecord.result : body, null, 2),
|
|
126
|
-
retryable: false,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
catch (error) {
|
|
130
|
-
return { text: formatMcpToolError(toolName, serverId, error), retryable: false };
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
return { text: formatMcpToolError(toolName, serverId, error), retryable: false };
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
function sanitizeName(name) {
|
|
138
|
-
const sanitized = name
|
|
139
|
-
.toLowerCase()
|
|
140
|
-
.replace(/[^a-z0-9]+/g, "_")
|
|
141
|
-
.replace(/^_+|_+$/g, "");
|
|
142
|
-
return sanitized || "tool";
|
|
143
|
-
}
|
|
144
|
-
function buildParameters(inputSchema) {
|
|
145
|
-
const properties = inputSchema.properties;
|
|
146
|
-
if (!properties || typeof properties !== "object") {
|
|
147
|
-
return Type.Object({
|
|
148
|
-
args: Type.Record(Type.String(), Type.Unknown(), { description: "Tool arguments as key-value pairs" }),
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
return Type.Unsafe(inputSchema);
|
|
152
|
-
}
|
|
153
|
-
export async function createMcpToolDefinitions(getAuth, onProgress, signal) {
|
|
154
|
-
const discoveryAuth = await getAuth();
|
|
155
|
-
const tools = await discoverMcpTools(discoveryAuth.baseUrl, discoveryAuth.apiKey, discoveryAuth.headers, onProgress, signal, discoveryAuth.allowInsecureHttp);
|
|
156
|
-
return tools.map((mcpTool) => {
|
|
157
|
-
const safeServer = sanitizeName(mcpTool.server_name);
|
|
158
|
-
const safeTool = sanitizeName(mcpTool.name);
|
|
159
|
-
const parameters = buildParameters(mcpTool.input_schema);
|
|
160
|
-
return defineTool({
|
|
161
|
-
name: `mcp_${safeServer}_${safeTool}`,
|
|
162
|
-
label: `${mcpTool.server_name}: ${mcpTool.name}`,
|
|
163
|
-
description: `${mcpTool.description} (via ${mcpTool.server_name} MCP server)`,
|
|
164
|
-
promptSnippet: `${mcpTool.description} via ${mcpTool.server_name} MCP server`,
|
|
165
|
-
executionMode: "parallel",
|
|
166
|
-
parameters,
|
|
167
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
168
|
-
const auth = await getAuth(ctx);
|
|
169
|
-
const rawParams = params;
|
|
170
|
-
const args = Object.keys(rawParams).length === 1 && rawParams.args && typeof rawParams.args === "object"
|
|
171
|
-
? rawParams.args
|
|
172
|
-
: rawParams;
|
|
173
|
-
const text = await executeMcpTool(auth.baseUrl, auth.apiKey, mcpTool.server_id ?? mcpTool.server_name, mcpTool.name, args, auth.headers, auth.allowInsecureHttp);
|
|
174
|
-
return {
|
|
175
|
-
content: [{ type: "text", text }],
|
|
176
|
-
details: { server: mcpTool.server_name, serverId: mcpTool.server_id, tool: mcpTool.name },
|
|
177
|
-
};
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
}
|
package/dist/provider.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { type Credential, type Model, type Provider, type ProviderAuth } from "@earendil-works/pi-ai";
|
|
2
|
-
import type { DiscoveredModel, DiscoveryResult, LiteLLMApi } from "./types.js";
|
|
3
|
-
export type LiteLLMProviderOptions = {
|
|
4
|
-
id: string;
|
|
5
|
-
name: string;
|
|
6
|
-
baseUrl: string;
|
|
7
|
-
auth: ProviderAuth;
|
|
8
|
-
/** Catalog discovered during activation. Pi's startup refresh never allows network access, so
|
|
9
|
-
* without a seed the provider stays empty until the user opens /model. */
|
|
10
|
-
models?: readonly Model<LiteLLMApi>[];
|
|
11
|
-
discover(credential: Credential, signal?: AbortSignal): Promise<DiscoveryResult & {
|
|
12
|
-
baseUrl?: string;
|
|
13
|
-
}>;
|
|
14
|
-
};
|
|
15
|
-
export declare function toNativeModels(provider: string, baseUrl: string, models: readonly DiscoveredModel[]): Model<LiteLLMApi>[];
|
|
16
|
-
export declare function createLiteLLMProvider(options: LiteLLMProviderOptions): Provider<LiteLLMApi>;
|
package/dist/provider.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { createProvider } from "@earendil-works/pi-ai";
|
|
2
|
-
import { openAICompletionsApi, openAIResponsesApi } from "@earendil-works/pi-ai/compat";
|
|
3
|
-
import { enrichCachedModel } from "./discover.js";
|
|
4
|
-
export function toNativeModels(provider, baseUrl, models) {
|
|
5
|
-
return models.map((model) => ({
|
|
6
|
-
...model,
|
|
7
|
-
provider,
|
|
8
|
-
api: model.api ?? "openai-completions",
|
|
9
|
-
baseUrl,
|
|
10
|
-
}));
|
|
11
|
-
}
|
|
12
|
-
export function createLiteLLMProvider(options) {
|
|
13
|
-
const provider = createProvider({
|
|
14
|
-
id: options.id,
|
|
15
|
-
name: options.name,
|
|
16
|
-
baseUrl: options.baseUrl,
|
|
17
|
-
auth: options.auth,
|
|
18
|
-
models: options.models ?? [],
|
|
19
|
-
async fetchModels(context) {
|
|
20
|
-
if (!context.credential)
|
|
21
|
-
throw new Error("LiteLLM model discovery requires a credential");
|
|
22
|
-
const result = await options.discover(context.credential, context.signal);
|
|
23
|
-
return toNativeModels(options.id, result.baseUrl ?? options.baseUrl, result.models);
|
|
24
|
-
},
|
|
25
|
-
api: {
|
|
26
|
-
"openai-completions": openAICompletionsApi(),
|
|
27
|
-
"openai-responses": openAIResponsesApi(),
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
const refreshModels = provider.refreshModels;
|
|
31
|
-
if (!refreshModels)
|
|
32
|
-
return provider;
|
|
33
|
-
return {
|
|
34
|
-
...provider,
|
|
35
|
-
refreshModels: (context) => refreshModels({
|
|
36
|
-
...context,
|
|
37
|
-
stored: context.stored && { ...context.stored, models: context.stored.models.map(enrichCachedModel) },
|
|
38
|
-
}),
|
|
39
|
-
};
|
|
40
|
-
}
|
package/dist/skills.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { LiteLLMRuntimeAuth, LiteLLMSkill } from "./types.js";
|
|
3
|
-
export declare function listSkills(baseUrl: string, apiKey: string, headers?: Record<string, string>, allowInsecureHttp?: boolean): Promise<LiteLLMSkill[]>;
|
|
4
|
-
export declare function createSkill(baseUrl: string, apiKey: string, input: {
|
|
5
|
-
name: string;
|
|
6
|
-
description?: string;
|
|
7
|
-
source?: Record<string, unknown>;
|
|
8
|
-
code?: string;
|
|
9
|
-
inputSchema?: Record<string, unknown>;
|
|
10
|
-
}, headers?: Record<string, string>, allowInsecureHttp?: boolean): Promise<unknown>;
|
|
11
|
-
export declare function deleteSkill(baseUrl: string, apiKey: string, skillId: string, headers?: Record<string, string>, allowInsecureHttp?: boolean): Promise<void>;
|
|
12
|
-
export declare function createSkillsPromptSection(skills: LiteLLMSkill[]): string | undefined;
|
|
13
|
-
export declare function createSkillToolDefinitions(getAuth: (ctx?: ExtensionContext) => Promise<LiteLLMRuntimeAuth>): ToolDefinition[];
|
|
14
|
-
export declare function resetSkillsCache(): void;
|
package/dist/skills.js
DELETED
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import { Type } from "@earendil-works/pi-ai";
|
|
2
|
-
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { normalizeBaseUrl } from "./discover.js";
|
|
4
|
-
const SKILLS_CACHE_TTL_MS = 60_000;
|
|
5
|
-
let skillsCache;
|
|
6
|
-
function getSkillsFromBody(body) {
|
|
7
|
-
if (Array.isArray(body))
|
|
8
|
-
return body;
|
|
9
|
-
if (body && typeof body === "object") {
|
|
10
|
-
const record = body;
|
|
11
|
-
if (Array.isArray(record.plugins))
|
|
12
|
-
return record.plugins;
|
|
13
|
-
if (Array.isArray(record.data))
|
|
14
|
-
return record.data;
|
|
15
|
-
if (Array.isArray(record.skills))
|
|
16
|
-
return record.skills;
|
|
17
|
-
}
|
|
18
|
-
return [];
|
|
19
|
-
}
|
|
20
|
-
export async function listSkills(baseUrl, apiKey, headers, allowInsecureHttp = false) {
|
|
21
|
-
const normalizedBaseUrl = normalizeBaseUrl(baseUrl, allowInsecureHttp);
|
|
22
|
-
if (skillsCache &&
|
|
23
|
-
skillsCache.baseUrl === normalizedBaseUrl &&
|
|
24
|
-
skillsCache.apiKey === apiKey &&
|
|
25
|
-
Date.now() - skillsCache.fetchedAt < SKILLS_CACHE_TTL_MS) {
|
|
26
|
-
return skillsCache.skills;
|
|
27
|
-
}
|
|
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 {
|
|
37
|
-
return [];
|
|
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;
|
|
48
|
-
}
|
|
49
|
-
export async function createSkill(baseUrl, apiKey, input, headers, allowInsecureHttp = false) {
|
|
50
|
-
if (!input.source && !input.code)
|
|
51
|
-
throw new Error("code is required when source is omitted");
|
|
52
|
-
const normalizedBaseUrl = normalizeBaseUrl(baseUrl, allowInsecureHttp);
|
|
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"}`, {
|
|
67
|
-
method: "POST",
|
|
68
|
-
headers: {
|
|
69
|
-
...headers,
|
|
70
|
-
Authorization: `Bearer ${apiKey}`,
|
|
71
|
-
"Content-Type": "application/json",
|
|
72
|
-
},
|
|
73
|
-
body: JSON.stringify(skillHubPayload),
|
|
74
|
-
signal: AbortSignal.timeout(10_000),
|
|
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
|
-
}
|
|
94
|
-
if (!response.ok)
|
|
95
|
-
throw new Error(`LiteLLM skill create failed: HTTP ${response.status}`);
|
|
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
|
-
}
|
|
106
|
-
return response.json().catch(() => ({}));
|
|
107
|
-
}
|
|
108
|
-
export async function deleteSkill(baseUrl, apiKey, skillId, headers, allowInsecureHttp = false) {
|
|
109
|
-
const normalizedBaseUrl = normalizeBaseUrl(baseUrl, allowInsecureHttp);
|
|
110
|
-
let skillHubError;
|
|
111
|
-
const skillHubResponse = await fetch(`${normalizedBaseUrl}/claude-code/plugins/${encodeURIComponent(skillId)}`, {
|
|
112
|
-
method: "DELETE",
|
|
113
|
-
headers: { ...headers, Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
114
|
-
signal: AbortSignal.timeout(10_000),
|
|
115
|
-
}).catch((error) => {
|
|
116
|
-
skillHubError = error;
|
|
117
|
-
return undefined;
|
|
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
|
-
}
|
|
132
|
-
if (!response.ok && response.status !== 404)
|
|
133
|
-
throw new Error(`LiteLLM skill delete failed: HTTP ${response.status}`);
|
|
134
|
-
skillsCache = undefined;
|
|
135
|
-
}
|
|
136
|
-
function escapeXml(value) {
|
|
137
|
-
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
138
|
-
}
|
|
139
|
-
export function createSkillsPromptSection(skills) {
|
|
140
|
-
const enabled = skills.filter((skill) => skill.enabled !== false && skill.name);
|
|
141
|
-
if (enabled.length === 0)
|
|
142
|
-
return undefined;
|
|
143
|
-
const body = enabled
|
|
144
|
-
.map((skill) => {
|
|
145
|
-
const description = skill.description?.trim() || "No description provided.";
|
|
146
|
-
return `<skill name="${escapeXml(skill.name)}">\n${escapeXml(description)}\n</skill>`;
|
|
147
|
-
})
|
|
148
|
-
.join("\n");
|
|
149
|
-
return `LiteLLM Skills Gateway provided these active skills. Use them as additional guidance when relevant.\n<litellm_skills>\n${body}\n</litellm_skills>`;
|
|
150
|
-
}
|
|
151
|
-
function formatSkills(skills) {
|
|
152
|
-
if (skills.length === 0)
|
|
153
|
-
return "No LiteLLM skills are registered.";
|
|
154
|
-
return skills
|
|
155
|
-
.map((skill) => `- ${skill.name}${skill.enabled === false ? " (disabled)" : ""}: ${skill.description ?? ""}`)
|
|
156
|
-
.join("\n");
|
|
157
|
-
}
|
|
158
|
-
const CreateSkillParams = Type.Object({
|
|
159
|
-
name: Type.String({ description: "Skill name" }),
|
|
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" })),
|
|
163
|
-
inputSchemaJson: Type.Optional(Type.String({ description: "Optional JSON Schema string for skill inputs" })),
|
|
164
|
-
});
|
|
165
|
-
const DeleteSkillParams = Type.Object({
|
|
166
|
-
skillId: Type.String({ description: "LiteLLM skill id" }),
|
|
167
|
-
});
|
|
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(getAuth) {
|
|
176
|
-
return [
|
|
177
|
-
defineTool({
|
|
178
|
-
name: "litellm_skill_list",
|
|
179
|
-
label: "LiteLLM Skills",
|
|
180
|
-
description: "List skills registered on the LiteLLM proxy Skills Gateway.",
|
|
181
|
-
promptSnippet: "List LiteLLM Skills Gateway skills",
|
|
182
|
-
parameters: Type.Object({}),
|
|
183
|
-
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
184
|
-
const auth = await getAuth(ctx);
|
|
185
|
-
const skills = await listSkills(auth.baseUrl, auth.apiKey, auth.headers, auth.allowInsecureHttp);
|
|
186
|
-
return { content: [{ type: "text", text: formatSkills(skills) }], details: { count: skills.length } };
|
|
187
|
-
},
|
|
188
|
-
}),
|
|
189
|
-
defineTool({
|
|
190
|
-
name: "litellm_skill_create",
|
|
191
|
-
label: "Create LiteLLM Skill",
|
|
192
|
-
description: "Create a skill on the LiteLLM proxy Skills Gateway.",
|
|
193
|
-
parameters: CreateSkillParams,
|
|
194
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
195
|
-
const auth = await getAuth(ctx);
|
|
196
|
-
const source = params.sourceJson ? parseJsonObject(params.sourceJson, "sourceJson") : undefined;
|
|
197
|
-
const inputSchema = params.inputSchemaJson
|
|
198
|
-
? parseJsonObject(params.inputSchemaJson, "inputSchemaJson")
|
|
199
|
-
: undefined;
|
|
200
|
-
const result = await createSkill(auth.baseUrl, auth.apiKey, {
|
|
201
|
-
name: params.name,
|
|
202
|
-
description: params.description,
|
|
203
|
-
source,
|
|
204
|
-
code: params.code,
|
|
205
|
-
inputSchema,
|
|
206
|
-
}, auth.headers, auth.allowInsecureHttp);
|
|
207
|
-
return { content: [{ type: "text", text: "LiteLLM skill created." }], details: { result } };
|
|
208
|
-
},
|
|
209
|
-
}),
|
|
210
|
-
defineTool({
|
|
211
|
-
name: "litellm_skill_delete",
|
|
212
|
-
label: "Delete LiteLLM Skill",
|
|
213
|
-
description: "Delete a skill from the LiteLLM proxy Skills Gateway.",
|
|
214
|
-
parameters: DeleteSkillParams,
|
|
215
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
216
|
-
const auth = await getAuth(ctx);
|
|
217
|
-
await deleteSkill(auth.baseUrl, auth.apiKey, params.skillId, auth.headers, auth.allowInsecureHttp);
|
|
218
|
-
return { content: [{ type: "text", text: `LiteLLM skill deleted: ${params.skillId}` }], details: {} };
|
|
219
|
-
},
|
|
220
|
-
}),
|
|
221
|
-
];
|
|
222
|
-
}
|
|
223
|
-
export function resetSkillsCache() {
|
|
224
|
-
skillsCache = undefined;
|
|
225
|
-
}
|
package/dist/types.d.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import type { Model } from "@earendil-works/pi-ai";
|
|
2
|
-
export type DiscoverySource = "model_info" | "models_list" | "health";
|
|
3
|
-
export type LiteLLMApi = "openai-completions" | "openai-responses";
|
|
4
|
-
export type LiteLLMRuntimeAuth = {
|
|
5
|
-
baseUrl: string;
|
|
6
|
-
apiKey: string;
|
|
7
|
-
headers?: Record<string, string>;
|
|
8
|
-
allowInsecureHttp?: boolean;
|
|
9
|
-
};
|
|
10
|
-
export type DiscoveredModel = Omit<Model<"openai-completions">, "provider" | "api" | "baseUrl"> & {
|
|
11
|
-
api?: LiteLLMApi;
|
|
12
|
-
};
|
|
13
|
-
export interface DiscoveryResult {
|
|
14
|
-
models: DiscoveredModel[];
|
|
15
|
-
source: DiscoverySource;
|
|
16
|
-
}
|
|
17
|
-
export interface DiscoveryOptions {
|
|
18
|
-
timeoutMs?: number;
|
|
19
|
-
signal?: AbortSignal;
|
|
20
|
-
headers?: Record<string, string>;
|
|
21
|
-
allowInsecureHttp?: boolean;
|
|
22
|
-
}
|
|
23
|
-
export interface ModelInfoEntry {
|
|
24
|
-
model_name?: string;
|
|
25
|
-
model_info?: {
|
|
26
|
-
mode?: string | null;
|
|
27
|
-
input_cost_per_token?: number;
|
|
28
|
-
output_cost_per_token?: number;
|
|
29
|
-
cache_read_input_token_cost?: number;
|
|
30
|
-
cache_creation_input_token_cost?: number;
|
|
31
|
-
max_input_tokens?: number;
|
|
32
|
-
max_output_tokens?: number;
|
|
33
|
-
supports_reasoning?: boolean;
|
|
34
|
-
supports_none_reasoning_effort?: boolean;
|
|
35
|
-
supports_minimal_reasoning_effort?: boolean;
|
|
36
|
-
supports_low_reasoning_effort?: boolean;
|
|
37
|
-
supports_medium_reasoning_effort?: boolean;
|
|
38
|
-
supports_high_reasoning_effort?: boolean;
|
|
39
|
-
supports_xhigh_reasoning_effort?: boolean;
|
|
40
|
-
supports_max_reasoning_effort?: boolean;
|
|
41
|
-
supports_vision?: boolean;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
export interface ModelInfoResponse {
|
|
45
|
-
data?: ModelInfoEntry[];
|
|
46
|
-
}
|
|
47
|
-
export interface HealthModelEntry {
|
|
48
|
-
model?: string;
|
|
49
|
-
model_id?: string;
|
|
50
|
-
api_base?: string;
|
|
51
|
-
}
|
|
52
|
-
export interface HealthResponse {
|
|
53
|
-
healthy_endpoints?: HealthModelEntry[];
|
|
54
|
-
}
|
|
55
|
-
export interface ModelsListEntry {
|
|
56
|
-
id?: string;
|
|
57
|
-
owned_by?: string;
|
|
58
|
-
}
|
|
59
|
-
export interface ModelsListResponse {
|
|
60
|
-
data?: ModelsListEntry[];
|
|
61
|
-
}
|
|
62
|
-
export type AuthFileEntry = {
|
|
63
|
-
type: "oauth";
|
|
64
|
-
access: string;
|
|
65
|
-
refresh: string;
|
|
66
|
-
expires: number;
|
|
67
|
-
baseUrl?: string;
|
|
68
|
-
} | {
|
|
69
|
-
type: "api_key";
|
|
70
|
-
key: string;
|
|
71
|
-
};
|
|
72
|
-
export interface ResolvedCredentials {
|
|
73
|
-
baseUrl?: string;
|
|
74
|
-
apiKey?: string;
|
|
75
|
-
apiKeyFingerprint?: string;
|
|
76
|
-
apiKeyConfig?: string;
|
|
77
|
-
}
|
|
78
|
-
export interface LiteLLMMcpTool {
|
|
79
|
-
name: string;
|
|
80
|
-
server_name: string;
|
|
81
|
-
server_id?: string;
|
|
82
|
-
description: string;
|
|
83
|
-
input_schema: Record<string, unknown>;
|
|
84
|
-
}
|
|
85
|
-
export interface LiteLLMSkill {
|
|
86
|
-
id?: string;
|
|
87
|
-
name: string;
|
|
88
|
-
description?: string;
|
|
89
|
-
enabled?: boolean;
|
|
90
|
-
source?: Record<string, unknown>;
|
|
91
|
-
version?: string;
|
|
92
|
-
keywords?: string[];
|
|
93
|
-
domain?: string;
|
|
94
|
-
namespace?: string;
|
|
95
|
-
category?: string;
|
|
96
|
-
author?: string;
|
|
97
|
-
homepage?: string;
|
|
98
|
-
input_schema?: Record<string, unknown>;
|
|
99
|
-
code?: string;
|
|
100
|
-
}
|
package/dist/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|