@hyav/pi-provider 0.1.1 → 0.1.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/CHANGELOG.md +17 -1
- package/README.md +27 -6
- package/README.zh-CN.md +27 -6
- package/SECURITY.md +1 -1
- package/core/adapter-extensions.ts +14 -14
- package/core/adapter-loader.ts +99 -0
- package/core/adapter-protocol.ts +11 -11
- package/core/definition.ts +8 -8
- package/core/extension.ts +9 -9
- package/core/host.ts +30 -29
- package/core/official-pricing.ts +2 -6
- package/core/provider-registration.ts +5 -5
- package/core/public-adapters.ts +46 -0
- package/core/runtime-config.ts +110 -26
- package/core/runtime-entry.ts +26 -0
- package/core/runtime.ts +33 -27
- package/core/status-report.ts +2 -2
- package/core/types.ts +11 -1
- package/index.ts +56 -13
- package/package.json +8 -8
- package/preflight/charm-hyper.ts +2 -4
- package/preflight/deepseek.ts +2 -4
- package/preflight/google.ts +2 -4
- package/preflight/openai-codex.ts +2 -4
- package/preflight/opencode-go.ts +2 -3
- package/preflight/opencode.ts +2 -3
- package/providers/charm-hyper.ts +8 -5
- package/status/charm-hyper.ts +2 -4
- package/status/deepseek.ts +2 -4
- package/status/openai-codex.ts +2 -4
- package/status/opencode-go.ts +2 -4
package/core/official-pricing.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
3
|
import { dirname, join } from "node:path";
|
|
5
4
|
import { withDeadline } from "./deadline.ts";
|
|
6
5
|
import type { ModelQualityScore, ProviderCost, ProviderModel, ProviderModelDraft } from "./types.ts";
|
|
@@ -68,11 +67,8 @@ const pricingCache = new Map<string, PricingCacheEntry>();
|
|
|
68
67
|
const pricingRequests = new Map<string, Promise<Record<string, OfficialModelMeta>>>();
|
|
69
68
|
|
|
70
69
|
/** Default cache for OpenRouter metadata, not Pi's native model catalog. */
|
|
71
|
-
export function getDefaultOpenRouterMetadataCachePath(): string {
|
|
72
|
-
|
|
73
|
-
const agentDir =
|
|
74
|
-
configuredAgentDir && configuredAgentDir.trim() !== "" ? configuredAgentDir : join(homedir(), ".pi", "agent");
|
|
75
|
-
return join(agentDir, "provider-kit", "openrouter-model-metadata.json");
|
|
70
|
+
export function getDefaultOpenRouterMetadataCachePath(agentDir: string): string {
|
|
71
|
+
return join(agentDir, "extensions", "pi-provider", "openrouter-model-metadata.json");
|
|
76
72
|
}
|
|
77
73
|
|
|
78
74
|
function cloneCost(cost: ProviderCost): ProviderCost {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ProviderConfig } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { applyOfficialModelCosts, findOfficialMeta, type OfficialModelMeta } from "./official-pricing.ts";
|
|
3
3
|
import { resolvePricingDetails } from "./pricing-adjustments.ts";
|
|
4
|
-
import type {
|
|
4
|
+
import type { PiProviderDependencies } from "./runtime-config.ts";
|
|
5
5
|
import type {
|
|
6
6
|
ProviderAdapter,
|
|
7
7
|
ProviderCost,
|
|
@@ -109,7 +109,7 @@ function selectPricingAdjustment(
|
|
|
109
109
|
|
|
110
110
|
function resolveModelRegistration(
|
|
111
111
|
adapter: ProviderAdapter,
|
|
112
|
-
runtime:
|
|
112
|
+
runtime: PiProviderDependencies,
|
|
113
113
|
modelDrafts: ProviderModelDraft[],
|
|
114
114
|
officialPricing: Record<string, OfficialModelMeta>,
|
|
115
115
|
): { models: ProviderModel[]; modelMetadata: Record<string, ProviderModelMetadata> } {
|
|
@@ -184,7 +184,7 @@ function getErrorCode(error: unknown): string {
|
|
|
184
184
|
*/
|
|
185
185
|
export function prepareProviderRegistration(
|
|
186
186
|
adapter: ProviderAdapter,
|
|
187
|
-
runtime:
|
|
187
|
+
runtime: PiProviderDependencies,
|
|
188
188
|
officialPricing: Record<string, OfficialModelMeta> = {},
|
|
189
189
|
modelDrafts?: ProviderModelDraft[],
|
|
190
190
|
): ProviderConfig {
|
|
@@ -239,7 +239,7 @@ export function prepareProviderRegistration(
|
|
|
239
239
|
export function refreshProviderRegistrations(
|
|
240
240
|
pi: Pick<ExtensionAPI, "registerProvider">,
|
|
241
241
|
providers: readonly ProviderAdapter[],
|
|
242
|
-
runtime:
|
|
242
|
+
runtime: PiProviderDependencies,
|
|
243
243
|
officialPricing: Record<string, OfficialModelMeta>,
|
|
244
244
|
providerDrafts?: ReadonlyMap<ProviderAdapter, ProviderModelDraft[]>,
|
|
245
245
|
): void {
|
|
@@ -251,7 +251,7 @@ export function refreshProviderRegistrations(
|
|
|
251
251
|
export function registerProviderAdapter(
|
|
252
252
|
pi: Pick<ExtensionAPI, "registerProvider">,
|
|
253
253
|
adapter: ProviderAdapter,
|
|
254
|
-
runtime:
|
|
254
|
+
runtime: PiProviderDependencies,
|
|
255
255
|
officialPricing: Record<string, OfficialModelMeta> = {},
|
|
256
256
|
modelDrafts?: ProviderModelDraft[],
|
|
257
257
|
): ProviderConfig {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jiti-safe public entrypoint for user adapter files discovered from the
|
|
3
|
+
* adapter roots. `loadPackageAdapterExtensions` aliases `@hyav/pi-provider`
|
|
4
|
+
* to this module so adapter files can import the same helpers and types the
|
|
5
|
+
* built-in adapters use, without resolving Pi's bundled runtime packages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
AdapterExtensionContext,
|
|
10
|
+
PreflightExtensionDefinition,
|
|
11
|
+
ProviderExtensionDefinition,
|
|
12
|
+
StatusExtensionDefinition,
|
|
13
|
+
TunerExtensionDefinition,
|
|
14
|
+
} from "./adapter-extensions.ts";
|
|
15
|
+
export {
|
|
16
|
+
definePreflightExtension,
|
|
17
|
+
defineProviderExtension,
|
|
18
|
+
defineStatusExtension,
|
|
19
|
+
defineTunerExtension,
|
|
20
|
+
} from "./adapter-extensions.ts";
|
|
21
|
+
export { withDeadline } from "./deadline.ts";
|
|
22
|
+
export { isProviderDataError, ProviderDataError } from "./errors.ts";
|
|
23
|
+
export { createOpenCodeCatalogPreflightAdapter } from "./opencode-preflight.ts";
|
|
24
|
+
export type {
|
|
25
|
+
PreflightAdapter,
|
|
26
|
+
PreflightContextLike,
|
|
27
|
+
PreflightModel,
|
|
28
|
+
PreflightSnapshot,
|
|
29
|
+
} from "./preflight-manager.ts";
|
|
30
|
+
export { normalizeProviderModels } from "./provider-registration.ts";
|
|
31
|
+
export { parseRetryAfter } from "./retry-after.ts";
|
|
32
|
+
export type { StatusContextLike } from "./status-manager.ts";
|
|
33
|
+
export type {
|
|
34
|
+
ActiveModel,
|
|
35
|
+
ModelCatalogStatus,
|
|
36
|
+
ProviderAdapter,
|
|
37
|
+
ProviderModel,
|
|
38
|
+
ProviderModelDraft,
|
|
39
|
+
ProviderRefreshContext,
|
|
40
|
+
StatusAdapter,
|
|
41
|
+
StatusContext,
|
|
42
|
+
StatusEntry,
|
|
43
|
+
StatusSnapshot,
|
|
44
|
+
StoredCredentialLike,
|
|
45
|
+
TunerContext,
|
|
46
|
+
} from "./types.ts";
|
package/core/runtime-config.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import { isValidTimeoutMs } from "./deadline.ts";
|
|
2
|
-
import type {
|
|
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
|
-
export interface
|
|
9
|
+
export interface PiProviderDependencies {
|
|
8
10
|
fetch: typeof globalThis.fetch;
|
|
9
11
|
now: () => number;
|
|
10
12
|
modelDiscoveryTimeoutMs: number;
|
|
@@ -14,16 +16,83 @@ export interface ProviderKitDependencies {
|
|
|
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
|
-
/** Optional Provider
|
|
28
|
+
/** Optional Pi Provider-level price policies keyed by Provider ID. */
|
|
21
29
|
pricingPolicies?: Record<string, ProviderPricingPolicy>;
|
|
22
30
|
}
|
|
23
31
|
|
|
24
|
-
export type
|
|
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,42 +102,57 @@ const defaultDependencies: ProviderKitDependencies = {
|
|
|
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
|
-
export function
|
|
46
|
-
if (typeof runtime.fetch !== "function") throw new Error("Provider
|
|
47
|
-
if (typeof runtime.now !== "function") throw new Error("Provider
|
|
123
|
+
export function validatePiProviderDependencies(runtime: PiProviderDependencies): void {
|
|
124
|
+
if (typeof runtime.fetch !== "function") throw new Error("Pi Provider fetch must be a function");
|
|
125
|
+
if (typeof runtime.now !== "function") throw new Error("Pi Provider now must be a function");
|
|
48
126
|
for (const [name, value] of [
|
|
49
127
|
["modelDiscoveryTimeoutMs", runtime.modelDiscoveryTimeoutMs],
|
|
50
128
|
["statusRequestTimeoutMs", runtime.statusRequestTimeoutMs],
|
|
51
129
|
["liveCheckRequestTimeoutMs", runtime.liveCheckRequestTimeoutMs],
|
|
52
130
|
["officialPricingTimeoutMs", runtime.officialPricingTimeoutMs],
|
|
53
131
|
] as const) {
|
|
54
|
-
if (!isValidTimeoutMs(value)) throw new Error(`Provider
|
|
132
|
+
if (!isValidTimeoutMs(value)) throw new Error(`Pi Provider ${name} must be a valid timeout`);
|
|
55
133
|
}
|
|
56
134
|
for (const [name, value] of [
|
|
57
135
|
["officialPricingCacheTtlMs", runtime.officialPricingCacheTtlMs],
|
|
58
136
|
["officialPricingMaxStaleMs", runtime.officialPricingMaxStaleMs],
|
|
59
137
|
] as const) {
|
|
60
138
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
61
|
-
throw new Error(`Provider
|
|
139
|
+
throw new Error(`Pi Provider ${name} must be a finite non-negative number`);
|
|
62
140
|
}
|
|
63
141
|
}
|
|
64
142
|
if (typeof runtime.officialPricingUrl !== "string" || runtime.officialPricingUrl.trim() === "") {
|
|
65
|
-
throw new Error("Provider
|
|
143
|
+
throw new Error("Pi Provider officialPricingUrl must be a non-empty string");
|
|
144
|
+
}
|
|
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");
|
|
66
150
|
}
|
|
67
|
-
if (typeof runtime.
|
|
68
|
-
throw new Error("Provider
|
|
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
|
-
throw new Error("Provider
|
|
155
|
+
throw new Error("Pi Provider enableOfficialPricingFallback must be a boolean");
|
|
72
156
|
}
|
|
73
157
|
if (runtime.pricingPolicies !== undefined) {
|
|
74
158
|
if (
|
|
@@ -76,20 +160,20 @@ export function validateProviderKitDependencies(runtime: ProviderKitDependencies
|
|
|
76
160
|
typeof runtime.pricingPolicies !== "object" ||
|
|
77
161
|
Array.isArray(runtime.pricingPolicies)
|
|
78
162
|
) {
|
|
79
|
-
throw new Error("Provider
|
|
163
|
+
throw new Error("Pi Provider pricingPolicies must be an object");
|
|
80
164
|
}
|
|
81
165
|
for (const [providerId, policy] of Object.entries(runtime.pricingPolicies)) {
|
|
82
|
-
if (providerId.trim() === "") throw new Error("Provider
|
|
83
|
-
validatePricingPolicy(policy, `Provider
|
|
166
|
+
if (providerId.trim() === "") throw new Error("Pi Provider pricingPolicies has an empty Provider ID");
|
|
167
|
+
validatePricingPolicy(policy, `Pi Provider pricingPolicies.${providerId}`);
|
|
84
168
|
}
|
|
85
169
|
}
|
|
86
170
|
}
|
|
87
171
|
|
|
88
|
-
export function
|
|
89
|
-
dependencies: Partial<
|
|
90
|
-
):
|
|
91
|
-
const runtime = { ...
|
|
172
|
+
export function resolvePiProviderDependencies(
|
|
173
|
+
dependencies: Partial<PiProviderDependencies> = {},
|
|
174
|
+
): PiProviderDependencies {
|
|
175
|
+
const runtime = { ...getDefaultPiProviderDependencies(), ...dependencies };
|
|
92
176
|
if (runtime.pricingPolicies === undefined) runtime.pricingPolicies = {};
|
|
93
|
-
|
|
177
|
+
validatePiProviderDependencies(runtime);
|
|
94
178
|
return runtime;
|
|
95
179
|
}
|
|
@@ -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,8 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import type { ProviderKitDefinition } from "./definition.ts";
|
|
5
|
-
import { validateProviderKitDefinition } from "./definition.ts";
|
|
2
|
+
import type { PiProviderDefinition } from "./definition.ts";
|
|
3
|
+
import { validatePiProviderDefinition } from "./definition.ts";
|
|
6
4
|
import { LiveCheckManager, type LiveCheckResult } from "./live-check-manager.ts";
|
|
7
5
|
import {
|
|
8
6
|
fetchOfficialPricing,
|
|
@@ -14,8 +12,8 @@ import {
|
|
|
14
12
|
import type { PreflightContextLike } from "./preflight-manager.ts";
|
|
15
13
|
import { PreflightManager } from "./preflight-manager.ts";
|
|
16
14
|
import { refreshProviderRegistrations, registerProviderAdapter } from "./provider-registration.ts";
|
|
17
|
-
import type {
|
|
18
|
-
import {
|
|
15
|
+
import type { PiProviderDependencies, PiProviderLoader } from "./runtime-config.ts";
|
|
16
|
+
import { resolvePiProviderDependencies } from "./runtime-config.ts";
|
|
19
17
|
import type { StatusContextLike } from "./status-manager.ts";
|
|
20
18
|
import { StatusManager } from "./status-manager.ts";
|
|
21
19
|
import {
|
|
@@ -33,15 +31,19 @@ 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"]>;
|
|
39
38
|
type StatusNotificationContext = Pick<ExtensionContext, "modelRegistry" | "ui"> & {
|
|
40
39
|
mode?: ExtensionContext["mode"];
|
|
41
40
|
};
|
|
42
|
-
const STATUS_WIDGET_KEY = "provider-
|
|
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
|
if (credential?.type !== "oauth") return undefined;
|
|
@@ -59,7 +61,11 @@ function clearTransientStatus(ctx: Pick<ExtensionContext, "ui">): void {
|
|
|
59
61
|
ctx.ui.setWidget(STATUS_WIDGET_KEY, undefined);
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
function showTransientStatus(
|
|
64
|
+
function showTransientStatus(
|
|
65
|
+
message: string,
|
|
66
|
+
ctx: StatusNotificationContext,
|
|
67
|
+
wrapTextWithAnsi: (text: string, width: number) => string[],
|
|
68
|
+
): boolean {
|
|
63
69
|
if ((ctx.mode !== "tui" && ctx.mode !== "rpc") || typeof ctx.ui.setWidget !== "function") return false;
|
|
64
70
|
// RPC cannot render component factories, so keep its plain text protocol unchanged.
|
|
65
71
|
if (ctx.mode === "rpc") {
|
|
@@ -103,7 +109,7 @@ export function scheduleModelCatalogRefresh(ctx: Pick<ExtensionContext, "modelRe
|
|
|
103
109
|
.catch(() => undefined);
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
export interface
|
|
112
|
+
export interface PiProviderRuntimeController {
|
|
107
113
|
resetForSession(): void;
|
|
108
114
|
updateOfficialPricing?(snapshot: Record<string, OfficialModelMeta>): void;
|
|
109
115
|
shutdown(): void;
|
|
@@ -126,7 +132,7 @@ function cloneProviderCost(cost: ProviderCost): ProviderCost {
|
|
|
126
132
|
|
|
127
133
|
function getOfficialMetadataStatus(
|
|
128
134
|
snapshot: Record<string, OfficialModelMeta>,
|
|
129
|
-
runtime:
|
|
135
|
+
runtime: PiProviderDependencies,
|
|
130
136
|
): ModelMetadataStatus | undefined {
|
|
131
137
|
const source = runtime.officialPricingUrl === OPENROUTER_MODELS_URL ? "AA/OpenRouter" : "Official metadata";
|
|
132
138
|
if (!runtime.enableOfficialPricingFallback && Object.keys(snapshot).length === 0) return undefined;
|
|
@@ -185,17 +191,17 @@ function getNativeModelMetadata(
|
|
|
185
191
|
};
|
|
186
192
|
}
|
|
187
193
|
|
|
188
|
-
export function
|
|
194
|
+
export function installPiProviderRuntime(
|
|
189
195
|
pi: ExtensionAPI,
|
|
190
|
-
runtime:
|
|
191
|
-
definition:
|
|
196
|
+
runtime: PiProviderDependencies,
|
|
197
|
+
definition: PiProviderDefinition,
|
|
192
198
|
officialPricing: Record<string, OfficialModelMeta> = {},
|
|
193
199
|
options: {
|
|
194
200
|
registerHandlers?: boolean;
|
|
195
201
|
providerDrafts?: ReadonlyMap<ProviderAdapter, ProviderModelDraft[]>;
|
|
196
202
|
} = {},
|
|
197
|
-
):
|
|
198
|
-
|
|
203
|
+
): PiProviderRuntimeController {
|
|
204
|
+
validatePiProviderDefinition(definition);
|
|
199
205
|
const registerHandlers = options.registerHandlers ?? true;
|
|
200
206
|
let currentOfficialPricing = officialPricing;
|
|
201
207
|
let currentOfficialMetadataStatus = getOfficialMetadataStatus(officialPricing, runtime);
|
|
@@ -301,7 +307,7 @@ export function installProviderKitRuntime(
|
|
|
301
307
|
);
|
|
302
308
|
const message = report.report;
|
|
303
309
|
if (report.warningLevel !== "hard") {
|
|
304
|
-
statusPresentationVisible = showTransientStatus(message, ctx);
|
|
310
|
+
statusPresentationVisible = showTransientStatus(message, ctx, runtime.wrapTextWithAnsi);
|
|
305
311
|
if (!statusPresentationVisible) ctx.ui.notify(message, "info");
|
|
306
312
|
return;
|
|
307
313
|
}
|
|
@@ -335,7 +341,7 @@ export function installProviderKitRuntime(
|
|
|
335
341
|
model,
|
|
336
342
|
modelRegistry: ctx.modelRegistry,
|
|
337
343
|
getCredentialKey: () => ctx.modelRegistry.getApiKeyForProvider(model.provider),
|
|
338
|
-
getCredentialMetadata: () => readProviderCredentialMetadata(model.provider),
|
|
344
|
+
getCredentialMetadata: () => readProviderCredentialMetadata(model.provider, runtime.readStoredCredential),
|
|
339
345
|
};
|
|
340
346
|
const preflightContext: PreflightContextLike = { model, modelRegistry: ctx.modelRegistry };
|
|
341
347
|
const refreshChecks: Array<Promise<unknown>> = [];
|
|
@@ -387,7 +393,7 @@ export function installProviderKitRuntime(
|
|
|
387
393
|
liveCheckManager.clear();
|
|
388
394
|
};
|
|
389
395
|
|
|
390
|
-
const controller:
|
|
396
|
+
const controller: PiProviderRuntimeController = {
|
|
391
397
|
resetForSession,
|
|
392
398
|
updateOfficialPricing(snapshot) {
|
|
393
399
|
currentOfficialPricing = snapshot;
|
|
@@ -425,15 +431,15 @@ export function installProviderKitRuntime(
|
|
|
425
431
|
return controller;
|
|
426
432
|
}
|
|
427
433
|
|
|
428
|
-
export function
|
|
429
|
-
loadDefinition:
|
|
430
|
-
dependencies: Partial<
|
|
434
|
+
export function createPiProviderRuntime(
|
|
435
|
+
loadDefinition: PiProviderLoader,
|
|
436
|
+
dependencies: Partial<PiProviderDependencies> = {},
|
|
431
437
|
): (pi: ExtensionAPI) => Promise<void> {
|
|
432
|
-
const runtime =
|
|
438
|
+
const runtime = resolvePiProviderDependencies(dependencies);
|
|
433
439
|
return async (pi) => {
|
|
434
440
|
let latestBackgroundPricing: Record<string, OfficialModelMeta> | undefined;
|
|
435
|
-
let installedDefinition:
|
|
436
|
-
let installedController:
|
|
441
|
+
let installedDefinition: PiProviderDefinition | undefined;
|
|
442
|
+
let installedController: PiProviderRuntimeController | undefined;
|
|
437
443
|
let disposed = false;
|
|
438
444
|
const onBackgroundRefresh = (snapshot: Record<string, OfficialModelMeta>): void => {
|
|
439
445
|
latestBackgroundPricing = snapshot;
|
|
@@ -462,8 +468,8 @@ export function createProviderKitRuntime(
|
|
|
462
468
|
: Promise.resolve({});
|
|
463
469
|
const definitionPromise = loadDefinition(runtime);
|
|
464
470
|
const [officialPricing, definition] = await Promise.all([officialPricingPromise, definitionPromise]);
|
|
465
|
-
|
|
466
|
-
installedController =
|
|
471
|
+
validatePiProviderDefinition(definition);
|
|
472
|
+
installedController = installPiProviderRuntime(pi, runtime, definition, officialPricing);
|
|
467
473
|
installedDefinition = definition;
|
|
468
474
|
if (latestBackgroundPricing !== undefined) onBackgroundRefresh(latestBackgroundPricing);
|
|
469
475
|
pi.on("session_shutdown", () => {
|
package/core/status-report.ts
CHANGED
|
@@ -264,7 +264,7 @@ function formatCatalog(
|
|
|
264
264
|
now: number,
|
|
265
265
|
): { lines: string[]; issue: ReportIssue } {
|
|
266
266
|
if (!adapter) {
|
|
267
|
-
if (!nativeLookupAvailable) return { lines: ["Status: not managed by Provider
|
|
267
|
+
if (!nativeLookupAvailable) return { lines: ["Status: not managed by Pi Provider"], issue: { level: "none" } };
|
|
268
268
|
if (!nativeProvider) return { lines: ["Status: unavailable in Pi"], issue: { level: "none" } };
|
|
269
269
|
const count = getNativeModelCount(nativeProvider);
|
|
270
270
|
return {
|
|
@@ -482,7 +482,7 @@ function appendLiveCheckReport(
|
|
|
482
482
|
return { level: "none" };
|
|
483
483
|
}
|
|
484
484
|
if (options.showScope) {
|
|
485
|
-
lines.push("Live check scope: streamSimple() · Provider
|
|
485
|
+
lines.push("Live check scope: streamSimple() · Pi Provider tuners only (other hooks not replayed)");
|
|
486
486
|
}
|
|
487
487
|
if (diagnostics?.pending) lines.push("Availability: checking");
|
|
488
488
|
else if (!diagnostics?.snapshot && !diagnostics?.lastError) lines.push("Availability: not checked");
|
package/core/types.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type ThinkingLevel = ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
|
10
10
|
export type ProviderCost = ProviderModelConfig["cost"];
|
|
11
11
|
export type ProviderModel = ProviderModelConfig;
|
|
12
12
|
export type PricingSku = "input" | "output" | "cacheRead" | "cacheWrite";
|
|
13
|
-
/** Pricing provenance used by Provider
|
|
13
|
+
/** Pricing provenance used by Pi Provider sidecars; not added to Pi model objects. */
|
|
14
14
|
export type ProviderPricingSource = "provider" | "fallback" | "official";
|
|
15
15
|
export type ModelPricingSource = ProviderPricingSource | "native";
|
|
16
16
|
export type ModelFieldSource = ProviderPricingSource | "native" | "default";
|
|
@@ -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;
|
package/index.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { getAgentDir, readStoredCredential } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
4
|
+
import { createJiti } from "jiti";
|
|
5
|
+
import type { PiProviderDependencies } from "./core/runtime-config.ts";
|
|
2
6
|
|
|
3
7
|
export type {
|
|
4
8
|
AdapterExtensionContext,
|
|
@@ -13,25 +17,26 @@ export {
|
|
|
13
17
|
defineStatusExtension,
|
|
14
18
|
defineTunerExtension,
|
|
15
19
|
} from "./core/adapter-extensions.ts";
|
|
20
|
+
export { withDeadline } from "./core/deadline.ts";
|
|
16
21
|
export type { ProviderDataErrorLike } from "./core/errors.ts";
|
|
17
22
|
export { isProviderDataError, ProviderDataError } from "./core/errors.ts";
|
|
18
23
|
export type {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
PiProviderDefinition,
|
|
25
|
+
PiProviderDependencies,
|
|
26
|
+
PiProviderLoader,
|
|
27
|
+
PiProviderRuntimeController,
|
|
23
28
|
} from "./core/extension.ts";
|
|
24
29
|
export {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
createPiProviderRuntime,
|
|
31
|
+
getDefaultPiProviderDependencies,
|
|
32
|
+
installPiProviderRuntime,
|
|
28
33
|
prepareProviderRegistration,
|
|
29
34
|
registerProviderAdapter,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
resolvePiProviderDependencies,
|
|
36
|
+
validatePiProviderDefinition,
|
|
37
|
+
validatePiProviderDependencies,
|
|
33
38
|
} from "./core/extension.ts";
|
|
34
|
-
export {
|
|
39
|
+
export { createPiProviderHost } from "./core/host.ts";
|
|
35
40
|
export type {
|
|
36
41
|
LiveCheckContextLike,
|
|
37
42
|
LiveCheckDiagnostics,
|
|
@@ -56,6 +61,7 @@ export {
|
|
|
56
61
|
parseOpenRouterPricing,
|
|
57
62
|
setPricingCache,
|
|
58
63
|
} from "./core/official-pricing.ts";
|
|
64
|
+
export { createOpenCodeCatalogPreflightAdapter } from "./core/opencode-preflight.ts";
|
|
59
65
|
export type {
|
|
60
66
|
PreflightAdapter,
|
|
61
67
|
PreflightContext,
|
|
@@ -67,6 +73,8 @@ export type {
|
|
|
67
73
|
} from "./core/preflight-manager.ts";
|
|
68
74
|
export { getPreflightKey, normalizePreflightSnapshot, PreflightManager } from "./core/preflight-manager.ts";
|
|
69
75
|
export { applyPricingAdjustment, resolvePricingDetails } from "./core/pricing-adjustments.ts";
|
|
76
|
+
export { normalizeProviderModels } from "./core/provider-registration.ts";
|
|
77
|
+
export { parseRetryAfter } from "./core/retry-after.ts";
|
|
70
78
|
export type { StatusDiagnostics, StatusErrorState } from "./core/status-manager.ts";
|
|
71
79
|
export { normalizeStatusSnapshot, StatusManager } from "./core/status-manager.ts";
|
|
72
80
|
export { applyTunerAdapters, sortTunerAdapters } from "./core/tuner-manager.ts";
|
|
@@ -105,4 +113,39 @@ export type {
|
|
|
105
113
|
TunerContext,
|
|
106
114
|
} from "./core/types.ts";
|
|
107
115
|
|
|
108
|
-
export
|
|
116
|
+
export interface PiProviderExtensionOptions {
|
|
117
|
+
/** User adapter root; replaces the default `<agentDir>/pi-provider` directory. */
|
|
118
|
+
adapterRoot?: string;
|
|
119
|
+
/** Host runtime dependency overrides. */
|
|
120
|
+
dependencies?: Partial<PiProviderDependencies>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Create one Pi extension that discovers the current Adapter files when it loads. */
|
|
124
|
+
export function createPiProviderExtension(
|
|
125
|
+
options: PiProviderExtensionOptions = {},
|
|
126
|
+
): (pi: ExtensionAPI) => Promise<void> {
|
|
127
|
+
return async (pi) => {
|
|
128
|
+
const jiti = createJiti(import.meta.url, { moduleCache: true, tryNative: false });
|
|
129
|
+
const { runPiProviderEntry } = (await jiti.import("./core/runtime-entry.ts")) as {
|
|
130
|
+
runPiProviderEntry: (
|
|
131
|
+
pi: ExtensionAPI,
|
|
132
|
+
entry: {
|
|
133
|
+
agentDir: string;
|
|
134
|
+
readStoredCredential: typeof readStoredCredential;
|
|
135
|
+
wrapTextWithAnsi: typeof wrapTextWithAnsi;
|
|
136
|
+
adapterRoot?: string;
|
|
137
|
+
dependencies?: Partial<PiProviderDependencies>;
|
|
138
|
+
},
|
|
139
|
+
) => Promise<void>;
|
|
140
|
+
};
|
|
141
|
+
await runPiProviderEntry(pi, {
|
|
142
|
+
agentDir: getAgentDir(),
|
|
143
|
+
readStoredCredential,
|
|
144
|
+
wrapTextWithAnsi,
|
|
145
|
+
adapterRoot: options.adapterRoot,
|
|
146
|
+
dependencies: options.dependencies,
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export default createPiProviderExtension();
|