@hyav/pi-provider 0.1.2 → 0.1.4
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/CHANGELOG.md +23 -0
- package/README.md +30 -4
- package/README.zh-CN.md +30 -4
- package/core/adapter-loader.ts +58 -12
- package/core/catalog-preflight.ts +130 -0
- package/core/credential-type.ts +13 -0
- package/core/host.ts +4 -3
- package/core/official-pricing.ts +2 -3
- package/core/preflight-manager.ts +13 -0
- package/core/public-adapters.ts +47 -0
- package/core/ratelimit-headers.ts +72 -0
- package/core/runtime-config.ts +92 -8
- package/core/runtime-entry.ts +26 -0
- package/core/runtime.ts +23 -10
- package/core/status-manager.ts +7 -1
- package/core/types.ts +12 -0
- package/index.ts +30 -6
- package/package.json +1 -1
- package/preflight/anthropic.ts +42 -0
- package/preflight/cerebras.ts +27 -0
- package/preflight/charm-hyper.ts +2 -4
- package/preflight/deepseek.ts +2 -4
- package/preflight/github-copilot.ts +74 -0
- package/preflight/google.ts +2 -4
- package/preflight/groq.ts +72 -0
- package/preflight/huggingface.ts +27 -0
- package/preflight/mistral.ts +27 -0
- package/preflight/moonshotai-cn.ts +27 -0
- package/preflight/moonshotai.ts +37 -0
- package/preflight/nvidia.ts +27 -0
- package/preflight/openai-codex.ts +2 -4
- package/preflight/openai.ts +27 -0
- package/preflight/opencode-go.ts +2 -3
- package/preflight/opencode.ts +2 -3
- package/preflight/openrouter.ts +111 -0
- package/preflight/vercel-ai-gateway.ts +86 -0
- package/preflight/xai.ts +70 -0
- package/providers/charm-hyper.ts +8 -5
- package/status/anthropic.ts +258 -0
- package/status/charm-hyper.ts +3 -5
- package/status/deepseek.ts +2 -4
- package/status/github-copilot.ts +176 -0
- package/status/groq.ts +88 -0
- package/status/huggingface.ts +94 -0
- package/status/moonshotai-cn.ts +26 -0
- package/status/moonshotai.ts +150 -0
- package/status/openai-codex.ts +2 -4
- package/status/opencode-go.ts +2 -4
- package/status/openrouter.ts +172 -0
- package/status/vercel-ai-gateway/constants.ts +3 -0
- package/status/vercel-ai-gateway.ts +94 -0
- package/status/xai.ts +73 -0
package/core/runtime-config.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import { isValidTimeoutMs } from "./deadline.ts";
|
|
2
4
|
import type { PiProviderDefinition } from "./definition.ts";
|
|
3
5
|
import { getDefaultOpenRouterMetadataCachePath, OPENROUTER_MODELS_URL } from "./official-pricing.ts";
|
|
4
6
|
import { validatePricingPolicy } from "./pricing-adjustments.ts";
|
|
5
|
-
import type { ProviderPricingPolicy } from "./types.ts";
|
|
7
|
+
import type { ProviderPricingPolicy, StoredCredentialLike } from "./types.ts";
|
|
6
8
|
|
|
7
9
|
export interface PiProviderDependencies {
|
|
8
10
|
fetch: typeof globalThis.fetch;
|
|
@@ -14,8 +16,14 @@ export interface PiProviderDependencies {
|
|
|
14
16
|
officialPricingTimeoutMs: number;
|
|
15
17
|
officialPricingCacheTtlMs: number;
|
|
16
18
|
officialPricingMaxStaleMs: number;
|
|
19
|
+
/** Resolved Pi agent directory; empty disables disk persistence of pricing metadata. */
|
|
20
|
+
agentDir: string;
|
|
17
21
|
/** Persistent cache for OpenRouter metadata used by the pricing fallback. */
|
|
18
22
|
openRouterMetadataCachePath: string;
|
|
23
|
+
/** Read Pi's stored credential metadata; injected by the Pi entrypoint. */
|
|
24
|
+
readStoredCredential: (providerId: string) => StoredCredentialLike | undefined;
|
|
25
|
+
/** Wrap ANSI-aware text to a render width; injected by the Pi entrypoint. */
|
|
26
|
+
wrapTextWithAnsi: (text: string, width: number) => string[];
|
|
19
27
|
enableOfficialPricingFallback: boolean;
|
|
20
28
|
/** Optional Pi Provider-level price policies keyed by Provider ID. */
|
|
21
29
|
pricingPolicies?: Record<string, ProviderPricingPolicy>;
|
|
@@ -23,7 +31,68 @@ export interface PiProviderDependencies {
|
|
|
23
31
|
|
|
24
32
|
export type PiProviderLoader = (runtime: PiProviderDependencies) => Promise<PiProviderDefinition>;
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Resolve Pi's agent directory without importing Pi's bundled packages, so the
|
|
36
|
+
* Jiti module graph and programmatic consumers share the same default. Mirrors
|
|
37
|
+
* Pi's `getAgentDir()`: `PI_CODING_AGENT_DIR` wins, `~/` expands to the home
|
|
38
|
+
* directory, and the fallback is `~/.pi/agent`.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveDefaultAgentDir(): string {
|
|
41
|
+
const configured = process.env.PI_CODING_AGENT_DIR;
|
|
42
|
+
const raw =
|
|
43
|
+
configured !== undefined && configured.trim() !== "" ? configured.trim() : join(homedir(), ".pi", "agent");
|
|
44
|
+
if (raw === "~") return homedir();
|
|
45
|
+
if (raw.startsWith("~/")) return join(homedir(), raw.slice(2));
|
|
46
|
+
return raw;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Degraded fallback used only when the Pi entrypoint does not inject the real wrapper. */
|
|
50
|
+
const WIDE_CHAR_RANGES: Array<[number, number]> = [
|
|
51
|
+
[0x1100, 0x115f],
|
|
52
|
+
[0x2329, 0x232a],
|
|
53
|
+
[0x2e80, 0xa4cf],
|
|
54
|
+
[0xac00, 0xd7a3],
|
|
55
|
+
[0xf900, 0xfaff],
|
|
56
|
+
[0xfe30, 0xfe4f],
|
|
57
|
+
[0xff00, 0xff60],
|
|
58
|
+
[0xffe0, 0xffe6],
|
|
59
|
+
[0x1f300, 0x1f64f],
|
|
60
|
+
[0x1f900, 0x1f9ff],
|
|
61
|
+
[0x20000, 0x2fffd],
|
|
62
|
+
[0x30000, 0x3fffd],
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
function displayWidth(text: string): number {
|
|
66
|
+
let width = 0;
|
|
67
|
+
for (const ch of text) {
|
|
68
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
69
|
+
const wide = WIDE_CHAR_RANGES.some(([start, end]) => code >= start && code <= end);
|
|
70
|
+
width += wide ? 2 : 1;
|
|
71
|
+
}
|
|
72
|
+
return width;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function defaultWrapTextWithAnsi(text: string, width: number): string[] {
|
|
76
|
+
if (width <= 0) return [text];
|
|
77
|
+
const plain = text.replace(/\u001b\[[0-9;]*m/g, "");
|
|
78
|
+
if (displayWidth(plain) <= width) return [text];
|
|
79
|
+
const chunks: string[] = [];
|
|
80
|
+
let chunk = "";
|
|
81
|
+
for (const ch of plain) {
|
|
82
|
+
if (chunk !== "" && displayWidth(chunk) + displayWidth(ch) > width) {
|
|
83
|
+
chunks.push(chunk);
|
|
84
|
+
chunk = ch;
|
|
85
|
+
} else {
|
|
86
|
+
chunk += ch;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (chunk !== "") chunks.push(chunk);
|
|
90
|
+
return chunks;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type DefaultDependencies = Omit<PiProviderDependencies, "agentDir" | "openRouterMetadataCachePath">;
|
|
94
|
+
|
|
95
|
+
const defaultDependencies: DefaultDependencies = {
|
|
27
96
|
fetch: globalThis.fetch,
|
|
28
97
|
now: Date.now,
|
|
29
98
|
modelDiscoveryTimeoutMs: 3_000,
|
|
@@ -33,13 +102,22 @@ const defaultDependencies: PiProviderDependencies = {
|
|
|
33
102
|
officialPricingTimeoutMs: 3_000,
|
|
34
103
|
officialPricingCacheTtlMs: 60 * 60 * 1_000,
|
|
35
104
|
officialPricingMaxStaleMs: 24 * 60 * 60 * 1_000,
|
|
36
|
-
|
|
105
|
+
readStoredCredential: () => undefined,
|
|
106
|
+
wrapTextWithAnsi: defaultWrapTextWithAnsi,
|
|
37
107
|
enableOfficialPricingFallback: true,
|
|
38
108
|
pricingPolicies: {},
|
|
39
109
|
};
|
|
40
110
|
|
|
41
|
-
|
|
42
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Programmatic defaults keep the resolved agent directory and its pricing cache
|
|
113
|
+
* path. The Pi entrypoint overrides `agentDir` with Pi's own resolution.
|
|
114
|
+
*/
|
|
115
|
+
export function getDefaultPiProviderDependencies(agentDir = resolveDefaultAgentDir()): PiProviderDependencies {
|
|
116
|
+
return {
|
|
117
|
+
...defaultDependencies,
|
|
118
|
+
agentDir,
|
|
119
|
+
openRouterMetadataCachePath: getDefaultOpenRouterMetadataCachePath(agentDir),
|
|
120
|
+
};
|
|
43
121
|
}
|
|
44
122
|
|
|
45
123
|
export function validatePiProviderDependencies(runtime: PiProviderDependencies): void {
|
|
@@ -64,8 +142,14 @@ export function validatePiProviderDependencies(runtime: PiProviderDependencies):
|
|
|
64
142
|
if (typeof runtime.officialPricingUrl !== "string" || runtime.officialPricingUrl.trim() === "") {
|
|
65
143
|
throw new Error("Pi Provider officialPricingUrl must be a non-empty string");
|
|
66
144
|
}
|
|
67
|
-
if (typeof runtime.openRouterMetadataCachePath !== "string"
|
|
68
|
-
throw new Error("Pi Provider openRouterMetadataCachePath must be a
|
|
145
|
+
if (typeof runtime.openRouterMetadataCachePath !== "string") {
|
|
146
|
+
throw new Error("Pi Provider openRouterMetadataCachePath must be a string");
|
|
147
|
+
}
|
|
148
|
+
if (typeof runtime.readStoredCredential !== "function") {
|
|
149
|
+
throw new Error("Pi Provider readStoredCredential must be a function");
|
|
150
|
+
}
|
|
151
|
+
if (typeof runtime.wrapTextWithAnsi !== "function") {
|
|
152
|
+
throw new Error("Pi Provider wrapTextWithAnsi must be a function");
|
|
69
153
|
}
|
|
70
154
|
if (typeof runtime.enableOfficialPricingFallback !== "boolean") {
|
|
71
155
|
throw new Error("Pi Provider enableOfficialPricingFallback must be a boolean");
|
|
@@ -88,7 +172,7 @@ export function validatePiProviderDependencies(runtime: PiProviderDependencies):
|
|
|
88
172
|
export function resolvePiProviderDependencies(
|
|
89
173
|
dependencies: Partial<PiProviderDependencies> = {},
|
|
90
174
|
): PiProviderDependencies {
|
|
91
|
-
const runtime = { ...
|
|
175
|
+
const runtime = { ...getDefaultPiProviderDependencies(), ...dependencies };
|
|
92
176
|
if (runtime.pricingPolicies === undefined) runtime.pricingPolicies = {};
|
|
93
177
|
validatePiProviderDependencies(runtime);
|
|
94
178
|
return runtime;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { loadPackageAdapterExtensions } from "./adapter-loader.ts";
|
|
3
|
+
import { createPiProviderHost } from "./host.ts";
|
|
4
|
+
import type { PiProviderDependencies } from "./runtime-config.ts";
|
|
5
|
+
import type { StoredCredentialLike } from "./types.ts";
|
|
6
|
+
|
|
7
|
+
/** Runtime values resolved by the Pi-loaded entrypoint and injected into the Jiti graph. */
|
|
8
|
+
export interface PiProviderEntry {
|
|
9
|
+
agentDir: string;
|
|
10
|
+
readStoredCredential: (providerId: string) => StoredCredentialLike | undefined;
|
|
11
|
+
wrapTextWithAnsi: (text: string, width: number) => string[];
|
|
12
|
+
adapterRoot?: string;
|
|
13
|
+
dependencies?: Partial<PiProviderDependencies>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Runs the Pi Provider host and adapter discovery inside a single Jiti module graph. */
|
|
17
|
+
export async function runPiProviderEntry(pi: ExtensionAPI, entry: PiProviderEntry): Promise<void> {
|
|
18
|
+
const piProviderHost = createPiProviderHost({
|
|
19
|
+
agentDir: entry.agentDir,
|
|
20
|
+
readStoredCredential: entry.readStoredCredential,
|
|
21
|
+
wrapTextWithAnsi: entry.wrapTextWithAnsi,
|
|
22
|
+
...entry.dependencies,
|
|
23
|
+
});
|
|
24
|
+
piProviderHost(pi);
|
|
25
|
+
await loadPackageAdapterExtensions(pi, { agentDir: entry.agentDir, userRoot: entry.adapterRoot });
|
|
26
|
+
}
|
package/core/runtime.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { readStoredCredential } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
4
2
|
import type { PiProviderDefinition } from "./definition.ts";
|
|
5
3
|
import { validatePiProviderDefinition } from "./definition.ts";
|
|
6
4
|
import { LiveCheckManager, type LiveCheckResult } from "./live-check-manager.ts";
|
|
@@ -33,6 +31,7 @@ import type {
|
|
|
33
31
|
ProviderCost,
|
|
34
32
|
ProviderModelDraft,
|
|
35
33
|
ProviderModelMetadata,
|
|
34
|
+
StoredCredentialLike,
|
|
36
35
|
} from "./types.ts";
|
|
37
36
|
|
|
38
37
|
type ActiveModel = NonNullable<ExtensionContext["model"]>;
|
|
@@ -41,13 +40,17 @@ type StatusNotificationContext = Pick<ExtensionContext, "modelRegistry" | "ui">
|
|
|
41
40
|
};
|
|
42
41
|
const STATUS_WIDGET_KEY = "pi-provider-status";
|
|
43
42
|
|
|
44
|
-
function readProviderCredentialMetadata(
|
|
43
|
+
function readProviderCredentialMetadata(
|
|
44
|
+
provider: string,
|
|
45
|
+
readStoredCredential: (providerId: string) => StoredCredentialLike | undefined,
|
|
46
|
+
): unknown {
|
|
45
47
|
try {
|
|
46
48
|
const credential = readStoredCredential(provider);
|
|
47
|
-
|
|
49
|
+
const type = credential?.type;
|
|
50
|
+
if (type !== "oauth" && type !== "api_key") return undefined;
|
|
48
51
|
return {
|
|
49
|
-
type
|
|
50
|
-
...(typeof credential
|
|
52
|
+
type,
|
|
53
|
+
...(type === "oauth" && typeof credential?.teamName === "string" ? { teamName: credential.teamName } : {}),
|
|
51
54
|
};
|
|
52
55
|
} catch {
|
|
53
56
|
return undefined;
|
|
@@ -59,7 +62,11 @@ function clearTransientStatus(ctx: Pick<ExtensionContext, "ui">): void {
|
|
|
59
62
|
ctx.ui.setWidget(STATUS_WIDGET_KEY, undefined);
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
function showTransientStatus(
|
|
65
|
+
function showTransientStatus(
|
|
66
|
+
message: string,
|
|
67
|
+
ctx: StatusNotificationContext,
|
|
68
|
+
wrapTextWithAnsi: (text: string, width: number) => string[],
|
|
69
|
+
): boolean {
|
|
63
70
|
if ((ctx.mode !== "tui" && ctx.mode !== "rpc") || typeof ctx.ui.setWidget !== "function") return false;
|
|
64
71
|
// RPC cannot render component factories, so keep its plain text protocol unchanged.
|
|
65
72
|
if (ctx.mode === "rpc") {
|
|
@@ -301,7 +308,7 @@ export function installPiProviderRuntime(
|
|
|
301
308
|
);
|
|
302
309
|
const message = report.report;
|
|
303
310
|
if (report.warningLevel !== "hard") {
|
|
304
|
-
statusPresentationVisible = showTransientStatus(message, ctx);
|
|
311
|
+
statusPresentationVisible = showTransientStatus(message, ctx, runtime.wrapTextWithAnsi);
|
|
305
312
|
if (!statusPresentationVisible) ctx.ui.notify(message, "info");
|
|
306
313
|
return;
|
|
307
314
|
}
|
|
@@ -331,13 +338,19 @@ export function installPiProviderRuntime(
|
|
|
331
338
|
const { status, preflight, auth } = getStatusDetails(model, ctx);
|
|
332
339
|
let liveCheckRequested = false;
|
|
333
340
|
if ((mode === "refresh" || mode === "check") && auth.configured) {
|
|
341
|
+
const getCredentialMetadata = () =>
|
|
342
|
+
readProviderCredentialMetadata(model.provider, runtime.readStoredCredential);
|
|
334
343
|
const statusContext: StatusContextLike = {
|
|
335
344
|
model,
|
|
336
345
|
modelRegistry: ctx.modelRegistry,
|
|
337
346
|
getCredentialKey: () => ctx.modelRegistry.getApiKeyForProvider(model.provider),
|
|
338
|
-
getCredentialMetadata
|
|
347
|
+
getCredentialMetadata,
|
|
348
|
+
};
|
|
349
|
+
const preflightContext: PreflightContextLike = {
|
|
350
|
+
model,
|
|
351
|
+
modelRegistry: ctx.modelRegistry,
|
|
352
|
+
getCredentialMetadata,
|
|
339
353
|
};
|
|
340
|
-
const preflightContext: PreflightContextLike = { model, modelRegistry: ctx.modelRegistry };
|
|
341
354
|
const refreshChecks: Array<Promise<unknown>> = [];
|
|
342
355
|
if (status) refreshChecks.push(statusManager.update(statusContext, { force: true }));
|
|
343
356
|
if (preflight) refreshChecks.push(preflightManager.update(preflightContext, { force: true }));
|
package/core/status-manager.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { deriveCredentialType } from "./credential-type.ts";
|
|
1
2
|
import { isValidTimeoutMs, withDeadline } from "./deadline.ts";
|
|
2
3
|
import { isProviderDataError, ProviderDataError } from "./errors.ts";
|
|
3
4
|
import type {
|
|
@@ -226,7 +227,12 @@ export class StatusManager {
|
|
|
226
227
|
adapter.fetch({
|
|
227
228
|
fetch: this.fetchFn,
|
|
228
229
|
getApiKey: () => ctx.modelRegistry.getApiKeyForProvider(adapter.providerId),
|
|
229
|
-
...(ctx.getCredentialMetadata
|
|
230
|
+
...(ctx.getCredentialMetadata === undefined
|
|
231
|
+
? {}
|
|
232
|
+
: {
|
|
233
|
+
getCredentialMetadata: ctx.getCredentialMetadata,
|
|
234
|
+
getCredentialType: async () => deriveCredentialType(ctx.getCredentialMetadata?.()),
|
|
235
|
+
}),
|
|
230
236
|
now: this.now,
|
|
231
237
|
signal,
|
|
232
238
|
}),
|
package/core/types.ts
CHANGED
|
@@ -22,6 +22,16 @@ export interface ModelMetadataStatus {
|
|
|
22
22
|
source?: string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Narrow credential shape shared across Pi's isolated extension module contexts.
|
|
27
|
+
* The full Credential type comes from Pi's bundled AI package; consumers of
|
|
28
|
+
* injected credential readers must not rely on `instanceof` or extra fields.
|
|
29
|
+
*/
|
|
30
|
+
export interface StoredCredentialLike {
|
|
31
|
+
readonly type?: string;
|
|
32
|
+
readonly teamName?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
export interface ModelFieldSources {
|
|
26
36
|
contextWindow?: ModelFieldSource;
|
|
27
37
|
maxTokens?: ModelFieldSource;
|
|
@@ -137,6 +147,8 @@ export interface StatusContext {
|
|
|
137
147
|
getApiKey: () => Promise<string | undefined>;
|
|
138
148
|
/** Optional non-secret credential metadata for provider-specific account labels. */
|
|
139
149
|
getCredentialMetadata?: () => unknown;
|
|
150
|
+
/** Optional non-secret credential type ("oauth" vs "api_key") for providers with dual auth modes. */
|
|
151
|
+
getCredentialType?: () => Promise<string | undefined>;
|
|
140
152
|
signal?: AbortSignal;
|
|
141
153
|
now: () => number;
|
|
142
154
|
}
|
package/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { getAgentDir, readStoredCredential } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
4
|
+
import { createJiti } from "jiti";
|
|
4
5
|
import type { PiProviderDependencies } from "./core/runtime-config.ts";
|
|
5
6
|
|
|
6
7
|
export type {
|
|
@@ -16,6 +17,8 @@ export {
|
|
|
16
17
|
defineStatusExtension,
|
|
17
18
|
defineTunerExtension,
|
|
18
19
|
} from "./core/adapter-extensions.ts";
|
|
20
|
+
export { createCatalogPreflightAdapter } from "./core/catalog-preflight.ts";
|
|
21
|
+
export { withDeadline } from "./core/deadline.ts";
|
|
19
22
|
export type { ProviderDataErrorLike } from "./core/errors.ts";
|
|
20
23
|
export { isProviderDataError, ProviderDataError } from "./core/errors.ts";
|
|
21
24
|
export type {
|
|
@@ -59,6 +62,7 @@ export {
|
|
|
59
62
|
parseOpenRouterPricing,
|
|
60
63
|
setPricingCache,
|
|
61
64
|
} from "./core/official-pricing.ts";
|
|
65
|
+
export { createOpenCodeCatalogPreflightAdapter } from "./core/opencode-preflight.ts";
|
|
62
66
|
export type {
|
|
63
67
|
PreflightAdapter,
|
|
64
68
|
PreflightContext,
|
|
@@ -70,6 +74,9 @@ export type {
|
|
|
70
74
|
} from "./core/preflight-manager.ts";
|
|
71
75
|
export { getPreflightKey, normalizePreflightSnapshot, PreflightManager } from "./core/preflight-manager.ts";
|
|
72
76
|
export { applyPricingAdjustment, resolvePricingDetails } from "./core/pricing-adjustments.ts";
|
|
77
|
+
export { normalizeProviderModels } from "./core/provider-registration.ts";
|
|
78
|
+
export type { RateLimitWindow } from "./core/ratelimit-headers.ts";
|
|
79
|
+
export { parseRetryAfter } from "./core/retry-after.ts";
|
|
73
80
|
export type { StatusDiagnostics, StatusErrorState } from "./core/status-manager.ts";
|
|
74
81
|
export { normalizeStatusSnapshot, StatusManager } from "./core/status-manager.ts";
|
|
75
82
|
export { applyTunerAdapters, sortTunerAdapters } from "./core/tuner-manager.ts";
|
|
@@ -109,7 +116,7 @@ export type {
|
|
|
109
116
|
} from "./core/types.ts";
|
|
110
117
|
|
|
111
118
|
export interface PiProviderExtensionOptions {
|
|
112
|
-
/**
|
|
119
|
+
/** User adapter root; replaces the default `<agentDir>/pi-provider` directory. */
|
|
113
120
|
adapterRoot?: string;
|
|
114
121
|
/** Host runtime dependency overrides. */
|
|
115
122
|
dependencies?: Partial<PiProviderDependencies>;
|
|
@@ -119,10 +126,27 @@ export interface PiProviderExtensionOptions {
|
|
|
119
126
|
export function createPiProviderExtension(
|
|
120
127
|
options: PiProviderExtensionOptions = {},
|
|
121
128
|
): (pi: ExtensionAPI) => Promise<void> {
|
|
122
|
-
const piProviderHost = createPiProviderHost(options.dependencies);
|
|
123
129
|
return async (pi) => {
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
const jiti = createJiti(import.meta.url, { moduleCache: true, tryNative: false });
|
|
131
|
+
const { runPiProviderEntry } = (await jiti.import("./core/runtime-entry.ts")) as {
|
|
132
|
+
runPiProviderEntry: (
|
|
133
|
+
pi: ExtensionAPI,
|
|
134
|
+
entry: {
|
|
135
|
+
agentDir: string;
|
|
136
|
+
readStoredCredential: typeof readStoredCredential;
|
|
137
|
+
wrapTextWithAnsi: typeof wrapTextWithAnsi;
|
|
138
|
+
adapterRoot?: string;
|
|
139
|
+
dependencies?: Partial<PiProviderDependencies>;
|
|
140
|
+
},
|
|
141
|
+
) => Promise<void>;
|
|
142
|
+
};
|
|
143
|
+
await runPiProviderEntry(pi, {
|
|
144
|
+
agentDir: getAgentDir(),
|
|
145
|
+
readStoredCredential,
|
|
146
|
+
wrapTextWithAnsi,
|
|
147
|
+
adapterRoot: options.adapterRoot,
|
|
148
|
+
dependencies: options.dependencies,
|
|
149
|
+
});
|
|
126
150
|
};
|
|
127
151
|
}
|
|
128
152
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyav/pi-provider",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Provider extension toolkit for Pi to integrate and manage custom LLM providers with dynamic models, request tuners, and account status.",
|
|
5
5
|
"author": "hyav",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PreflightAdapter } from "@hyav/pi-provider";
|
|
2
|
+
import { definePreflightExtension } from "@hyav/pi-provider";
|
|
3
|
+
import { createCatalogPreflightAdapter } from "../core/catalog-preflight.ts";
|
|
4
|
+
import { isAnthropicApiKey, isAnthropicOAuthToken } from "../status/anthropic.ts";
|
|
5
|
+
|
|
6
|
+
export const ANTHROPIC_MODELS_URL = "https://api.anthropic.com/v1/models";
|
|
7
|
+
export const ANTHROPIC_API_VERSION = "2023-06-01";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Anthropic accepts API keys via x-api-key and short-lived OAuth/WIF tokens via
|
|
11
|
+
* Authorization Bearer. Only the user's own default clause receives the key, no
|
|
12
|
+
* unknown/third-party endpoint.
|
|
13
|
+
*/
|
|
14
|
+
function anthropicAuthHeaders(apiKey: string, credential: string | undefined): Record<string, string> {
|
|
15
|
+
const isOAuth = credential === "oauth" ? !isAnthropicApiKey(apiKey) : isAnthropicOAuthToken(apiKey);
|
|
16
|
+
if (isOAuth) return { Authorization: `Bearer ${apiKey}` };
|
|
17
|
+
return { "x-api-key": apiKey };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const anthropicPreflightAdapter: PreflightAdapter = createCatalogPreflightAdapter(
|
|
21
|
+
{
|
|
22
|
+
id: "anthropic-preflight",
|
|
23
|
+
providerId: "anthropic",
|
|
24
|
+
name: "Anthropic",
|
|
25
|
+
modelsUrl: ANTHROPIC_MODELS_URL,
|
|
26
|
+
headers: { "anthropic-version": ANTHROPIC_API_VERSION },
|
|
27
|
+
authHeaders: anthropicAuthHeaders,
|
|
28
|
+
},
|
|
29
|
+
8_000,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export function createAnthropicPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
|
|
33
|
+
return { ...anthropicPreflightAdapter, requestTimeoutMs };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const anthropicPreflightExtension = definePreflightExtension({
|
|
37
|
+
id: "anthropic-preflight",
|
|
38
|
+
providerId: "anthropic",
|
|
39
|
+
create: ({ statusRequestTimeoutMs }) => createAnthropicPreflightAdapter(statusRequestTimeoutMs),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export default anthropicPreflightExtension;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PreflightAdapter } from "@hyav/pi-provider";
|
|
2
|
+
import { definePreflightExtension } from "@hyav/pi-provider";
|
|
3
|
+
import { createCatalogPreflightAdapter } from "../core/catalog-preflight.ts";
|
|
4
|
+
|
|
5
|
+
export const CEREBRAS_MODELS_URL = "https://api.cerebras.ai/v1/models";
|
|
6
|
+
|
|
7
|
+
export const cerebrasPreflightAdapter: PreflightAdapter = createCatalogPreflightAdapter(
|
|
8
|
+
{
|
|
9
|
+
id: "cerebras-preflight",
|
|
10
|
+
providerId: "cerebras",
|
|
11
|
+
name: "Cerebras",
|
|
12
|
+
modelsUrl: CEREBRAS_MODELS_URL,
|
|
13
|
+
},
|
|
14
|
+
8_000,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export function createCerebrasPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
|
|
18
|
+
return { ...cerebrasPreflightAdapter, requestTimeoutMs };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const cerebrasPreflightExtension = definePreflightExtension({
|
|
22
|
+
id: "cerebras-preflight",
|
|
23
|
+
providerId: "cerebras",
|
|
24
|
+
create: ({ statusRequestTimeoutMs }) => createCerebrasPreflightAdapter(statusRequestTimeoutMs),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export default cerebrasPreflightExtension;
|
package/preflight/charm-hyper.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ProviderDataError } from "
|
|
3
|
-
import type { PreflightAdapter } from "../core/preflight-manager.ts";
|
|
4
|
-
import { parseRetryAfter } from "../core/retry-after.ts";
|
|
1
|
+
import type { PreflightAdapter } from "@hyav/pi-provider";
|
|
2
|
+
import { definePreflightExtension, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
|
|
5
3
|
import { hyperJsonHeaders } from "../providers/charm-hyper/constants.ts";
|
|
6
4
|
import { HYPER_MODELS_URL, HYPER_PROVIDER_URL, parseHyperModels } from "../providers/charm-hyper.ts";
|
|
7
5
|
|
package/preflight/deepseek.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ProviderDataError } from "
|
|
3
|
-
import type { PreflightAdapter } from "../core/preflight-manager.ts";
|
|
4
|
-
import { parseRetryAfter } from "../core/retry-after.ts";
|
|
1
|
+
import type { PreflightAdapter } from "@hyav/pi-provider";
|
|
2
|
+
import { definePreflightExtension, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
|
|
5
3
|
|
|
6
4
|
export const DEEPSEEK_MODELS_URL = "https://api.deepseek.com/models";
|
|
7
5
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { PreflightAdapter } from "@hyav/pi-provider";
|
|
2
|
+
import { definePreflightExtension, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
|
|
3
|
+
|
|
4
|
+
export const COPILOT_MODELS_URL = "https://api.individual.githubcopilot.com/models";
|
|
5
|
+
|
|
6
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
7
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const githubCopilotPreflightAdapter: PreflightAdapter = {
|
|
11
|
+
id: "github-copilot-preflight",
|
|
12
|
+
providerId: "github-copilot",
|
|
13
|
+
name: "GitHub Copilot",
|
|
14
|
+
cacheTtlMs: 30_000,
|
|
15
|
+
requestTimeoutMs: 8_000,
|
|
16
|
+
async fetch(context) {
|
|
17
|
+
const apiKey = await context.getApiKey();
|
|
18
|
+
if (!apiKey || apiKey === "proxy-managed") {
|
|
19
|
+
return { passed: false, checks: ["auth"], updatedAt: context.now() };
|
|
20
|
+
}
|
|
21
|
+
const response = await context.fetch(COPILOT_MODELS_URL, {
|
|
22
|
+
headers: {
|
|
23
|
+
Accept: "application/json",
|
|
24
|
+
"Accept-Encoding": "identity",
|
|
25
|
+
Authorization: `Bearer ${apiKey}`,
|
|
26
|
+
"User-Agent": "@hyav/pi-provider",
|
|
27
|
+
},
|
|
28
|
+
signal: context.signal,
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new ProviderDataError(
|
|
32
|
+
`GitHub Copilot preflight failed: HTTP ${response.status}`,
|
|
33
|
+
`http${response.status}`,
|
|
34
|
+
parseRetryAfter(response.headers.get("retry-after"), context.now()),
|
|
35
|
+
response.status,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
let payload: unknown;
|
|
39
|
+
try {
|
|
40
|
+
payload = await response.json();
|
|
41
|
+
} catch {
|
|
42
|
+
throw new ProviderDataError("GitHub Copilot preflight returned invalid JSON", "badjson");
|
|
43
|
+
}
|
|
44
|
+
if (!isRecord(payload) || !Array.isArray(payload.data)) {
|
|
45
|
+
throw new ProviderDataError("GitHub Copilot preflight returned invalid catalog data", "badjson");
|
|
46
|
+
}
|
|
47
|
+
const modelIds = new Set(
|
|
48
|
+
payload.data.flatMap((model) => {
|
|
49
|
+
if (!isRecord(model) || typeof model.id !== "string") return [];
|
|
50
|
+
const id = model.id.trim();
|
|
51
|
+
if (id === "") return [];
|
|
52
|
+
return [id];
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
return {
|
|
56
|
+
passed: modelIds.has(context.model.id),
|
|
57
|
+
checks: ["endpoint", "catalog", "auth"],
|
|
58
|
+
updatedAt: context.now(),
|
|
59
|
+
httpStatus: response.status,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function createGithubCopilotPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
|
|
65
|
+
return { ...githubCopilotPreflightAdapter, requestTimeoutMs };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const githubCopilotPreflightExtension = definePreflightExtension({
|
|
69
|
+
id: "github-copilot-preflight",
|
|
70
|
+
providerId: "github-copilot",
|
|
71
|
+
create: ({ statusRequestTimeoutMs }) => createGithubCopilotPreflightAdapter(statusRequestTimeoutMs),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export default githubCopilotPreflightExtension;
|
package/preflight/google.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ProviderDataError } from "
|
|
3
|
-
import type { PreflightAdapter } from "../core/preflight-manager.ts";
|
|
4
|
-
import { parseRetryAfter } from "../core/retry-after.ts";
|
|
1
|
+
import type { PreflightAdapter } from "@hyav/pi-provider";
|
|
2
|
+
import { definePreflightExtension, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
|
|
5
3
|
|
|
6
4
|
export const GOOGLE_MODELS_URL = "https://generativelanguage.googleapis.com/v1beta/models";
|
|
7
5
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { PreflightAdapter } from "@hyav/pi-provider";
|
|
2
|
+
import { definePreflightExtension, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
|
|
3
|
+
import { GROQ_MODELS_URL } from "../status/groq.ts";
|
|
4
|
+
|
|
5
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
6
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const groqPreflightAdapter: PreflightAdapter = {
|
|
10
|
+
id: "groq-preflight",
|
|
11
|
+
providerId: "groq",
|
|
12
|
+
name: "Groq",
|
|
13
|
+
cacheTtlMs: 30_000,
|
|
14
|
+
requestTimeoutMs: 8_000,
|
|
15
|
+
async fetch(context) {
|
|
16
|
+
const apiKey = await context.getApiKey();
|
|
17
|
+
if (!apiKey || apiKey === "proxy-managed") {
|
|
18
|
+
return { passed: false, checks: ["auth"], updatedAt: context.now() };
|
|
19
|
+
}
|
|
20
|
+
const response = await context.fetch(GROQ_MODELS_URL, {
|
|
21
|
+
headers: {
|
|
22
|
+
Accept: "application/json",
|
|
23
|
+
"Accept-Encoding": "identity",
|
|
24
|
+
Authorization: `Bearer ${apiKey}`,
|
|
25
|
+
},
|
|
26
|
+
signal: context.signal,
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new ProviderDataError(
|
|
30
|
+
`Groq preflight failed: HTTP ${response.status}`,
|
|
31
|
+
`http${response.status}`,
|
|
32
|
+
parseRetryAfter(response.headers.get("retry-after"), context.now()),
|
|
33
|
+
response.status,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
let payload: unknown;
|
|
37
|
+
try {
|
|
38
|
+
payload = await response.json();
|
|
39
|
+
} catch {
|
|
40
|
+
throw new ProviderDataError("Groq preflight returned invalid JSON", "badjson");
|
|
41
|
+
}
|
|
42
|
+
if (!isRecord(payload) || !Array.isArray(payload.data)) {
|
|
43
|
+
throw new ProviderDataError("Groq preflight returned invalid catalog data", "badjson");
|
|
44
|
+
}
|
|
45
|
+
const activeIds = new Set(
|
|
46
|
+
payload.data.flatMap((model) => {
|
|
47
|
+
if (!isRecord(model) || typeof model.id !== "string") return [];
|
|
48
|
+
const id = model.id.trim();
|
|
49
|
+
if (id === "" || model.active === false) return [];
|
|
50
|
+
return [id];
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
return {
|
|
54
|
+
passed: activeIds.has(context.model.id),
|
|
55
|
+
checks: ["endpoint", "auth", "catalog"],
|
|
56
|
+
updatedAt: context.now(),
|
|
57
|
+
httpStatus: response.status,
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export function createGroqPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
|
|
63
|
+
return { ...groqPreflightAdapter, requestTimeoutMs };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const groqPreflightExtension = definePreflightExtension({
|
|
67
|
+
id: "groq-preflight",
|
|
68
|
+
providerId: "groq",
|
|
69
|
+
create: ({ statusRequestTimeoutMs }) => createGroqPreflightAdapter(statusRequestTimeoutMs),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export default groqPreflightExtension;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PreflightAdapter } from "@hyav/pi-provider";
|
|
2
|
+
import { definePreflightExtension } from "@hyav/pi-provider";
|
|
3
|
+
import { createCatalogPreflightAdapter } from "../core/catalog-preflight.ts";
|
|
4
|
+
|
|
5
|
+
export const HF_ROUTER_MODELS_URL = "https://router.huggingface.co/v1/models";
|
|
6
|
+
|
|
7
|
+
export const huggingFacePreflightAdapter: PreflightAdapter = createCatalogPreflightAdapter(
|
|
8
|
+
{
|
|
9
|
+
id: "huggingface-preflight",
|
|
10
|
+
providerId: "huggingface",
|
|
11
|
+
name: "Hugging Face",
|
|
12
|
+
modelsUrl: HF_ROUTER_MODELS_URL,
|
|
13
|
+
},
|
|
14
|
+
8_000,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export function createHuggingFacePreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
|
|
18
|
+
return { ...huggingFacePreflightAdapter, requestTimeoutMs };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const huggingFacePreflightExtension = definePreflightExtension({
|
|
22
|
+
id: "huggingface-preflight",
|
|
23
|
+
providerId: "huggingface",
|
|
24
|
+
create: ({ statusRequestTimeoutMs }) => createHuggingFacePreflightAdapter(statusRequestTimeoutMs),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export default huggingFacePreflightExtension;
|