pi-provider-litellm 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,8 +24,8 @@ pi -e npm:pi-provider-litellm
24
24
  ```bash
25
25
  git clone https://github.com/balcsida/pi-provider-litellm.git ~/.pi/agent/extensions/pi-provider-litellm
26
26
  cd ~/.pi/agent/extensions/pi-provider-litellm
27
- npm install
28
- npm run build
27
+ npm ci
28
+ npm run clean && npm run build
29
29
  ```
30
30
 
31
31
  </details>
@@ -66,6 +66,39 @@ Stored pi credentials for `litellm` take precedence over `LITELLM_API_KEY`; the
66
66
 
67
67
  `LITELLM_DISCOVERY_TIMEOUT_MS=0` only disables startup and refresh model discovery. It does not replace the base URL or API key settings required to send requests when you are not using `/login litellm`.
68
68
 
69
+ ## Mocked LiteLLM smoke workflow
70
+
71
+ The `LiteLLM Smoke` GitHub Actions workflow starts VidaiMock and a real LiteLLM proxy on the runner. LiteLLM exposes OpenAI-compatible and Anthropic routes whose upstreams are served by VidaiMock, then this extension's smoke runner discovers those models through LiteLLM and sends `/v1/chat/completions` requests through the proxy.
72
+
73
+ This keeps the LiteLLM integration path under test but does not call real LLM APIs. No provider API keys or GitHub Models permission are required. The workflow also runs a non-interactive Pi CLI smoke with `--list-models` and `-p` so extension loading, model discovery, and a real completion path are covered without opening the TUI.
74
+
75
+ ## Development
76
+
77
+ This package requires Node.js `>=22.19.0`. CI currently uses Node `24.16.0`.
78
+
79
+ ```bash
80
+ npm ci
81
+ npm run check
82
+ npm run clean && npm run build
83
+ ```
84
+
85
+ `npm run check` runs Biome, type checking, and the Vitest suite. Runtime changes must be built before local Pi smoke checks because the extension entrypoint is `./dist/index.js`.
86
+
87
+ Before changing package contents or dependency policy, also run:
88
+
89
+ ```bash
90
+ npm run supply-chain:guard
91
+ npm pack --dry-run
92
+ ```
93
+
94
+ The published npm package should contain only `dist`, `README.md`, and `LICENSE`.
95
+
96
+ ## Release
97
+
98
+ Releases are driven by semver tags named `v*.*.*`. The GitHub release workflow installs from the lockfile, runs the checks, builds `dist`, verifies the package tarball, publishes to npm with provenance, and creates a GitHub release.
99
+
100
+ Before tagging a release, keep `package.json` and `package-lock.json` versions in sync and verify the dry-run package contents.
101
+
69
102
  ## Slash commands
70
103
 
71
104
  - `/litellm-refresh` — force re-fetch the model list, ignoring cache
@@ -74,6 +107,8 @@ Stored pi credentials for `litellm` take precedence over `LITELLM_API_KEY`; the
74
107
 
75
108
  The model list is cached at `~/.pi/agent/litellm-models.json` with a keyed fingerprint of the base URL + API key. Changing either invalidates the cache automatically.
76
109
 
110
+ If the cache is older than 24 hours, the extension refreshes it in the background on session start (non-blocking). Failures are silent — the cached models remain in use. Run `/litellm-refresh` to force an immediate refresh.
111
+
77
112
  ## Troubleshooting
78
113
 
79
114
  | Symptom | Likely cause |
package/dist/cost.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
1
+ import type { ExtensionAPI, ProviderModelConfig } from "@earendil-works/pi-coding-agent";
2
2
  export interface ModelCostInfo {
3
3
  inputCostPerToken: number;
4
4
  outputCostPerToken: number;
5
5
  cacheReadCostPerToken: number;
6
6
  cacheWriteCostPerToken: number;
7
7
  }
