@juvio15/pi-cliproxyapi-provider 0.1.0 → 0.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juvio15/pi-cliproxyapi-provider",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "pi provider for CLIProxyAPI: per-family APIs, model catalog, costs, web search, usage footer",
5
5
  "keywords": [
6
6
  "cliproxyapi",
@@ -368,6 +368,13 @@ export const fetchCatalog = async (
368
368
  return "models" in payload ? payload.models : payload.data;
369
369
  };
370
370
 
371
+ /**
372
+ * Drops models the latest catalog no longer lists. mergeCatalog keeps them so
373
+ * a session that uses one still resolves, but nobody should pick one anew.
374
+ */
375
+ export const withoutStale = <T extends object>(models: readonly T[]): T[] =>
376
+ models.filter((model) => !("stale" in model && model.stale === true));
377
+
371
378
  export const mergeCatalog = (
372
379
  previous: readonly CliproxyModel[],
373
380
  fresh: readonly CliproxyModel[],
package/src/core/index.ts CHANGED
@@ -13,6 +13,7 @@
13
13
  * matchModelCost(modelId: string, catalog: CostCatalog, fast?: boolean): ModelCost
14
14
  * mapCatalog(entries: readonly CatalogModel[], options: MappingOptions): MappedCatalog
15
15
  * mergeCatalog(previous: readonly CliproxyModel[], fresh: readonly CliproxyModel[], options?: MergeOptions): CliproxyModel[]
16
+ * withoutStale<T extends object>(models: readonly T[]): T[]
16
17
  * wrapCliproxyStreams(streams: ProviderStreams, settings: PayloadSettings): ProviderStreams
17
18
  * createCliproxyProvider(options: CliproxyProviderOptions): Provider<CliproxyApi>
18
19
  * quotaResetFromError(message: string, now?: number): number | undefined
@@ -23,13 +24,21 @@
23
24
  * resetCooldowns(options: ResetCooldownsOptions): Promise<string[]>
24
25
  * unavailableModels(usage: readonly ProviderUsage[], exhausted: Readonly<Record<string, number>>, now?: number): Map<string, number>
25
26
  */
26
- import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
27
- import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
28
- import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
27
+ // pi maps only some pi-ai entry points into extensions. compat re-exports the same lazy API factories.
28
+ import {
29
+ anthropicMessagesApi,
30
+ openAICompletionsApi,
31
+ openAIResponsesApi,
32
+ } from "@earendil-works/pi-ai/compat";
29
33
  import { createProvider } from "@earendil-works/pi-ai/models";
30
34
  import type { Provider } from "@earendil-works/pi-ai/models";
31
35
 
32
- import { fetchCatalog, mapCatalog, mergeCatalog } from "./catalog.ts";
36
+ import {
37
+ fetchCatalog,
38
+ mapCatalog,
39
+ mergeCatalog,
40
+ withoutStale,
41
+ } from "./catalog.ts";
33
42
  import type { CliproxyApi, CliproxyModel, MappingOptions } from "./catalog.ts";
34
43
  import { resolveEndpoints } from "./connection.ts";
35
44
  import { fetchCosts } from "./costs.ts";
@@ -43,6 +52,7 @@ export {
43
52
  mapCatalog,
44
53
  mergeCatalog,
45
54
  selectModelApi,
55
+ withoutStale,
46
56
  type CatalogFetchOptions,
47
57
  type CatalogModel,
48
58
  type CliproxyApi,
@@ -177,6 +187,7 @@ export const createCliproxyProvider = (
177
187
  staleTtlMs: options.staleTtlMs,
178
188
  });
179
189
  },
190
+ filterModels: (models) => withoutStale(models),
180
191
  id,
181
192
  models: options.models ?? [],
182
193
  name: options.providerName ?? "CLIProxyAPI",
@@ -4,7 +4,11 @@ import type {
4
4
  ExtensionContext,
5
5
  } from "@earendil-works/pi-coding-agent";
6
6
 
7
- import { resolveConnectionSources, resolveEndpoints } from "../core/index.ts";
7
+ import {
8
+ resolveConnectionSources,
9
+ resolveEndpoints,
10
+ withoutStale,
11
+ } from "../core/index.ts";
8
12
  import type { CliproxyModel } from "../core/index.ts";
9
13
  import { completeFrom } from "./completions.ts";
10
14
  import {
@@ -140,7 +144,7 @@ const registerCatalogCommands = (
140
144
  const models = controller.getModels();
141
145
  const stale = models.filter((model) => model.stale).length;
142
146
  ctx.ui.notify(
143
- `Refreshed ${models.length} CLIProxyAPI models${stale ? ` (${stale} retained from cache)` : ""}.`,
147
+ `Refreshed ${models.length - stale} CLIProxyAPI models${stale ? `. Hid ${stale} that CLIProxyAPI no longer lists` : ""}.`,
144
148
  "info"
145
149
  );
146
150
  } catch (error) {
@@ -229,7 +233,7 @@ export default async function cliproxyapiProvider(
229
233
  configured: connection !== null,
230
234
  connection: initialConnection,
231
235
  fast,
232
- filterAvailable: (models) => tracker.filter(models),
236
+ filterAvailable: (models) => tracker.filter(withoutStale(models)),
233
237
  onModels: (models) => {
234
238
  latestModels = models;
235
239
 
@@ -370,8 +374,9 @@ export default async function cliproxyapiProvider(
370
374
  });
371
375
  registerWebSearch(pi, {
372
376
  agentDir,
373
- getAvailableModels: () => tracker.filter(controller.getModels()),
374
- getModels: controller.getModels,
377
+ getAvailableModels: () =>
378
+ tracker.filter(withoutStale(controller.getModels())),
379
+ getModels: () => withoutStale(controller.getModels()),
375
380
  getStandardCost: controller.getStandardCost,
376
381
  providerId,
377
382
  });
@@ -1,6 +1,5 @@
1
1
  import path from "node:path";
2
2
 
3
- import { ModelCostSchema } from "@earendil-works/pi-ai/providers/model-schema";
4
3
  import { Type } from "typebox";
5
4
  import type { Static } from "typebox";
6
5
 
@@ -22,6 +21,21 @@ const MINUTE_MS = 60_000;
22
21
  const HOUR_MS = 60 * MINUTE_MS;
23
22
  const DAY_MS = 24 * HOUR_MS;
24
23
 
24
+ const RateFields = {
25
+ cacheRead: Type.Number(),
26
+ cacheWrite: Type.Number(),
27
+ input: Type.Number(),
28
+ output: Type.Number(),
29
+ };
30
+
31
+ /** pi-ai's ModelCost. pi does not expose pi-ai's model-schema module to extensions, and typecheck holds this to ModelCost where the file feeds costCatalogFromPrices. */
32
+ const ModelCostSchema = Type.Object({
33
+ ...RateFields,
34
+ tiers: Type.Optional(
35
+ Type.Array(Type.Object({ ...RateFields, inputTokensAbove: Type.Number() }))
36
+ ),
37
+ });
38
+
25
39
  const PricesFileSchema = Type.Object({
26
40
  etag: Type.Optional(Type.String()),
27
41
  fetchedAt: Type.Number(),
@@ -1,8 +1,8 @@
1
1
  import type { ModelPromptCache } from "@earendil-works/pi-ai";
2
- import { ANTHROPIC_MODELS } from "@earendil-works/pi-ai/providers/anthropic.models";
2
+ import { getBuiltinModels } from "@earendil-works/pi-ai/providers/all";
3
3
 
4
4
  const LIFETIMES = new Map<string, ModelPromptCache | undefined>(
5
- Object.entries(ANTHROPIC_MODELS).map(([id, model]) => [id, model.promptCache])
5
+ getBuiltinModels("anthropic").map((model) => [model.id, model.promptCache])
6
6
  );
7
7
 
8
8
  /** pi-ai's own Anthropic lifetimes, matched on the id or its segment after the last "/". */