pi-cliproxyapi-native 0.3.1 → 0.3.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 +46 -23
- package/extensions/index.ts +35 -26
- package/package.json +1 -1
- package/src/catalog.ts +124 -21
- package/src/config.ts +11 -0
- package/src/media.ts +475 -120
- package/src/picker.ts +2 -3
- package/src/provider.ts +38 -59
package/src/picker.ts
CHANGED
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
type TuiMouseEvent,
|
|
8
8
|
truncateToWidth,
|
|
9
9
|
} from "@earendil-works/pi-tui";
|
|
10
|
-
import { backendLabel, mediaCapability, parseCatalog, routedName } from "./catalog.ts";
|
|
11
|
-
import type
|
|
10
|
+
import { backendLabel, fetchCatalog, mediaCapability, parseCatalog, routedName } from "./catalog.ts";
|
|
11
|
+
import { type Config, deadline } from "./config.ts";
|
|
12
12
|
import { mediaKey } from "./media.ts";
|
|
13
13
|
import {
|
|
14
14
|
effectiveMediaDefaults,
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
type MediaDefaults,
|
|
17
17
|
readMediaDefaults,
|
|
18
18
|
} from "./media-defaults.ts";
|
|
19
|
-
import { deadline, fetchCatalog } from "./provider.ts";
|
|
20
19
|
|
|
21
20
|
export function pickerCatalog(value: unknown) {
|
|
22
21
|
const rows = new Map<
|
package/src/provider.ts
CHANGED
|
@@ -18,10 +18,19 @@ import {
|
|
|
18
18
|
openAIResponsesApi,
|
|
19
19
|
} from "@earendil-works/pi-ai/compat";
|
|
20
20
|
import { getBuiltinModels } from "@earendil-works/pi-ai/providers/all";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
type CpaApi,
|
|
23
|
+
endpoint,
|
|
24
|
+
fetchCatalog,
|
|
25
|
+
IMAGE_API,
|
|
26
|
+
mapCatalog,
|
|
27
|
+
mapImageModels,
|
|
28
|
+
modelRoute,
|
|
29
|
+
} from "./catalog.ts";
|
|
22
30
|
import { type QuotaWaitHandler, waitForClaudeQuota } from "./claude-quota.ts";
|
|
23
31
|
import { withCompactionRecovery } from "./compaction.ts";
|
|
24
|
-
import { type Config, isRecord, PROVIDER_ID } from "./config.ts";
|
|
32
|
+
import { type Config, isRecord, PROVIDER_ID, validateKey } from "./config.ts";
|
|
33
|
+
import { generateNativeImages } from "./media.ts";
|
|
25
34
|
|
|
26
35
|
export type { QuotaWaitHandler } from "./claude-quota.ts";
|
|
27
36
|
|
|
@@ -31,63 +40,21 @@ export function builtinCatalog() {
|
|
|
31
40
|
...getBuiltinModels("openai"),
|
|
32
41
|
...getBuiltinModels("openai-codex"),
|
|
33
42
|
...getBuiltinModels("google"),
|
|
43
|
+
...getBuiltinModels("xai"),
|
|
34
44
|
];
|
|
35
45
|
}
|
|
36
46
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function fetchCatalog(config: Config, key: string, signal: AbortSignal) {
|
|
49
|
-
validateKey(key);
|
|
50
|
-
const boundedSignal = deadline(signal, 10000);
|
|
51
|
-
let response: Response;
|
|
52
|
-
try {
|
|
53
|
-
response = await fetch(`${config.baseUrl}/v1/models`, {
|
|
54
|
-
headers: { Authorization: `Bearer ${key}` },
|
|
55
|
-
redirect: "error",
|
|
56
|
-
signal: boundedSignal,
|
|
57
|
-
});
|
|
58
|
-
} catch {
|
|
59
|
-
signal.throwIfAborted();
|
|
60
|
-
throw new Error("CLIProxyAPI discovery failed: connection, timeout, or redirect error.");
|
|
61
|
-
}
|
|
62
|
-
if (!response.ok) {
|
|
63
|
-
await response.body?.cancel();
|
|
64
|
-
throw new Error(`CLIProxyAPI discovery failed (HTTP ${response.status}).`);
|
|
65
|
-
}
|
|
66
|
-
let payload: unknown;
|
|
67
|
-
const reader = response.body?.getReader();
|
|
68
|
-
if (!reader) throw new Error("CLIProxyAPI discovery returned an empty response.");
|
|
69
|
-
try {
|
|
70
|
-
const decoder = new TextDecoder();
|
|
71
|
-
let text = "";
|
|
72
|
-
let size = 0;
|
|
73
|
-
while (true) {
|
|
74
|
-
const chunk = await reader.read();
|
|
75
|
-
if (chunk.done) break;
|
|
76
|
-
size += chunk.value.byteLength;
|
|
77
|
-
if (size > 4 * 1024 * 1024) {
|
|
78
|
-
await reader.cancel();
|
|
79
|
-
throw new Error("Catalog too large.");
|
|
80
|
-
}
|
|
81
|
-
text += decoder.decode(chunk.value, { stream: true });
|
|
82
|
-
}
|
|
83
|
-
payload = JSON.parse(text + decoder.decode());
|
|
84
|
-
} catch {
|
|
85
|
-
signal.throwIfAborted();
|
|
86
|
-
throw new Error("CLIProxyAPI catalog is invalid, incomplete, or larger than 4 MiB.");
|
|
87
|
-
} finally {
|
|
88
|
-
reader.releaseLock();
|
|
89
|
-
}
|
|
90
|
-
return payload;
|
|
47
|
+
// Pi's live catalogs add models released after this Pi build; bundled metadata wins for known IDs.
|
|
48
|
+
export function metadataCatalog(live?: (provider: string) => readonly Model<Api>[] | undefined) {
|
|
49
|
+
const bundled = builtinCatalog();
|
|
50
|
+
if (!live) return bundled;
|
|
51
|
+
const ids = new Set(bundled.map(({ provider, id }) => `${provider}/${id}`));
|
|
52
|
+
return [
|
|
53
|
+
...bundled,
|
|
54
|
+
...["anthropic", "openai", "openai-codex", "google", "xai"]
|
|
55
|
+
.flatMap((provider) => live(provider) ?? [])
|
|
56
|
+
.filter(({ provider, id }) => !ids.has(`${provider}/${id}`)),
|
|
57
|
+
];
|
|
91
58
|
}
|
|
92
59
|
|
|
93
60
|
export async function discover(
|
|
@@ -345,10 +312,12 @@ function route(
|
|
|
345
312
|
|
|
346
313
|
export function createCliproxyProvider(
|
|
347
314
|
config: Config,
|
|
348
|
-
known: readonly Model<Api>[] = builtinCatalog
|
|
315
|
+
known: readonly Model<Api>[] | (() => readonly Model<Api>[]) = builtinCatalog,
|
|
349
316
|
onQuotaWait?: QuotaWaitHandler,
|
|
350
317
|
compactionSignals = new WeakSet<AbortSignal>(),
|
|
351
318
|
) {
|
|
319
|
+
const metadata = typeof known === "function" ? known : () => known;
|
|
320
|
+
let skipped: string[] = [];
|
|
352
321
|
const standardAuth = envApiKeyAuth("CLIProxyAPI API key", ["CLIPROXYAPI_API_KEY"]);
|
|
353
322
|
const scope = createHash("sha256")
|
|
354
323
|
.update(JSON.stringify([5, { baseUrl: config.baseUrl, aliases: config.aliases }]))
|
|
@@ -364,7 +333,7 @@ export function createCliproxyProvider(
|
|
|
364
333
|
const credential = await standardAuth.login?.(interaction);
|
|
365
334
|
if (!credential?.key) throw new Error("CLIProxyAPI requires an API key.");
|
|
366
335
|
validateKey(credential.key);
|
|
367
|
-
await discover(config, credential.key, interaction.signal,
|
|
336
|
+
await discover(config, credential.key, interaction.signal, metadata());
|
|
368
337
|
return credential;
|
|
369
338
|
},
|
|
370
339
|
async resolve(input) {
|
|
@@ -380,7 +349,15 @@ export function createCliproxyProvider(
|
|
|
380
349
|
if (context.credential?.type !== "api_key" || !context.credential.key) {
|
|
381
350
|
throw new Error("Use /login cliproxyapi or set CLIPROXYAPI_API_KEY before refreshing.");
|
|
382
351
|
}
|
|
383
|
-
|
|
352
|
+
const payload = await fetchCatalog(config, context.credential.key, context.signal);
|
|
353
|
+
const chat = mapCatalog(payload, config, metadata());
|
|
354
|
+
skipped = chat.skipped;
|
|
355
|
+
return [...chat.models, ...mapImageModels(payload, config)];
|
|
356
|
+
},
|
|
357
|
+
images: {
|
|
358
|
+
[IMAGE_API]: {
|
|
359
|
+
generateImages: (model, context, options) => generateNativeImages(config, model, context, options),
|
|
360
|
+
},
|
|
384
361
|
},
|
|
385
362
|
api: {
|
|
386
363
|
"anthropic-messages": route(anthropicMessagesApi(), config, compactionSignals, onQuotaWait),
|
|
@@ -392,6 +369,8 @@ export function createCliproxyProvider(
|
|
|
392
369
|
const refresh = provider.refreshModels;
|
|
393
370
|
return {
|
|
394
371
|
...provider,
|
|
372
|
+
// Chat IDs without Pi metadata from the latest network refresh, for diagnostics.
|
|
373
|
+
skippedModels: () => skipped,
|
|
395
374
|
async refreshModels(context: RefreshModelsContext) {
|
|
396
375
|
await refresh?.({
|
|
397
376
|
...context,
|