pi-provider-litellm 2.2.0 → 2.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 +26 -1
- package/dist/discover.d.ts +2 -2
- package/dist/discover.js +59 -12
- package/dist/index.js +87 -32
- package/dist/mcp-tools.d.ts +2 -2
- package/dist/mcp-tools.js +8 -8
- package/dist/provider.d.ts +4 -4
- package/dist/skills.d.ts +3 -3
- package/dist/skills.js +9 -9
- package/dist/types.d.ts +10 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -44,12 +44,14 @@ To configure an API key, run `/login`, choose `Sign in with an API key`, then ch
|
|
|
44
44
|
|
|
45
45
|
You'll be prompted for the base URL and API key. Credentials are persisted to `~/.pi/agent/auth.json`.
|
|
46
46
|
|
|
47
|
+
If a proxy URL is already known — from `providers.<name>.baseUrl`, `LITELLM_BASE_URL`, or a previous login — pi offers it as the first option instead of asking you to retype it. Pick `Enter a different URL…` to point at another proxy.
|
|
48
|
+
|
|
47
49
|
#### Enterprise SSO login
|
|
48
50
|
|
|
49
51
|
If your LiteLLM proxy requires SSO/OAuth authentication (enterprise deployments), you can authenticate through LiteLLM's CLI SSO flow:
|
|
50
52
|
|
|
51
53
|
1. Run `/login litellm` inside pi and select `Sign in with LiteLLM SSO`
|
|
52
|
-
2.
|
|
54
|
+
2. Confirm the offered proxy URL, or enter one if this is the first login
|
|
53
55
|
3. Pi displays a verification code and opens the LiteLLM SSO login URL automatically
|
|
54
56
|
4. Authenticate in the browser and confirm the displayed code
|
|
55
57
|
5. If your account belongs to multiple teams, select the team for this session in Pi
|
|
@@ -115,6 +117,7 @@ Provider fields:
|
|
|
115
117
|
| `headers` | `$LITELLM_HEADERS` for `litellm`; unset for aliases | JSON string env reference or inline object of request headers |
|
|
116
118
|
| `displayName` | provider name | Label shown in Pi UI |
|
|
117
119
|
| `enabled` | `true` | Set `false` to skip an alias |
|
|
120
|
+
| `allowInsecureHttp` | `false` | Set `true` to permit plaintext HTTP for this provider, for example `http://host.docker.internal`. Credentials and request data will not be encrypted. Loopback HTTP works without this setting. |
|
|
118
121
|
|
|
119
122
|
`/login litellm` and Google ADC token auth remain scoped to the default `litellm` provider. Aliases use their configured `apiKey` or manually stored auth entries matching the alias name.
|
|
120
123
|
|
|
@@ -235,6 +238,7 @@ Opening `/model` refreshes configured provider catalogs in the background using
|
|
|
235
238
|
| Symptom | Likely cause |
|
|
236
239
|
|---|---|
|
|
237
240
|
| "no credentials" warning at startup | Env vars not set and no OAuth credential — run `/login litellm` |
|
|
241
|
+
| `No models available` at startup, gone after a restart | A Pi startup race, not discovery — see [`No models available` at startup](#no-models-available-at-startup) |
|
|
238
242
|
| "discovered no models" | Proxy returned an empty list — check pi's startup log and verify `/model/info`, `/v1/models`, or `/health` responds |
|
|
239
243
|
| `/model/info` returning 401/403/404 | Expected behavior with virtual keys — extension falls back to `/v1/models` |
|
|
240
244
|
| Discovery times out | Increase `LITELLM_DISCOVERY_TIMEOUT_MS` or set `LITELLM_OFFLINE=1` to fall back on cached models |
|
|
@@ -247,6 +251,27 @@ Opening `/model` refreshes configured provider catalogs in the background using
|
|
|
247
251
|
| MCP tools not showing | Verify the proxy exposes `/mcp-rest/tools/list` and open `/model` after fixing the proxy |
|
|
248
252
|
| Skills not affecting prompts | Verify the proxy exposes `/claude-code/marketplace.json` or `/v1/skills` and returns enabled skills |
|
|
249
253
|
|
|
254
|
+
### `No models available` at startup
|
|
255
|
+
|
|
256
|
+
Pi 0.84.0 and later can finish startup before the provider availability snapshot is written, so the
|
|
257
|
+
initial model pick sees nothing even though discovery succeeded. The warning is intermittent and a
|
|
258
|
+
restart usually clears it. Nothing is wrong with the catalog: `/model` in the same session still
|
|
259
|
+
lists every discovered model, and picking one there fixes that session.
|
|
260
|
+
|
|
261
|
+
Scoping models keeps selection off that path, because Pi resolves the scope with its own awaited
|
|
262
|
+
availability pass before it picks a model. In `~/.pi/agent/settings.json`:
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{
|
|
266
|
+
"enabledModels": ["litellm/*"]
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Your `defaultProvider` and `defaultModel` still win as long as they match a pattern. `/model` then
|
|
271
|
+
opens on the scoped list, with a toggle inside the picker for the full catalog.
|
|
272
|
+
|
|
273
|
+
The extension cannot set this for you — Pi reads `enabledModels` before extensions activate.
|
|
274
|
+
|
|
250
275
|
## License
|
|
251
276
|
|
|
252
277
|
MIT — see [LICENSE](./LICENSE).
|
package/dist/discover.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { DiscoveredModel, DiscoveryOptions, DiscoveryResult } from "./types.js";
|
|
3
|
-
export declare function normalizeBaseUrl(input: string): string;
|
|
3
|
+
export declare function normalizeBaseUrl(input: string, allowInsecureHttp?: boolean): string;
|
|
4
4
|
export declare function isMoonshotModel(modelId: string): boolean;
|
|
5
5
|
export declare function isGpt55Model(modelId: string): boolean;
|
|
6
|
-
export declare function
|
|
6
|
+
export declare function emitsThinkTags(modelId: string): boolean;
|
|
7
7
|
export declare function buildCompat(modelId: string): DiscoveredModel["compat"];
|
|
8
8
|
export declare function enrichCachedModel(model: Model<Api>): Model<Api>;
|
|
9
9
|
export declare function discoverModels(baseUrl: string, apiKey: string, options?: DiscoveryOptions & {
|
package/dist/discover.js
CHANGED
|
@@ -4,11 +4,11 @@ const DEFAULT_TIMEOUT_MS = 5000;
|
|
|
4
4
|
const DEFAULT_CONTEXT_WINDOW = 128_000;
|
|
5
5
|
const DEFAULT_MAX_TOKENS = 16_384;
|
|
6
6
|
const KNOWN_PROVIDER_SET = new Set(getProviders());
|
|
7
|
-
export function normalizeBaseUrl(input) {
|
|
7
|
+
export function normalizeBaseUrl(input, allowInsecureHttp = false) {
|
|
8
8
|
const url = new URL(input);
|
|
9
9
|
const hostname = url.hostname.toLowerCase();
|
|
10
10
|
const loopback = hostname === "localhost" || hostname === "[::1]" || (isIP(hostname) === 4 && hostname.startsWith("127."));
|
|
11
|
-
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
|
|
11
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && (loopback || allowInsecureHttp))) {
|
|
12
12
|
throw new Error("LiteLLM base URL must use HTTPS except for loopback hosts");
|
|
13
13
|
}
|
|
14
14
|
return input.replace(/\/+$/, "").replace(/\/v1\/?$/i, "");
|
|
@@ -38,7 +38,36 @@ export function isMoonshotModel(modelId) {
|
|
|
38
38
|
export function isGpt55Model(modelId) {
|
|
39
39
|
return GPT55_MODEL_PATTERN.test(modelId);
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
const MOONSHOT_ROUTE_PROVIDERS = new Set(["moonshot", "moonshotai"]);
|
|
42
|
+
function isMoonshotRoute(entry) {
|
|
43
|
+
const params = entry.litellm_params;
|
|
44
|
+
if (!params)
|
|
45
|
+
return false;
|
|
46
|
+
const model = params.model?.trim();
|
|
47
|
+
if (!model)
|
|
48
|
+
return false;
|
|
49
|
+
const providers = [
|
|
50
|
+
params.custom_llm_provider?.trim(),
|
|
51
|
+
model.includes("/") ? model.split("/", 1)[0] : undefined,
|
|
52
|
+
].filter((provider) => Boolean(provider));
|
|
53
|
+
return providers.length > 0 && providers.every((provider) => MOONSHOT_ROUTE_PROVIDERS.has(provider.toLowerCase()));
|
|
54
|
+
}
|
|
55
|
+
function shouldSuppressReasoningContent(modelId, entry) {
|
|
56
|
+
const routeModelId = entry.litellm_params?.model;
|
|
57
|
+
return (isMoonshotRoute(entry) &&
|
|
58
|
+
!FORCED_THINKING_MODEL_PATTERN.test(modelId) &&
|
|
59
|
+
!(routeModelId && FORCED_THINKING_MODEL_PATTERN.test(routeModelId)));
|
|
60
|
+
}
|
|
61
|
+
function aggregateSuppressionEvidence(evidence) {
|
|
62
|
+
let hasEvidence = false;
|
|
63
|
+
for (const suppress of evidence) {
|
|
64
|
+
hasEvidence = true;
|
|
65
|
+
if (!suppress)
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return hasEvidence;
|
|
69
|
+
}
|
|
70
|
+
export function emitsThinkTags(modelId) {
|
|
42
71
|
return isMoonshotModel(modelId) && !FORCED_THINKING_MODEL_PATTERN.test(modelId);
|
|
43
72
|
}
|
|
44
73
|
export function buildCompat(modelId) {
|
|
@@ -172,7 +201,7 @@ function mapReasoningEfforts(info) {
|
|
|
172
201
|
.map(([level, value, supported]) => [level, supported ? value : null]));
|
|
173
202
|
return Object.keys(map).length > 0 ? map : undefined;
|
|
174
203
|
}
|
|
175
|
-
function mapFromModelInfo(entry) {
|
|
204
|
+
function mapFromModelInfo(entry, suppressReasoningContent = shouldSuppressReasoningContent(entry.model_name ?? "", entry)) {
|
|
176
205
|
const id = entry.model_name;
|
|
177
206
|
if (!id)
|
|
178
207
|
return undefined;
|
|
@@ -195,6 +224,7 @@ function mapFromModelInfo(entry) {
|
|
|
195
224
|
contextWindow: info.max_input_tokens ?? DEFAULT_CONTEXT_WINDOW,
|
|
196
225
|
maxTokens: info.max_output_tokens ?? DEFAULT_MAX_TOKENS,
|
|
197
226
|
compat: buildCompat(id),
|
|
227
|
+
...(suppressReasoningContent ? { suppressReasoningContent: true } : {}),
|
|
198
228
|
...(responsesMode ? { api: "openai-responses" } : {}),
|
|
199
229
|
};
|
|
200
230
|
}
|
|
@@ -266,32 +296,49 @@ async function discoverFromHealth(base, apiKey, options) {
|
|
|
266
296
|
return models.filter((model) => model !== undefined);
|
|
267
297
|
}
|
|
268
298
|
function deduplicateModels(models) {
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
299
|
+
const entries = new Map();
|
|
300
|
+
for (const model of models) {
|
|
301
|
+
const existing = entries.get(model.id);
|
|
302
|
+
if (existing) {
|
|
303
|
+
existing.suppressions.push(model.suppressReasoningContent === true);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
entries.set(model.id, { model, suppressions: [model.suppressReasoningContent === true] });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return [...entries.values()].map(({ model, suppressions }) => {
|
|
310
|
+
const deduplicated = { ...model };
|
|
311
|
+
if (aggregateSuppressionEvidence(suppressions))
|
|
312
|
+
deduplicated.suppressReasoningContent = true;
|
|
313
|
+
else
|
|
314
|
+
delete deduplicated.suppressReasoningContent;
|
|
315
|
+
return deduplicated;
|
|
275
316
|
});
|
|
276
317
|
}
|
|
277
318
|
export async function discoverModels(baseUrl, apiKey, options = {}) {
|
|
278
|
-
const base = normalizeBaseUrl(baseUrl);
|
|
319
|
+
const base = normalizeBaseUrl(baseUrl, options.allowInsecureHttp);
|
|
279
320
|
const progress = options.silent ? undefined : options.onProgress;
|
|
280
321
|
progress?.("Querying /model/info endpoint...");
|
|
281
322
|
const infoResult = await fetchJson(`${base}/model/info`, apiKey, options);
|
|
282
323
|
if (infoResult.ok) {
|
|
283
324
|
const entries = new Map();
|
|
325
|
+
const suppressionEvidence = new Map();
|
|
284
326
|
for (const entry of infoResult.data.data ?? []) {
|
|
285
327
|
if (!entry.model_name)
|
|
286
328
|
continue;
|
|
287
329
|
const previous = entries.get(entry.model_name);
|
|
330
|
+
const suppressions = suppressionEvidence.get(entry.model_name) ?? new Set();
|
|
331
|
+
suppressions.add(shouldSuppressReasoningContent(entry.model_name, entry));
|
|
332
|
+
suppressionEvidence.set(entry.model_name, suppressions);
|
|
288
333
|
entries.set(entry.model_name, {
|
|
289
334
|
...previous,
|
|
290
335
|
...entry,
|
|
291
336
|
model_info: { ...previous?.model_info, ...entry.model_info },
|
|
292
337
|
});
|
|
293
338
|
}
|
|
294
|
-
let models = [...entries.
|
|
339
|
+
let models = [...entries.entries()]
|
|
340
|
+
.map(([id, entry]) => mapFromModelInfo(entry, aggregateSuppressionEvidence(suppressionEvidence.get(id))))
|
|
341
|
+
.filter((m) => m !== undefined);
|
|
295
342
|
// LiteLLM's /model/info does NOT expand wildcard model_name entries (e.g.
|
|
296
343
|
// "lemonade/*" backed by model: openai/* + check_provider_endpoint: true)
|
|
297
344
|
// — it returns the literal wildcard only. The discovered ids live in
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { setTimeout as delay } from "node:timers/promises";
|
|
5
5
|
import { getAgentDir, readStoredCredential } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { setupLiteLLMCostTracking } from "./cost.js";
|
|
7
|
-
import { discoverModels, isGpt55Model, isMoonshotModel, normalizeBaseUrl
|
|
7
|
+
import { discoverModels, emitsThinkTags, isGpt55Model, isMoonshotModel, normalizeBaseUrl } from "./discover.js";
|
|
8
8
|
import { getGcloudToken, getGcloudTokenCacheKey, getGcloudTokenCommand, isGcloudTokenAuthEnabled, } from "./gcloud-token.js";
|
|
9
9
|
import { getSessionIdFromFile } from "./litellm.js";
|
|
10
10
|
import { createMcpToolDefinitions } from "./mcp-tools.js";
|
|
@@ -335,7 +335,7 @@ async function resolveCredentials(definition, { executeHelpers = true } = {}) {
|
|
|
335
335
|
apiKeyConfig = `$${ENV_API_KEY}`;
|
|
336
336
|
}
|
|
337
337
|
return {
|
|
338
|
-
baseUrl: configuredBase ? normalizeBaseUrl(configuredBase) : undefined,
|
|
338
|
+
baseUrl: configuredBase ? normalizeBaseUrl(configuredBase, definition.allowInsecureHttp) : undefined,
|
|
339
339
|
apiKey: apiKey || undefined,
|
|
340
340
|
apiKeyConfig,
|
|
341
341
|
};
|
|
@@ -393,6 +393,7 @@ function getProviderDefinitions(settings) {
|
|
|
393
393
|
useDefaultEnv: isDefault,
|
|
394
394
|
useGcloudTokenAuth: isDefault,
|
|
395
395
|
enableOAuth: isDefault,
|
|
396
|
+
allowInsecureHttp: raw?.allowInsecureHttp === true,
|
|
396
397
|
});
|
|
397
398
|
const definitions = [makeDefinition(PROVIDER_NAME, defaultSettings, true)];
|
|
398
399
|
for (const [name, raw] of Object.entries(providerSettings ?? {})) {
|
|
@@ -405,7 +406,51 @@ function getProviderDefinitions(settings) {
|
|
|
405
406
|
}
|
|
406
407
|
return definitions;
|
|
407
408
|
}
|
|
408
|
-
|
|
409
|
+
const DIFFERENT_BASE_URL = "enter-a-different-url";
|
|
410
|
+
/**
|
|
411
|
+
* The proxy URL is the one thing a user has to retype on every re-login, and an expired SSO token is
|
|
412
|
+
* the common reason to re-login. Offer whatever we already know: settings, env, or the credential the
|
|
413
|
+
* dead session was issued against.
|
|
414
|
+
*/
|
|
415
|
+
function knownBaseUrl(definition) {
|
|
416
|
+
const stored = readStoredCredential(definition.name, join(getAgentDir(), "auth.json"));
|
|
417
|
+
const candidates = [
|
|
418
|
+
[cleanConfig(definition.baseUrl), "configured"],
|
|
419
|
+
[definition.useDefaultEnv ? cleanConfig(process.env[ENV_BASE_URL]) : undefined, `$${ENV_BASE_URL}`],
|
|
420
|
+
[
|
|
421
|
+
stored?.type === "oauth"
|
|
422
|
+
? cleanConfig(typeof stored.baseUrl === "string" ? stored.baseUrl : undefined)
|
|
423
|
+
: cleanConfig(stored?.env?.[ENV_BASE_URL]),
|
|
424
|
+
"previous login",
|
|
425
|
+
],
|
|
426
|
+
];
|
|
427
|
+
for (const [candidate, source] of candidates) {
|
|
428
|
+
if (!candidate)
|
|
429
|
+
continue;
|
|
430
|
+
try {
|
|
431
|
+
return { url: normalizeBaseUrl(candidate, definition.allowInsecureHttp), source };
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
// A URL we could not use anyway is not worth offering; try the next candidate.
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return undefined;
|
|
438
|
+
}
|
|
439
|
+
async function promptBaseUrl(interaction, definition) {
|
|
440
|
+
const known = knownBaseUrl(definition);
|
|
441
|
+
if (known) {
|
|
442
|
+
const choice = await interaction.prompt({
|
|
443
|
+
type: "select",
|
|
444
|
+
message: "LiteLLM proxy URL:",
|
|
445
|
+
// Pi's login selector renders labels only, so the source has to ride along in the label.
|
|
446
|
+
options: [
|
|
447
|
+
{ id: known.url, label: `${known.url} (${known.source})` },
|
|
448
|
+
{ id: DIFFERENT_BASE_URL, label: "Enter a different URL…" },
|
|
449
|
+
],
|
|
450
|
+
});
|
|
451
|
+
if (choice === known.url)
|
|
452
|
+
return known.url;
|
|
453
|
+
}
|
|
409
454
|
const rawBaseUrl = (await interaction.prompt({
|
|
410
455
|
type: "text",
|
|
411
456
|
message: "Enter LiteLLM proxy URL (no trailing /v1):",
|
|
@@ -413,10 +458,14 @@ async function loginApiKey(interaction) {
|
|
|
413
458
|
})).trim();
|
|
414
459
|
if (!rawBaseUrl)
|
|
415
460
|
throw new Error("Base URL is required");
|
|
461
|
+
return normalizeBaseUrl(rawBaseUrl, definition.allowInsecureHttp);
|
|
462
|
+
}
|
|
463
|
+
async function loginApiKey(interaction, definition) {
|
|
464
|
+
const baseUrl = await promptBaseUrl(interaction, definition);
|
|
416
465
|
const key = (await interaction.prompt({ type: "secret", message: "Enter API key:" })).trim();
|
|
417
466
|
if (!key)
|
|
418
467
|
throw new Error("Both base URL and API key are required");
|
|
419
|
-
return { type: "api_key", key, env: { [ENV_BASE_URL]:
|
|
468
|
+
return { type: "api_key", key, env: { [ENV_BASE_URL]: baseUrl } };
|
|
420
469
|
}
|
|
421
470
|
async function startCliSso(baseUrl, signal, headers) {
|
|
422
471
|
const response = await fetch(`${baseUrl}/sso/cli/start`, {
|
|
@@ -559,15 +608,9 @@ async function loginWithPastedToken(interaction, baseUrl, headers) {
|
|
|
559
608
|
}
|
|
560
609
|
return { type: "oauth", access, refresh: "", expires, baseUrl };
|
|
561
610
|
}
|
|
562
|
-
async function loginOAuth(interaction,
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
message: "Enter LiteLLM proxy URL (no trailing /v1):",
|
|
566
|
-
placeholder: "https://litellm.example.com",
|
|
567
|
-
})).trim();
|
|
568
|
-
if (!rawBaseUrl)
|
|
569
|
-
throw new Error("Base URL is required");
|
|
570
|
-
const baseUrl = normalizeBaseUrl(rawBaseUrl);
|
|
611
|
+
async function loginOAuth(interaction, definition) {
|
|
612
|
+
const headers = resolveHeaders(definition);
|
|
613
|
+
const baseUrl = await promptBaseUrl(interaction, definition);
|
|
571
614
|
const cliSso = await startCliSso(baseUrl, interaction.signal, headers);
|
|
572
615
|
if (!cliSso)
|
|
573
616
|
return loginWithPastedToken(interaction, baseUrl, headers);
|
|
@@ -610,7 +653,7 @@ async function resolveApiKeyAuth(definition, ctx, credential, executeHelpers = t
|
|
|
610
653
|
if (stored) {
|
|
611
654
|
source = "stored credential";
|
|
612
655
|
creds = {
|
|
613
|
-
baseUrl: baseUrl ? normalizeBaseUrl(baseUrl) : undefined,
|
|
656
|
+
baseUrl: baseUrl ? normalizeBaseUrl(baseUrl, definition.allowInsecureHttp) : undefined,
|
|
614
657
|
apiKey: stored,
|
|
615
658
|
};
|
|
616
659
|
}
|
|
@@ -647,7 +690,7 @@ async function resolveApiKeyAuth(definition, ctx, credential, executeHelpers = t
|
|
|
647
690
|
}
|
|
648
691
|
}
|
|
649
692
|
if (!creds.baseUrl && baseUrl)
|
|
650
|
-
creds.baseUrl = normalizeBaseUrl(baseUrl);
|
|
693
|
+
creds.baseUrl = normalizeBaseUrl(baseUrl, definition.allowInsecureHttp);
|
|
651
694
|
}
|
|
652
695
|
if (!creds.apiKey)
|
|
653
696
|
return undefined;
|
|
@@ -657,7 +700,7 @@ async function resolveApiKeyAuth(definition, ctx, credential, executeHelpers = t
|
|
|
657
700
|
baseUrl: creds.baseUrl ? `${creds.baseUrl}/v1` : undefined,
|
|
658
701
|
headers: await resolveHeadersFromContext(definition, ctx.env),
|
|
659
702
|
},
|
|
660
|
-
env: baseUrl ? { [ENV_BASE_URL]: normalizeBaseUrl(baseUrl) } : undefined,
|
|
703
|
+
env: baseUrl ? { [ENV_BASE_URL]: normalizeBaseUrl(baseUrl, definition.allowInsecureHttp) } : undefined,
|
|
661
704
|
source: source ?? creds.apiKeyConfig ?? ENV_API_KEY,
|
|
662
705
|
};
|
|
663
706
|
}
|
|
@@ -665,7 +708,7 @@ function createProviderAuth(definition) {
|
|
|
665
708
|
return {
|
|
666
709
|
apiKey: {
|
|
667
710
|
name: `${definition.displayName} API key`,
|
|
668
|
-
login: definition.name === PROVIDER_NAME ? loginApiKey : undefined,
|
|
711
|
+
login: definition.name === PROVIDER_NAME ? (interaction) => loginApiKey(interaction, definition) : undefined,
|
|
669
712
|
check: async ({ ctx, credential }) => {
|
|
670
713
|
const baseUrl = credential?.env?.[ENV_BASE_URL] ??
|
|
671
714
|
definition.baseUrl ??
|
|
@@ -701,14 +744,16 @@ function createProviderAuth(definition) {
|
|
|
701
744
|
? {
|
|
702
745
|
name: "LiteLLM SSO",
|
|
703
746
|
loginLabel: "Sign in with LiteLLM SSO",
|
|
704
|
-
login: (interaction) => loginOAuth(interaction,
|
|
747
|
+
login: (interaction) => loginOAuth(interaction, definition),
|
|
705
748
|
refresh: async (credential, signal) => ({
|
|
706
749
|
...(await refreshLiteLLM(credential, signal)),
|
|
707
750
|
type: "oauth",
|
|
708
751
|
}),
|
|
709
752
|
toAuth: async (credential) => ({
|
|
710
753
|
apiKey: credential.access,
|
|
711
|
-
baseUrl: credential.baseUrl
|
|
754
|
+
baseUrl: credential.baseUrl
|
|
755
|
+
? `${normalizeBaseUrl(String(credential.baseUrl), definition.allowInsecureHttp)}/v1`
|
|
756
|
+
: undefined,
|
|
712
757
|
headers: resolveHeaders(definition),
|
|
713
758
|
}),
|
|
714
759
|
}
|
|
@@ -777,7 +822,9 @@ const REASONING_SUPPRESSION_DEFAULTS = {
|
|
|
777
822
|
thinking: { type: "disabled" },
|
|
778
823
|
};
|
|
779
824
|
const GEMINI_MODEL_PATTERN = /(?:^|[-_/.:])gemini(?=$|[-_/.:])/i;
|
|
780
|
-
function prepareLiteLLMRequestPayload(payload,
|
|
825
|
+
function prepareLiteLLMRequestPayload(payload, model, sessionId) {
|
|
826
|
+
const modelId = model?.id;
|
|
827
|
+
const api = model?.api;
|
|
781
828
|
let next;
|
|
782
829
|
const update = (key, value) => {
|
|
783
830
|
if (payload[key] !== undefined)
|
|
@@ -785,7 +832,7 @@ function prepareLiteLLMRequestPayload(payload, modelId, api, sessionId) {
|
|
|
785
832
|
next ??= { ...payload };
|
|
786
833
|
next[key] = value;
|
|
787
834
|
};
|
|
788
|
-
if (api !== "openai-responses" &&
|
|
835
|
+
if (api !== "openai-responses" && model?.suppressReasoningContent === true) {
|
|
789
836
|
for (const [key, value] of Object.entries(REASONING_SUPPRESSION_DEFAULTS)) {
|
|
790
837
|
if (key !== "thinking")
|
|
791
838
|
update(key, value);
|
|
@@ -862,7 +909,7 @@ function prepareLiteLLMRequestPayload(payload, modelId, api, sessionId) {
|
|
|
862
909
|
return next;
|
|
863
910
|
}
|
|
864
911
|
function normalizeThinkTags(message, litellmProviderNames) {
|
|
865
|
-
if (!litellmProviderNames.has(message.provider) || !
|
|
912
|
+
if (!litellmProviderNames.has(message.provider) || !emitsThinkTags(message.model))
|
|
866
913
|
return;
|
|
867
914
|
let changed = false;
|
|
868
915
|
const content = [];
|
|
@@ -939,23 +986,29 @@ export default async function (pi) {
|
|
|
939
986
|
const baseUrl = cleanConfig(definition.baseUrl) ??
|
|
940
987
|
(definition.useDefaultEnv ? cleanConfig(process.env[ENV_BASE_URL]) : undefined) ??
|
|
941
988
|
"https://litellm.example.com";
|
|
942
|
-
return `${normalizeBaseUrl(baseUrl)}/v1`;
|
|
989
|
+
return `${normalizeBaseUrl(baseUrl, definition.allowInsecureHttp)}/v1`;
|
|
943
990
|
}
|
|
944
991
|
async function authForCredential(definition, credential, executeHelpers = true) {
|
|
945
992
|
if (credential?.type === "oauth") {
|
|
946
993
|
const baseUrl = typeof credential.baseUrl === "string"
|
|
947
|
-
? normalizeBaseUrl(credential.baseUrl)
|
|
948
|
-
: normalizeBaseUrl(requestBaseUrl(definition));
|
|
949
|
-
return {
|
|
994
|
+
? normalizeBaseUrl(credential.baseUrl, definition.allowInsecureHttp)
|
|
995
|
+
: normalizeBaseUrl(requestBaseUrl(definition), definition.allowInsecureHttp);
|
|
996
|
+
return {
|
|
997
|
+
baseUrl,
|
|
998
|
+
apiKey: credential.access,
|
|
999
|
+
headers: resolveHeaders(definition),
|
|
1000
|
+
allowInsecureHttp: definition.allowInsecureHttp,
|
|
1001
|
+
};
|
|
950
1002
|
}
|
|
951
1003
|
const resolved = await resolveApiKeyAuth(definition, { env: async (name) => process.env[name] }, credential, executeHelpers);
|
|
952
1004
|
if (!resolved?.auth.apiKey) {
|
|
953
1005
|
throw new Error(`no credentials for ${definition.name}. Run /login litellm or set env vars.`);
|
|
954
1006
|
}
|
|
955
1007
|
return {
|
|
956
|
-
baseUrl: normalizeBaseUrl(resolved.auth.baseUrl ?? requestBaseUrl(definition)),
|
|
1008
|
+
baseUrl: normalizeBaseUrl(resolved.auth.baseUrl ?? requestBaseUrl(definition), definition.allowInsecureHttp),
|
|
957
1009
|
apiKey: resolved.auth.apiKey,
|
|
958
1010
|
headers: resolved.auth.headers,
|
|
1011
|
+
allowInsecureHttp: definition.allowInsecureHttp,
|
|
959
1012
|
};
|
|
960
1013
|
}
|
|
961
1014
|
let registeredMcpIdentity;
|
|
@@ -963,7 +1016,7 @@ export default async function (pi) {
|
|
|
963
1016
|
let defaultRuntimeAuth;
|
|
964
1017
|
function mcpCatalogIdentity(auth) {
|
|
965
1018
|
return JSON.stringify({
|
|
966
|
-
baseUrl: normalizeBaseUrl(auth.baseUrl),
|
|
1019
|
+
baseUrl: normalizeBaseUrl(auth.baseUrl, auth.allowInsecureHttp),
|
|
967
1020
|
apiKey: auth.apiKey,
|
|
968
1021
|
headers: Object.entries(auth.headers ?? {}).sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0)),
|
|
969
1022
|
});
|
|
@@ -979,9 +1032,10 @@ export default async function (pi) {
|
|
|
979
1032
|
return undefined;
|
|
980
1033
|
const headers = Object.fromEntries(Object.entries(auth.auth.headers ?? provider?.headers ?? {}).filter((entry) => typeof entry[1] === "string"));
|
|
981
1034
|
return {
|
|
982
|
-
baseUrl: normalizeBaseUrl(baseUrl),
|
|
1035
|
+
baseUrl: normalizeBaseUrl(baseUrl, definitions[0]?.allowInsecureHttp),
|
|
983
1036
|
apiKey,
|
|
984
1037
|
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
1038
|
+
allowInsecureHttp: definitions[0]?.allowInsecureHttp,
|
|
985
1039
|
};
|
|
986
1040
|
}
|
|
987
1041
|
async function resolveDefaultRuntimeAuth(ctx) {
|
|
@@ -1056,11 +1110,12 @@ export default async function (pi) {
|
|
|
1056
1110
|
timeoutMs: getDiscoveryTimeoutMs(),
|
|
1057
1111
|
signal,
|
|
1058
1112
|
headers: auth.headers,
|
|
1113
|
+
allowInsecureHttp: auth.allowInsecureHttp,
|
|
1059
1114
|
silent: !isVerboseDiscovery(),
|
|
1060
1115
|
onProgress: isVerboseDiscovery() ? (message) => process.stderr.write(`LiteLLM: ${message}\n`) : undefined,
|
|
1061
1116
|
});
|
|
1062
1117
|
signal?.throwIfAborted();
|
|
1063
|
-
return { ...result, baseUrl: `${normalizeBaseUrl(auth.baseUrl)}/v1` };
|
|
1118
|
+
return { ...result, baseUrl: `${normalizeBaseUrl(auth.baseUrl, auth.allowInsecureHttp)}/v1` };
|
|
1064
1119
|
}
|
|
1065
1120
|
// Pi's startup path only ever refreshes with allowNetwork:false, and it returns before the
|
|
1066
1121
|
// network phase, so the provider would stay empty until the user opens /model. Discover here
|
|
@@ -1133,7 +1188,7 @@ export default async function (pi) {
|
|
|
1133
1188
|
return;
|
|
1134
1189
|
if (typeof event.payload !== "object" || event.payload === null)
|
|
1135
1190
|
return;
|
|
1136
|
-
return prepareLiteLLMRequestPayload(event.payload, ctx.model
|
|
1191
|
+
return prepareLiteLLMRequestPayload(event.payload, ctx.model, sessionId);
|
|
1137
1192
|
});
|
|
1138
1193
|
// Skills enrichment is best-effort: an expired credential or an unreachable proxy must not
|
|
1139
1194
|
// report an extension error on every turn. The failing model request states the real problem.
|
|
@@ -1147,7 +1202,7 @@ export default async function (pi) {
|
|
|
1147
1202
|
if (!auth)
|
|
1148
1203
|
return;
|
|
1149
1204
|
defaultRuntimeAuth = auth;
|
|
1150
|
-
const skills = await listSkills(auth.baseUrl, auth.apiKey, auth.headers);
|
|
1205
|
+
const skills = await listSkills(auth.baseUrl, auth.apiKey, auth.headers, auth.allowInsecureHttp);
|
|
1151
1206
|
section = createSkillsPromptSection(skills);
|
|
1152
1207
|
}
|
|
1153
1208
|
catch (error) {
|
package/dist/mcp-tools.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
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): Promise<LiteLLMMcpTool[]>;
|
|
4
|
-
export declare function executeMcpTool(baseUrl: string, apiKey: string, serverId: string, toolName: string, args: Record<string, unknown>, headers?: Record<string, string
|
|
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
5
|
export declare function createMcpToolDefinitions(getAuth: (ctx?: ExtensionContext) => Promise<LiteLLMRuntimeAuth>, onProgress?: (message: string) => void, signal?: AbortSignal): Promise<ToolDefinition[]>;
|
package/dist/mcp-tools.js
CHANGED
|
@@ -49,11 +49,11 @@ function normalizeMcpTool(value) {
|
|
|
49
49
|
input_schema: inputSchema,
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
-
export async function discoverMcpTools(baseUrl, apiKey, headers, onProgress, parentSignal) {
|
|
52
|
+
export async function discoverMcpTools(baseUrl, apiKey, headers, onProgress, parentSignal, allowInsecureHttp = false) {
|
|
53
53
|
onProgress?.("Discovering MCP tools from server...");
|
|
54
54
|
try {
|
|
55
55
|
onProgress?.("Querying MCP tools/list endpoint...");
|
|
56
|
-
const response = await fetch(`${normalizeBaseUrl(baseUrl)}/mcp-rest/tools/list`, {
|
|
56
|
+
const response = await fetch(`${normalizeBaseUrl(baseUrl, allowInsecureHttp)}/mcp-rest/tools/list`, {
|
|
57
57
|
headers: {
|
|
58
58
|
...headers,
|
|
59
59
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -80,9 +80,9 @@ export async function discoverMcpTools(baseUrl, apiKey, headers, onProgress, par
|
|
|
80
80
|
throw error;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
export async function executeMcpTool(baseUrl, apiKey, serverId, toolName, args, headers) {
|
|
83
|
+
export async function executeMcpTool(baseUrl, apiKey, serverId, toolName, args, headers, allowInsecureHttp = false) {
|
|
84
84
|
for (let attempt = 0; attempt <= MCP_RETRY_ATTEMPTS; attempt++) {
|
|
85
|
-
const result = await executeMcpToolOnce(baseUrl, apiKey, serverId, toolName, args, headers);
|
|
85
|
+
const result = await executeMcpToolOnce(baseUrl, apiKey, serverId, toolName, args, headers, allowInsecureHttp);
|
|
86
86
|
if (attempt < MCP_RETRY_ATTEMPTS && result.retryable) {
|
|
87
87
|
await sleep(MCP_RETRY_DELAY_MS);
|
|
88
88
|
continue;
|
|
@@ -91,11 +91,11 @@ export async function executeMcpTool(baseUrl, apiKey, serverId, toolName, args,
|
|
|
91
91
|
}
|
|
92
92
|
return `Error calling ${toolName} on ${serverId}: retry attempts exhausted`;
|
|
93
93
|
}
|
|
94
|
-
async function executeMcpToolOnce(baseUrl, apiKey, serverId, toolName, args, headers) {
|
|
94
|
+
async function executeMcpToolOnce(baseUrl, apiKey, serverId, toolName, args, headers, allowInsecureHttp = false) {
|
|
95
95
|
try {
|
|
96
96
|
let response;
|
|
97
97
|
try {
|
|
98
|
-
response = await fetch(`${normalizeBaseUrl(baseUrl)}/mcp-rest/tools/call`, {
|
|
98
|
+
response = await fetch(`${normalizeBaseUrl(baseUrl, allowInsecureHttp)}/mcp-rest/tools/call`, {
|
|
99
99
|
method: "POST",
|
|
100
100
|
headers: {
|
|
101
101
|
...headers,
|
|
@@ -152,7 +152,7 @@ function buildParameters(inputSchema) {
|
|
|
152
152
|
}
|
|
153
153
|
export async function createMcpToolDefinitions(getAuth, onProgress, signal) {
|
|
154
154
|
const discoveryAuth = await getAuth();
|
|
155
|
-
const tools = await discoverMcpTools(discoveryAuth.baseUrl, discoveryAuth.apiKey, discoveryAuth.headers, onProgress, signal);
|
|
155
|
+
const tools = await discoverMcpTools(discoveryAuth.baseUrl, discoveryAuth.apiKey, discoveryAuth.headers, onProgress, signal, discoveryAuth.allowInsecureHttp);
|
|
156
156
|
return tools.map((mcpTool) => {
|
|
157
157
|
const safeServer = sanitizeName(mcpTool.server_name);
|
|
158
158
|
const safeTool = sanitizeName(mcpTool.name);
|
|
@@ -170,7 +170,7 @@ export async function createMcpToolDefinitions(getAuth, onProgress, signal) {
|
|
|
170
170
|
const args = Object.keys(rawParams).length === 1 && rawParams.args && typeof rawParams.args === "object"
|
|
171
171
|
? rawParams.args
|
|
172
172
|
: rawParams;
|
|
173
|
-
const text = await executeMcpTool(auth.baseUrl, auth.apiKey, mcpTool.server_id ?? mcpTool.server_name, mcpTool.name, args, auth.headers);
|
|
173
|
+
const text = await executeMcpTool(auth.baseUrl, auth.apiKey, mcpTool.server_id ?? mcpTool.server_name, mcpTool.name, args, auth.headers, auth.allowInsecureHttp);
|
|
174
174
|
return {
|
|
175
175
|
content: [{ type: "text", text }],
|
|
176
176
|
details: { server: mcpTool.server_name, serverId: mcpTool.server_id, tool: mcpTool.name },
|
package/dist/provider.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Credential, type
|
|
2
|
-
import type { DiscoveredModel, DiscoveryResult, LiteLLMApi } from "./types.js";
|
|
1
|
+
import { type Credential, type Provider, type ProviderAuth } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { DiscoveredModel, DiscoveryResult, LiteLLMApi, LiteLLMModel } from "./types.js";
|
|
3
3
|
export type LiteLLMProviderOptions = {
|
|
4
4
|
id: string;
|
|
5
5
|
name: string;
|
|
@@ -7,10 +7,10 @@ export type LiteLLMProviderOptions = {
|
|
|
7
7
|
auth: ProviderAuth;
|
|
8
8
|
/** Catalog discovered during activation. Pi's startup refresh never allows network access, so
|
|
9
9
|
* without a seed the provider stays empty until the user opens /model. */
|
|
10
|
-
models?: readonly
|
|
10
|
+
models?: readonly LiteLLMModel[];
|
|
11
11
|
discover(credential: Credential, signal?: AbortSignal): Promise<DiscoveryResult & {
|
|
12
12
|
baseUrl?: string;
|
|
13
13
|
}>;
|
|
14
14
|
};
|
|
15
|
-
export declare function toNativeModels(provider: string, baseUrl: string, models: readonly DiscoveredModel[]):
|
|
15
|
+
export declare function toNativeModels(provider: string, baseUrl: string, models: readonly DiscoveredModel[]): LiteLLMModel[];
|
|
16
16
|
export declare function createLiteLLMProvider(options: LiteLLMProviderOptions): Provider<LiteLLMApi>;
|
package/dist/skills.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { LiteLLMRuntimeAuth, LiteLLMSkill } from "./types.js";
|
|
3
|
-
export declare function listSkills(baseUrl: string, apiKey: string, headers?: Record<string, string
|
|
3
|
+
export declare function listSkills(baseUrl: string, apiKey: string, headers?: Record<string, string>, allowInsecureHttp?: boolean): Promise<LiteLLMSkill[]>;
|
|
4
4
|
export declare function createSkill(baseUrl: string, apiKey: string, input: {
|
|
5
5
|
name: string;
|
|
6
6
|
description?: string;
|
|
7
7
|
source?: Record<string, unknown>;
|
|
8
8
|
code?: string;
|
|
9
9
|
inputSchema?: Record<string, unknown>;
|
|
10
|
-
}, headers?: Record<string, string
|
|
11
|
-
export declare function deleteSkill(baseUrl: string, apiKey: string, skillId: string, headers?: Record<string, string
|
|
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
12
|
export declare function createSkillsPromptSection(skills: LiteLLMSkill[]): string | undefined;
|
|
13
13
|
export declare function createSkillToolDefinitions(getAuth: (ctx?: ExtensionContext) => Promise<LiteLLMRuntimeAuth>): ToolDefinition[];
|
|
14
14
|
export declare function resetSkillsCache(): void;
|
package/dist/skills.js
CHANGED
|
@@ -17,8 +17,8 @@ function getSkillsFromBody(body) {
|
|
|
17
17
|
}
|
|
18
18
|
return [];
|
|
19
19
|
}
|
|
20
|
-
export async function listSkills(baseUrl, apiKey, headers) {
|
|
21
|
-
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
20
|
+
export async function listSkills(baseUrl, apiKey, headers, allowInsecureHttp = false) {
|
|
21
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl, allowInsecureHttp);
|
|
22
22
|
if (skillsCache &&
|
|
23
23
|
skillsCache.baseUrl === normalizedBaseUrl &&
|
|
24
24
|
skillsCache.apiKey === apiKey &&
|
|
@@ -46,10 +46,10 @@ export async function listSkills(baseUrl, apiKey, headers) {
|
|
|
46
46
|
skillsCache = { baseUrl: normalizedBaseUrl, apiKey, fetchedAt: Date.now(), skills };
|
|
47
47
|
return skills;
|
|
48
48
|
}
|
|
49
|
-
export async function createSkill(baseUrl, apiKey, input, headers) {
|
|
49
|
+
export async function createSkill(baseUrl, apiKey, input, headers, allowInsecureHttp = false) {
|
|
50
50
|
if (!input.source && !input.code)
|
|
51
51
|
throw new Error("code is required when source is omitted");
|
|
52
|
-
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
52
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl, allowInsecureHttp);
|
|
53
53
|
let usedSkillHub = input.source !== undefined;
|
|
54
54
|
const skillHubPayload = input.source
|
|
55
55
|
? {
|
|
@@ -105,8 +105,8 @@ export async function createSkill(baseUrl, apiKey, input, headers) {
|
|
|
105
105
|
}
|
|
106
106
|
return response.json().catch(() => ({}));
|
|
107
107
|
}
|
|
108
|
-
export async function deleteSkill(baseUrl, apiKey, skillId, headers) {
|
|
109
|
-
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
108
|
+
export async function deleteSkill(baseUrl, apiKey, skillId, headers, allowInsecureHttp = false) {
|
|
109
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl, allowInsecureHttp);
|
|
110
110
|
let skillHubError;
|
|
111
111
|
const skillHubResponse = await fetch(`${normalizedBaseUrl}/claude-code/plugins/${encodeURIComponent(skillId)}`, {
|
|
112
112
|
method: "DELETE",
|
|
@@ -182,7 +182,7 @@ export function createSkillToolDefinitions(getAuth) {
|
|
|
182
182
|
parameters: Type.Object({}),
|
|
183
183
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
184
184
|
const auth = await getAuth(ctx);
|
|
185
|
-
const skills = await listSkills(auth.baseUrl, auth.apiKey, auth.headers);
|
|
185
|
+
const skills = await listSkills(auth.baseUrl, auth.apiKey, auth.headers, auth.allowInsecureHttp);
|
|
186
186
|
return { content: [{ type: "text", text: formatSkills(skills) }], details: { count: skills.length } };
|
|
187
187
|
},
|
|
188
188
|
}),
|
|
@@ -203,7 +203,7 @@ export function createSkillToolDefinitions(getAuth) {
|
|
|
203
203
|
source,
|
|
204
204
|
code: params.code,
|
|
205
205
|
inputSchema,
|
|
206
|
-
}, auth.headers);
|
|
206
|
+
}, auth.headers, auth.allowInsecureHttp);
|
|
207
207
|
return { content: [{ type: "text", text: "LiteLLM skill created." }], details: { result } };
|
|
208
208
|
},
|
|
209
209
|
}),
|
|
@@ -214,7 +214,7 @@ export function createSkillToolDefinitions(getAuth) {
|
|
|
214
214
|
parameters: DeleteSkillParams,
|
|
215
215
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
216
216
|
const auth = await getAuth(ctx);
|
|
217
|
-
await deleteSkill(auth.baseUrl, auth.apiKey, params.skillId, auth.headers);
|
|
217
|
+
await deleteSkill(auth.baseUrl, auth.apiKey, params.skillId, auth.headers, auth.allowInsecureHttp);
|
|
218
218
|
return { content: [{ type: "text", text: `LiteLLM skill deleted: ${params.skillId}` }], details: {} };
|
|
219
219
|
},
|
|
220
220
|
}),
|
package/dist/types.d.ts
CHANGED
|
@@ -5,9 +5,14 @@ export type LiteLLMRuntimeAuth = {
|
|
|
5
5
|
baseUrl: string;
|
|
6
6
|
apiKey: string;
|
|
7
7
|
headers?: Record<string, string>;
|
|
8
|
+
allowInsecureHttp?: boolean;
|
|
8
9
|
};
|
|
9
10
|
export type DiscoveredModel = Omit<Model<"openai-completions">, "provider" | "api" | "baseUrl"> & {
|
|
10
11
|
api?: LiteLLMApi;
|
|
12
|
+
suppressReasoningContent?: boolean;
|
|
13
|
+
};
|
|
14
|
+
export type LiteLLMModel = Model<LiteLLMApi> & {
|
|
15
|
+
suppressReasoningContent?: boolean;
|
|
11
16
|
};
|
|
12
17
|
export interface DiscoveryResult {
|
|
13
18
|
models: DiscoveredModel[];
|
|
@@ -17,9 +22,14 @@ export interface DiscoveryOptions {
|
|
|
17
22
|
timeoutMs?: number;
|
|
18
23
|
signal?: AbortSignal;
|
|
19
24
|
headers?: Record<string, string>;
|
|
25
|
+
allowInsecureHttp?: boolean;
|
|
20
26
|
}
|
|
21
27
|
export interface ModelInfoEntry {
|
|
22
28
|
model_name?: string;
|
|
29
|
+
litellm_params?: {
|
|
30
|
+
model?: string;
|
|
31
|
+
custom_llm_provider?: string;
|
|
32
|
+
};
|
|
23
33
|
model_info?: {
|
|
24
34
|
mode?: string | null;
|
|
25
35
|
input_cost_per_token?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-provider-litellm",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "LiteLLM proxy provider extension for Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"typecheck": "tsgo --noEmit",
|
|
47
47
|
"lint": "biome check --error-on-warnings .",
|
|
48
48
|
"format": "biome check --write .",
|
|
49
|
-
"test": "vitest --run",
|
|
49
|
+
"test": "vitest --run --dir tests",
|
|
50
50
|
"supply-chain:guard": "tsx scripts/supply-chain-guard.ts",
|
|
51
51
|
"check": "npm run lint && npm run typecheck && npm test",
|
|
52
52
|
"prepublishOnly": "npm run check && npm run clean && npm run build && npm run supply-chain:guard"
|