8
- export declare function setupLiteLLMCostTracking(pi: ExtensionAPI): void;
8
+ export declare function setupLiteLLMCostTracking(pi: ExtensionAPI, models: ProviderModelConfig[]): (models: ProviderModelConfig[]) => void;
package/dist/cost.js CHANGED
@@ -1,48 +1,21 @@
1
- import { resolveCredentials } from "./litellm.js";
2
- export function setupLiteLLMCostTracking(pi) {
1
+ export function setupLiteLLMCostTracking(pi, models) {
3
2
  const modelCosts = new Map();
4
3
  let lastResponseCost = null;
5
- pi.on("session_start", async (_event, ctx) => {
6
- try {
7
- const config = await resolveCredentials();
8
- if (!config.baseUrl || !config.apiKey) {
9
- ctx.ui?.notify?.("LiteLLM cost extension: no credentials configured", "warning");
10
- return;
11
- }
12
- const response = await fetch(`${config.baseUrl}/model/info`, {
13
- headers: {
14
- Authorization: `Bearer ${config.apiKey}`,
15
- Accept: "application/json",
16
- },
4
+ // pi reports model costs per million tokens; cost lookups multiply by raw token counts.
5
+ const updateCosts = (newModels) => {
6
+ modelCosts.clear();
7
+ for (const model of newModels) {
8
+ if (!model.cost)
9
+ continue;
10
+ modelCosts.set(model.id, {
11
+ inputCostPerToken: (model.cost.input ?? 0) / 1_000_000,
12
+ outputCostPerToken: (model.cost.output ?? 0) / 1_000_000,
13
+ cacheReadCostPerToken: (model.cost.cacheRead ?? 0) / 1_000_000,
14
+ cacheWriteCostPerToken: (model.cost.cacheWrite ?? 0) / 1_000_000,
17
15
  });
18
- if (!response.ok) {
19
- ctx.ui?.notify?.(`LiteLLM cost extension: /model/info returned ${response.status}`, "warning");
20
- return;
21
- }
22
- const payload = (await response.json());
23
- for (const entry of payload.data) {
24
- const info = entry.model_info;
25
- if (!info)
26
- continue;
27
- const costInfo = {
28
- inputCostPerToken: info.input_cost_per_token ?? 0,
29
- outputCostPerToken: info.output_cost_per_token ?? 0,
30
- cacheReadCostPerToken: info.cache_read_input_token_cost ?? 0,
31
- cacheWriteCostPerToken: info.cache_creation_input_token_cost ?? 0,
32
- };
33
- modelCosts.set(entry.model_name, costInfo);
34
- if (entry.litellm_params?.model) {
35
- modelCosts.set(entry.litellm_params.model, costInfo);
36
- }
37
- }
38
- if (modelCosts.size > 0) {
39
- ctx.ui?.notify?.(`LiteLLM cost: loaded pricing for ${modelCosts.size} model(s)`, "info");
40
- }
41
16
  }
42
- catch (error) {
43
- ctx.ui?.notify?.(`LiteLLM cost extension: could not fetch /model/info (${error instanceof Error ? error.message : String(error)})`, "warning");
44
- }
45
- });
17
+ };
18
+ updateCosts(models);
46
19
  pi.on("after_provider_response", (event) => {
47
20
  const costHeader = event.headers?.["x-litellm-response-cost"] ?? event.headers?.["X-Litellm-Response-Cost"];
48
21
  if (costHeader) {
@@ -132,4 +105,5 @@ export function setupLiteLLMCostTracking(pi) {
132
105
  }
133
106
  return;
134
107
  });
108
+ return updateCosts;
135
109
  }
package/dist/discover.js CHANGED
@@ -1,15 +1,19 @@
1
+ import { getModels, getProviders } from "@earendil-works/pi-ai";
1
2
  const DEFAULT_TIMEOUT_MS = 5000;
2
3
  const DEFAULT_CONTEXT_WINDOW = 128_000;
3
4
  const DEFAULT_MAX_TOKENS = 16_384;
5
+ const KNOWN_PROVIDER_SET = new Set(getProviders());
6
+ const MODELS_DEV_URL = "https://models.dev/api.json";
7
+ let modelsDevCatalog;
4
8
  export function normalizeBaseUrl(input) {
5
9
  return input.replace(/\/+$/, "").replace(/\/v1\/?$/i, "");
6
10
  }
7
- // Matches both the conventional `anthropic/...` prefix and bare aliases that
11
+ // Matches both the conventional `anthropic/...` prefix and aliases that
8
12
  // LiteLLM deployments commonly assign to Anthropic-backed routes (e.g.
9
- // `opus-4.7`, `sonnet-4.6`, `haiku-4.5`, `claude-3-5-sonnet`). Without the
10
- // `cacheControlFormat: "anthropic"` flag, pi never relays cache_control markers
11
- // through the proxy, so prompt caching silently no-ops on Claude models.
12
- const ANTHROPIC_MODEL_PATTERN = /^(anthropic\/|claude|opus|sonnet|haiku)/i;
13
+ // `google/claude-sonnet-4-6`, `opus-4.7`, `sonnet-4.6`, `haiku-4.5`). Without
14
+ // the `cacheControlFormat: "anthropic"` flag, pi never relays cache_control
15
+ // markers through the proxy, so prompt caching silently no-ops on Claude models.
16
+ const ANTHROPIC_MODEL_PATTERN = /(?:^|[-_/.:])(?:anthropic\/|(?:claude|opus|sonnet|haiku)(?=$|[-_/.:]))/i;
13
17
  const MOONSHOT_MODEL_PATTERN = /^(moonshotai\/|moonshot\/|kimi[-/])/i;
14
18
  const FORCED_THINKING_MODEL_PATTERN = /(?:^|[-/])thinking(?:[-/]|$)/i;
15
19
  export function isMoonshotModel(modelId) {
@@ -33,6 +37,44 @@ export function buildCompat(modelId) {
33
37
  }
34
38
  return { supportsStore: false };
35
39
  }
40
+ function toKnownProvider(provider) {
41
+ if (!provider)
42
+ return undefined;
43
+ const normalized = provider.toLowerCase();
44
+ return KNOWN_PROVIDER_SET.has(normalized) ? normalized : undefined;
45
+ }
46
+ function findCatalogModel(id, ownedBy) {
47
+ const prefixProvider = toKnownProvider(id.split("/")[0]);
48
+ const candidates = [toKnownProvider(ownedBy), prefixProvider].filter((provider) => provider !== undefined);
49
+ for (const provider of candidates) {
50
+ const exact = getModels(provider).find((model) => model.id === id);
51
+ if (exact)
52
+ return exact;
53
+ const providerQualified = getModels(provider).find((model) => model.id === `${provider}/${id}`);
54
+ if (providerQualified)
55
+ return providerQualified;
56
+ }
57
+ for (const provider of getProviders()) {
58
+ const exact = getModels(provider).find((model) => model.id === id);
59
+ if (exact)
60
+ return exact;
61
+ }
62
+ return undefined;
63
+ }
64
+ function getFallbackProviderAndModel(id, ownedBy) {
65
+ const [prefix, ...rest] = id.split("/");
66
+ const prefixProvider = toKnownProvider(prefix);
67
+ if (prefixProvider && rest.length > 0) {
68
+ return { provider: prefixProvider, modelId: rest.join("/") };
69
+ }
70
+ return { provider: toKnownProvider(ownedBy), modelId: id };
71
+ }
72
+ function findModelsDevModel(catalog, id, ownedBy) {
73
+ const { provider, modelId } = getFallbackProviderAndModel(id, ownedBy);
74
+ if (!provider)
75
+ return undefined;
76
+ return catalog?.[provider]?.models?.[modelId];
77
+ }
36
78
  function withTimeout(timeoutMs, signal) {
37
79
  const controller = new AbortController();
38
80
  const onAbort = () => controller.abort(signal?.reason);
@@ -68,6 +110,59 @@ async function fetchJson(url, apiKey, options) {
68
110
  cancel();
69
111
  }
70
112
  }
113
+ async function fetchPublicJson(url, options) {
114
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
115
+ const { signal, cancel } = withTimeout(timeoutMs, options.signal);
116
+ try {
117
+ const response = await fetch(url, {
118
+ headers: { Accept: "application/json" },
119
+ signal,
120
+ });
121
+ if (!response.ok)
122
+ throw new Error(`${url} returned ${response.status}`);
123
+ return (await response.json());
124
+ }
125
+ finally {
126
+ cancel();
127
+ }
128
+ }
129
+ async function getModelsDevCatalog(options) {
130
+ if (modelsDevCatalog)
131
+ return modelsDevCatalog;
132
+ try {
133
+ modelsDevCatalog = await fetchPublicJson(MODELS_DEV_URL, options);
134
+ return modelsDevCatalog;
135
+ }
136
+ catch {
137
+ return undefined;
138
+ }
139
+ }
140
+ function mapModelsDevMetadata(model) {
141
+ if (!model)
142
+ return {};
143
+ const metadata = {};
144
+ if (model.name)
145
+ metadata.name = model.name;
146
+ if (model.reasoning !== undefined)
147
+ metadata.reasoning = model.reasoning;
148
+ if (model.modalities?.input) {
149
+ metadata.input = model.modalities.input.includes("image") ? ["text", "image"] : ["text"];
150
+ }
151
+ const contextWindow = model.limit?.context ?? model.limit?.input;
152
+ if (contextWindow !== undefined)
153
+ metadata.contextWindow = contextWindow;
154
+ if (model.limit?.output !== undefined)
155
+ metadata.maxTokens = model.limit.output;
156
+ if (model.cost) {
157
+ metadata.cost = {
158
+ input: model.cost.input ?? 0,
159
+ output: model.cost.output ?? 0,
160
+ cacheRead: model.cost.cache_read ?? 0,
161
+ cacheWrite: model.cost.cache_write ?? 0,
162
+ };
163
+ }
164
+ return metadata;
165
+ }
71
166
  function mapFromModelInfo(entry) {
72
167
  const id = entry.model_name;
73
168
  if (!id)
@@ -91,18 +186,21 @@ function mapFromModelInfo(entry) {
91
186
  compat: buildCompat(id),
92
187
  };
93
188
  }
94
- function mapFromModelsList(entry) {
189
+ function mapFromModelsList(entry, modelsDev) {
95
190
  const id = entry.id;
96
191
  if (!id)
97
192
  return undefined;
193
+ const catalogModel = findCatalogModel(id, entry.owned_by);
194
+ const modelsDevMetadata = mapModelsDevMetadata(findModelsDevModel(modelsDev, id, entry.owned_by));
98
195
  return {
99
196
  id,
100
- name: `${id} (no metadata)`,
101
- reasoning: false,
102
- input: ["text"],
103
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
104
- contextWindow: DEFAULT_CONTEXT_WINDOW,
105
- maxTokens: DEFAULT_MAX_TOKENS,
197
+ name: modelsDevMetadata.name ?? catalogModel?.name ?? `${id} (no metadata)`,
198
+ reasoning: modelsDevMetadata.reasoning ?? catalogModel?.reasoning ?? false,
199
+ thinkingLevelMap: catalogModel?.thinkingLevelMap,
200
+ input: modelsDevMetadata.input ?? catalogModel?.input ?? ["text"],
201
+ cost: modelsDevMetadata.cost ?? catalogModel?.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
202
+ contextWindow: modelsDevMetadata.contextWindow ?? catalogModel?.contextWindow ?? DEFAULT_CONTEXT_WINDOW,
203
+ maxTokens: modelsDevMetadata.maxTokens ?? catalogModel?.maxTokens ?? DEFAULT_MAX_TOKENS,
106
204
  compat: buildCompat(id),
107
205
  };
108
206
  }
@@ -122,8 +220,9 @@ export async function discoverModels(baseUrl, apiKey, options = {}) {
122
220
  if (!listResult.ok) {
123
221
  throw new Error(`/v1/models returned ${listResult.status}`);
124
222
  }
223
+ const modelsDev = await getModelsDevCatalog(options);
125
224
  const models = (listResult.data.data ?? [])
126
- .map(mapFromModelsList)
225
+ .map((entry) => mapFromModelsList(entry, modelsDev))
127
226
  .filter((m) => m !== undefined);
128
227
  return { source: "models_list", models };
129
228
  }
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ const ENV_OFFLINE = "LITELLM_OFFLINE";
13
13
  const DEFAULT_TIMEOUT_MS = 5000;
14
14
  const LOGIN_TIMEOUT_MS = 10_000;
15
15
  const CACHE_FILENAME = "litellm-models.json";
16
+ const CACHE_STALE_MS = 24 * 60 * 60 * 1000;
16
17
  function getAuthPath() {
17
18
  return join(getAgentDir(), "auth.json");
18
19
  }
@@ -72,7 +73,7 @@ async function discoverWithFallback(baseUrl, apiKey, options) {
72
73
  };
73
74
  }
74
75
  }
75
- async function loginLiteLLM(callbacks) {
76
+ async function loginLiteLLM(callbacks, onCacheWrite) {
76
77
  const rawBaseUrl = (await callbacks.onPrompt({
77
78
  message: "Enter LiteLLM proxy URL (no trailing /v1):",
78
79
  placeholder: "https://litellm.example.com",
@@ -85,13 +86,15 @@ async function loginLiteLLM(callbacks) {
85
86
  timeoutMs: LOGIN_TIMEOUT_MS,
86
87
  signal: callbacks.signal,
87
88
  });
88
- await writeCache(getCachePath(), {
89
+ const cache = {
89
90
  baseUrl,
90
91
  apiKeyFingerprint: fingerprint(apiKey),
91
92
  fetchedAt: Date.now(),
92
93
  source,
93
94
  models,
94
- });
95
+ };
96
+ await writeCache(getCachePath(), cache);
97
+ onCacheWrite?.(cache);
95
98
  callbacks.onProgress?.(`LiteLLM: ${models.length} models discovered (source: ${source})`);
96
99
  return {
97
100
  access: apiKey,
@@ -194,6 +197,7 @@ export default async function (pi) {
194
197
  const creds = await resolveCredentials();
195
198
  const cache = await readCache(getCachePath());
196
199
  const fp = creds.apiKey ? fingerprint(creds.apiKey) : undefined;
200
+ let cacheFetchedAt = cache?.fetchedAt ?? 0;
197
201
  const cacheValid = cache !== null &&
198
202
  creds.baseUrl !== undefined &&
199
203
  fp !== undefined &&
@@ -228,6 +232,7 @@ export default async function (pi) {
228
232
  models: result.models,
229
233
  };
230
234
  await writeCache(getCachePath(), next);
235
+ cacheFetchedAt = next.fetchedAt;
231
236
  if (isListModelsMode()) {
232
237
  process.stderr.write(`LiteLLM: ${result.models.length} models discovered (source: ${result.source}).\n`);
233
238
  }
@@ -236,52 +241,68 @@ export default async function (pi) {
236
241
  }
237
242
  const oauth = {
238
243
  name: "LiteLLM",
239
- login: loginLiteLLM,
244
+ login: (callbacks) => loginLiteLLM(callbacks, (next) => {
245
+ cacheFetchedAt = next.fetchedAt;
246
+ }),
240
247
  refreshToken: refreshLiteLLM,
241
248
  getApiKey: (cred) => cred.access,
242
249
  modifyModels: modifyLiteLLMModels,
243
250
  };
244
- pi.registerProvider(PROVIDER_NAME, {
245
- baseUrl: creds.baseUrl ? `${creds.baseUrl}/v1` : "https://litellm.example.com/v1",
246
- apiKey: ENV_API_KEY,
247
- api: "openai-completions",
248
- models,
249
- oauth,
250
- });
251
+ function registerProvider(baseUrl, models) {
252
+ pi.registerProvider(PROVIDER_NAME, {
253
+ baseUrl: baseUrl ? `${baseUrl}/v1` : "https://litellm.example.com/v1",
254
+ apiKey: ENV_API_KEY,
255
+ api: "openai-completions",
256
+ models,
257
+ oauth,
258
+ });
259
+ }
260
+ registerProvider(creds.baseUrl, models);
261
+ const updateCosts = setupLiteLLMCostTracking(pi, models);
262
+ let refreshInProgress = null;
263
+ function discoveryDisabledReason() {
264
+ if (isOffline())
265
+ return `${ENV_OFFLINE}=1`;
266
+ if (getDiscoveryTimeoutMs() === 0)
267
+ return `${ENV_TIMEOUT}=0`;
268
+ return null;
269
+ }
270
+ async function refreshModelsAndCosts() {
271
+ const fresh = await resolveCredentials();
272
+ const freshFp = fresh.apiKey ? fingerprint(fresh.apiKey) : undefined;
273
+ if (!fresh.baseUrl || !fresh.apiKey || !freshFp) {
274
+ throw new Error("no credentials. Run /login litellm or set env vars.");
275
+ }
276
+ const result = await discoverModels(fresh.baseUrl, fresh.apiKey, { timeoutMs: getDiscoveryTimeoutMs() });
277
+ const now = Date.now();
278
+ await writeCache(getCachePath(), {
279
+ baseUrl: fresh.baseUrl,
280
+ apiKeyFingerprint: freshFp,
281
+ fetchedAt: now,
282
+ source: result.source,
283
+ models: result.models,
284
+ });
285
+ registerProvider(fresh.baseUrl, result.models);
286
+ updateCosts(result.models);
287
+ cacheFetchedAt = now;
288
+ return { models: result.models, source: result.source };
289
+ }
290
+ function runRefresh() {
291
+ refreshInProgress ??= refreshModelsAndCosts().finally(() => {
292
+ refreshInProgress = null;
293
+ });
294
+ return refreshInProgress;
295
+ }
251
296
  pi.registerCommand("litellm-refresh", {
252
297
  description: "Re-discover models from the LiteLLM proxy.",
253
298
  handler: async (_args, ctx) => {
254
- if (isOffline()) {
255
- ctx.ui.notify(`LiteLLM refresh disabled (${ENV_OFFLINE}=1)`, "warning");
256
- return;
257
- }
258
- const timeoutMs = getDiscoveryTimeoutMs();
259
- if (timeoutMs === 0) {
260
- ctx.ui.notify(`LiteLLM refresh disabled (${ENV_TIMEOUT}=0)`, "warning");
261
- return;
262
- }
263
- const fresh = await resolveCredentials();
264
- const freshFp = fresh.apiKey ? fingerprint(fresh.apiKey) : undefined;
265
- if (!fresh.baseUrl || !fresh.apiKey || !freshFp) {
266
- ctx.ui.notify("LiteLLM refresh failed: no credentials. Run /login litellm or set env vars.", "error");
299
+ const disabledReason = discoveryDisabledReason();
300
+ if (disabledReason) {
301
+ ctx.ui.notify(`LiteLLM refresh disabled (${disabledReason})`, "warning");
267
302
  return;
268
303
  }
269
304
  try {
270
- const result = await discoverModels(fresh.baseUrl, fresh.apiKey, { timeoutMs });
271
- await writeCache(getCachePath(), {
272
- baseUrl: fresh.baseUrl,
273
- apiKeyFingerprint: freshFp,
274
- fetchedAt: Date.now(),
275
- source: result.source,
276
- models: result.models,
277
- });
278
- pi.registerProvider(PROVIDER_NAME, {
279
- baseUrl: `${fresh.baseUrl}/v1`,
280
- apiKey: ENV_API_KEY,
281
- api: "openai-completions",
282
- models: result.models,
283
- oauth,
284
- });
305
+ const result = await runRefresh();
285
306
  ctx.ui.notify(`LiteLLM: ${result.models.length} models refreshed (source: ${result.source})`, "info");
286
307
  }
287
308
  catch (error) {
@@ -290,10 +311,12 @@ export default async function (pi) {
290
311
  }
291
312
  },
292
313
  });
293
- setupLiteLLMCostTracking(pi);
294
314
  let sessionId;
295
- pi.on("session_start", async (_event, ctx) => {
315
+ pi.on("session_start", (_event, ctx) => {
296
316
  sessionId = getSessionIdFromFile(ctx.sessionManager.getSessionFile());
317
+ if (discoveryDisabledReason() || !cacheFetchedAt || Date.now() - cacheFetchedAt <= CACHE_STALE_MS)
318
+ return;
319
+ void runRefresh().catch(() => undefined);
297
320
  });
298
321
  pi.on("before_provider_request", (event, ctx) => {
299
322
  if (ctx.model?.provider !== PROVIDER_NAME)
package/dist/types.d.ts CHANGED
@@ -34,6 +34,7 @@ export interface ModelInfoResponse {
34
34
  }
35
35
  export interface ModelsListEntry {
36
36
  id?: string;
37
+ owned_by?: string;
37
38
  }
38
39
  export interface ModelsListResponse {
39
40
  data?: ModelsListEntry[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-provider-litellm",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "LiteLLM proxy provider extension for Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -47,15 +47,16 @@
47
47
  "lint": "biome check --error-on-warnings .",
48
48
  "format": "biome check --write .",
49
49
  "test": "vitest --run",
50
+ "supply-chain:guard": "tsx scripts/supply-chain-guard.ts",
50
51
  "check": "npm run lint && npm run typecheck && npm test",
51
- "prepublishOnly": "npm run check && npm run clean && npm run build"
52
+ "prepublishOnly": "npm run check && npm run clean && npm run build && npm run supply-chain:guard"
52
53
  },
53
54
  "engines": {
54
- "node": ">=20.0.0"
55
+ "node": ">=22.19.0"
55
56
  },
56
57
  "peerDependencies": {
57
- "@earendil-works/pi-ai": "^0.74.0",
58
- "@earendil-works/pi-coding-agent": "^0.74.0"
58
+ "@earendil-works/pi-ai": "^0.75.4",
59
+ "@earendil-works/pi-coding-agent": "^0.75.4"
59
60
  },
60
61
  "overrides": {
61
62
  "basic-ftp": "6.0.1",
@@ -63,14 +64,14 @@
63
64
  "protobufjs": "8.2.1"
64
65
  },
65
66
  "devDependencies": {
66
- "@earendil-works/pi-ai": "^0.74.0",
67
- "@earendil-works/pi-coding-agent": "^0.74.0",
68
- "@biomejs/biome": "^1.9.4",
69
- "@types/node": "^24.12.3",
70
- "@typescript/native-preview": "7.0.0-dev.20260120.1",
67
+ "@earendil-works/pi-ai": "^0.75.4",
68
+ "@earendil-works/pi-coding-agent": "^0.75.4",
69
+ "@biomejs/biome": "^2.4.15",
70
+ "@types/node": "^25.9.1",
71
+ "@typescript/native-preview": "7.0.0-dev.20260519.1",
71
72
  "shx": "^0.4.0",
72
73
  "tsx": "^4.21.0",
73
- "typescript": "^5.9.3",
74
- "vitest": "^3.2.4"
74
+ "typescript": "^6.0.3",
75
+ "vitest": "^4.1.7"
75
76
  }
76
77
  }