@hyav/pi-provider 0.1.1 → 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/CHANGELOG.md +8 -1
- package/README.md +5 -5
- package/README.zh-CN.md +5 -5
- package/SECURITY.md +1 -1
- package/core/adapter-extensions.ts +14 -14
- package/core/adapter-loader.ts +53 -0
- package/core/adapter-protocol.ts +11 -11
- package/core/definition.ts +8 -8
- package/core/extension.ts +9 -9
- package/core/host.ts +26 -26
- package/core/official-pricing.ts +2 -5
- package/core/provider-registration.ts +5 -5
- package/core/runtime-config.ts +21 -21
- package/core/runtime.ts +21 -21
- package/core/status-report.ts +2 -2
- package/core/types.ts +1 -1
- package/index.ts +34 -13
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
This file is the authoritative user-facing release history for `@hyav/pi-provider`.
|
|
4
4
|
|
|
5
|
+
## 0.1.2 - 2026-08-17
|
|
6
|
+
|
|
7
|
+
- Expose one Pi package entrypoint while preserving file-level Adapter discovery when `/reload` runs.
|
|
8
|
+
- Isolate invalid Adapter modules behind capability-relative diagnostics and declare `jiti` as the runtime loader.
|
|
9
|
+
- Rename the public APIs to Pi Provider equivalents and remove legacy aliases; programmatic consumers must update those imported names.
|
|
10
|
+
- Store generated metadata under `<agent-dir>/pi-provider/`, following Pi's resolved agent directory.
|
|
11
|
+
|
|
5
12
|
## 0.1.1 - 2026-08-16
|
|
6
13
|
|
|
7
14
|
- Hardened zero-coupling contract tests and isolated temporary directories for manifest drop-in validation.
|
|
@@ -10,7 +17,7 @@ This file is the authoritative user-facing release history for `@hyav/pi-provide
|
|
|
10
17
|
## 0.1.0 - 2026-08-16
|
|
11
18
|
|
|
12
19
|
- Initial public release of `@hyav/pi-provider`.
|
|
13
|
-
- Provider
|
|
20
|
+
- Pi Provider host for dynamic providers, status, preflight, live checks, and request tuners.
|
|
14
21
|
- Manifest-discovered adapter extensions with deterministic ordering, reload isolation, validation, and conflict handling.
|
|
15
22
|
- Cached model catalogs, bounded background refresh, pricing metadata, and explicit free-versus-billable diagnostics.
|
|
16
23
|
- Built-in Charm Hyper, DeepSeek, Google Gemini, OpenAI Codex, OpenCode Zen, and OpenCode Go integrations.
|
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@ A provider extension toolkit for [Pi](https://pi.dev). It registers LLM provider
|
|
|
8
8
|
|
|
9
9
|
## Highlights
|
|
10
10
|
|
|
11
|
-
- One Provider
|
|
12
|
-
-
|
|
11
|
+
- One Pi Provider Host for registration, status, preflight checks, live checks, and request tuners
|
|
12
|
+
- Provider, status, preflight, and tuner Adapter files discovered by one Pi entrypoint on `/reload`
|
|
13
13
|
- Resilient model catalogs with cached fallback, bounded background refresh, and failure retention
|
|
14
14
|
- Provider-first pricing metadata with optional OpenRouter completion and quality indicators
|
|
15
15
|
- Explicit diagnostics: cached `/status`, free `/status refresh`, and potentially billable `/status check`
|
|
@@ -46,11 +46,11 @@ Use `/status refresh` for free endpoint, authentication, catalog, and account ch
|
|
|
46
46
|
| Name | Required | Default | Effect |
|
|
47
47
|
|---|---:|---|---|
|
|
48
48
|
| `HYPER_API_KEY` | For Charm Hyper API-key auth | None | Supplies the built-in `charm-hyper` provider credential; OAuth users may use `/login` |
|
|
49
|
-
| `PI_CODING_AGENT_DIR` | No | `~/.pi/agent` | Changes
|
|
49
|
+
| `PI_CODING_AGENT_DIR` | No | `~/.pi/agent` | Changes Pi's agent directory; public OpenRouter metadata is cached under `<agent-dir>/pi-provider/` |
|
|
50
50
|
|
|
51
|
-
Programmatic integrations can configure pricing fallback, pricing policies, request timeouts, metadata URLs, and cache paths through `
|
|
51
|
+
Programmatic integrations can configure pricing fallback, pricing policies, request timeouts, metadata URLs, and cache paths through `createPiProviderRuntime()` or `createPiProviderHost()`. Host packages with a custom capability root can call `createPiProviderExtension({ adapterRoot, dependencies })`. The source definition [`PiProviderDependencies`](core/runtime-config.ts) is authoritative.
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
Add or remove Adapter files under `@hyav/pi-provider`'s `providers/`, `status/`, `preflight/`, or `tuners/` directories, then run `/reload` to rediscover them without changing `index.ts`. Standalone trusted Adapter packages still declare their capability entries in their own Pi manifest. See the [adapter extension contract](https://github.com/hyav/pi-provider/blob/main/docs/adapter-extensions.md) for helpers, validation, conflicts, reload behavior, and lifecycle boundaries. The root [`index.ts`](index.ts) defines the public TypeScript exports.
|
|
54
54
|
|
|
55
55
|
## Before you use it
|
|
56
56
|
|
package/README.zh-CN.md
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
## 核心能力
|
|
10
10
|
|
|
11
|
-
- 由一个 Provider
|
|
12
|
-
-
|
|
11
|
+
- 由一个 Pi Provider Host 统一负责注册、Status、Preflight、实时检查和请求 Tuner
|
|
12
|
+
- 由单一 Pi 入口在 `/reload` 时发现 Provider、Status、Preflight 和 Tuner Adapter 文件
|
|
13
13
|
- 通过缓存回退、有界后台刷新和失败保留提供可靠的模型目录
|
|
14
14
|
- 优先采用 Provider 价格元数据,并可由 OpenRouter 补全价格和质量指标
|
|
15
15
|
- 显式诊断:缓存 `/status`、免费 `/status refresh` 和可能计费的 `/status check`
|
|
@@ -46,11 +46,11 @@ pi install npm:@hyav/pi-provider
|
|
|
46
46
|
| 名称 | 必需 | 默认值 | 作用 |
|
|
47
47
|
|---|---:|---|---|
|
|
48
48
|
| `HYPER_API_KEY` | Charm Hyper API Key 鉴权需要 | 无 | 为内置 `charm-hyper` Provider 提供凭据;OAuth 用户可以使用 `/login` |
|
|
49
|
-
| `PI_CODING_AGENT_DIR` | 否 | `~/.pi/agent` |
|
|
49
|
+
| `PI_CODING_AGENT_DIR` | 否 | `~/.pi/agent` | 修改 Pi agent 目录;公开 OpenRouter 元数据缓存在 `<agent-dir>/pi-provider/` 下 |
|
|
50
50
|
|
|
51
|
-
程序化集成可以通过 `
|
|
51
|
+
程序化集成可以通过 `createPiProviderRuntime()` 或 `createPiProviderHost()` 配置价格回退、价格策略、请求超时、元数据 URL 和缓存路径;使用自定义 capability 根目录的 Host 包可以调用 `createPiProviderExtension({ adapterRoot, dependencies })`。源码定义 [`PiProviderDependencies`](core/runtime-config.ts) 是权威依据。
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
在 `@hyav/pi-provider` 的 `providers/`、`status/`、`preflight/` 或 `tuners/` 目录增删 Adapter 文件后,执行 `/reload` 即可重新发现,无需修改 `index.ts`。独立的可信 Adapter 包仍通过自身 Pi manifest 声明 capability 入口。Helper、校验、冲突、reload 行为和生命周期边界见 [Adapter Extension 契约](https://github.com/hyav/pi-provider/blob/main/docs/adapter-extensions.zh-CN.md)。根目录 [`index.ts`](index.ts) 定义公开 TypeScript 导出。
|
|
54
54
|
|
|
55
55
|
## 使用须知
|
|
56
56
|
|
package/SECURITY.md
CHANGED
|
@@ -30,7 +30,7 @@ Reports are handled on a best-effort basis; no acknowledgement, remediation, or
|
|
|
30
30
|
|
|
31
31
|
## Scope and dependency boundary
|
|
32
32
|
|
|
33
|
-
This policy covers the source repository, the published `@hyav/pi-provider` npm artifact, its Pi manifest, built-in adapters, credential handling, remote response validation, deadlines, and the Provider
|
|
33
|
+
This policy covers the source repository, the published `@hyav/pi-provider` npm artifact, its Pi manifest, built-in adapters, credential handling, remote response validation, deadlines, and the Pi Provider runtime. The published artifact has no bundled runtime dependencies; its Pi core imports are host-supplied peers. CI runs both `npm run audit:runtime` for the published boundary and `npm run audit:all` for development and tested-host dependencies. Scanner findings are triaged rather than force-fixed across an untested host version.
|
|
34
34
|
|
|
35
35
|
Vulnerabilities in Pi, a Provider service, npm, GitHub, or another dependency should also be reported to the relevant upstream maintainer. For ordinary defects and usage questions, use [SUPPORT.md](SUPPORT.md) and the [public issue tracker](https://github.com/hyav/pi-provider/issues).
|
|
36
36
|
|
|
@@ -2,21 +2,21 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import {
|
|
3
3
|
type AdapterKind,
|
|
4
4
|
type AdapterRegistrationEnvelope,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
PI_PROVIDER_ADAPTER_EVENT,
|
|
6
|
+
PI_PROVIDER_ADAPTER_PROTOCOL_VERSION,
|
|
7
|
+
PI_PROVIDER_STARTUP_BRIDGE_EVENT,
|
|
8
8
|
type StartupBridge,
|
|
9
9
|
type StartupBridgeRequest,
|
|
10
10
|
} from "./adapter-protocol.ts";
|
|
11
11
|
import { isStableAdapterId, validateAdapter, validateAdapterIdentity } from "./adapter-validation.ts";
|
|
12
12
|
import type { PreflightAdapter } from "./preflight-manager.ts";
|
|
13
13
|
import { registerProviderAdapter } from "./provider-registration.ts";
|
|
14
|
-
import type {
|
|
15
|
-
import {
|
|
14
|
+
import type { PiProviderDependencies } from "./runtime-config.ts";
|
|
15
|
+
import { getDefaultPiProviderDependencies } from "./runtime-config.ts";
|
|
16
16
|
import type { ProviderAdapter, StatusAdapter, TunerAdapter } from "./types.ts";
|
|
17
17
|
|
|
18
18
|
/** Context supplied to an Adapter Extension factory. */
|
|
19
|
-
export interface AdapterExtensionContext extends
|
|
19
|
+
export interface AdapterExtensionContext extends PiProviderDependencies {
|
|
20
20
|
/** The Pi API that owns this extension factory. */
|
|
21
21
|
pi: ExtensionAPI;
|
|
22
22
|
}
|
|
@@ -52,19 +52,19 @@ function validateStaticIdentity(kind: AdapterKind, id: string, providerId?: stri
|
|
|
52
52
|
|
|
53
53
|
function getStartupBridge(pi: ExtensionAPI): StartupBridge {
|
|
54
54
|
const request: StartupBridgeRequest = {};
|
|
55
|
-
pi.events.emit(
|
|
55
|
+
pi.events.emit(PI_PROVIDER_STARTUP_BRIDGE_EVENT, request);
|
|
56
56
|
return (
|
|
57
57
|
request.bridge ?? {
|
|
58
|
-
dependencies:
|
|
58
|
+
dependencies: getDefaultPiProviderDependencies(),
|
|
59
59
|
officialPricing: Promise.resolve({}),
|
|
60
60
|
}
|
|
61
61
|
);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
function emitRegistration(pi: ExtensionAPI, envelope: AdapterRegistrationEnvelope): void {
|
|
65
|
-
pi.events.emit(
|
|
65
|
+
pi.events.emit(PI_PROVIDER_ADAPTER_EVENT, envelope);
|
|
66
66
|
pi.on("session_start", () => {
|
|
67
|
-
pi.events.emit(
|
|
67
|
+
pi.events.emit(PI_PROVIDER_ADAPTER_EVENT, envelope);
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -94,7 +94,7 @@ function createAdapterExtension<TAdapter extends ProviderAdapter | StatusAdapter
|
|
|
94
94
|
// loaded later can recreate the adapter with its configured runtime.
|
|
95
95
|
registerProviderAdapter(pi, providerAdapter, bridge.dependencies, {}, modelDrafts);
|
|
96
96
|
emitRegistration(pi, {
|
|
97
|
-
version:
|
|
97
|
+
version: PI_PROVIDER_ADAPTER_PROTOCOL_VERSION,
|
|
98
98
|
kind: "provider",
|
|
99
99
|
id,
|
|
100
100
|
token,
|
|
@@ -110,7 +110,7 @@ function createAdapterExtension<TAdapter extends ProviderAdapter | StatusAdapter
|
|
|
110
110
|
validateAdapter("status", statusAdapter);
|
|
111
111
|
validateAdapterIdentity("status", id, providerId, statusAdapter);
|
|
112
112
|
emitRegistration(pi, {
|
|
113
|
-
version:
|
|
113
|
+
version: PI_PROVIDER_ADAPTER_PROTOCOL_VERSION,
|
|
114
114
|
kind: "status",
|
|
115
115
|
id,
|
|
116
116
|
providerId: providerId!,
|
|
@@ -126,7 +126,7 @@ function createAdapterExtension<TAdapter extends ProviderAdapter | StatusAdapter
|
|
|
126
126
|
validateAdapter("preflight", preflightAdapter);
|
|
127
127
|
validateAdapterIdentity("preflight", id, providerId, preflightAdapter);
|
|
128
128
|
emitRegistration(pi, {
|
|
129
|
-
version:
|
|
129
|
+
version: PI_PROVIDER_ADAPTER_PROTOCOL_VERSION,
|
|
130
130
|
kind: "preflight",
|
|
131
131
|
id,
|
|
132
132
|
providerId: providerId!,
|
|
@@ -141,7 +141,7 @@ function createAdapterExtension<TAdapter extends ProviderAdapter | StatusAdapter
|
|
|
141
141
|
validateAdapter("tuner", tunerAdapter);
|
|
142
142
|
validateAdapterIdentity("tuner", id, tunerAdapter);
|
|
143
143
|
emitRegistration(pi, {
|
|
144
|
-
version:
|
|
144
|
+
version: PI_PROVIDER_ADAPTER_PROTOCOL_VERSION,
|
|
145
145
|
kind: "tuner",
|
|
146
146
|
id,
|
|
147
147
|
token,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Dirent } from "node:fs";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { dirname, extname, join, relative } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { createJiti } from "jiti";
|
|
7
|
+
|
|
8
|
+
const adapterDirectories = ["providers", "status", "preflight", "tuners"] as const;
|
|
9
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
10
|
+
|
|
11
|
+
type AdapterExtensionFactory = (pi: ExtensionAPI) => void | Promise<void>;
|
|
12
|
+
|
|
13
|
+
function isAdapterFile(name: string): boolean {
|
|
14
|
+
if (name.endsWith(".d.ts")) return false;
|
|
15
|
+
return extname(name) === ".ts" || extname(name) === ".js";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function warnAdapterLoadIssue(path: string, error: unknown): void {
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
console.warn(`[pi-provider] failed to load adapter extension ${JSON.stringify(path)}: ${message}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function discoverAdapterPaths(root: string): Promise<string[]> {
|
|
24
|
+
const paths: string[] = [];
|
|
25
|
+
for (const directory of adapterDirectories) {
|
|
26
|
+
let entries: Dirent[];
|
|
27
|
+
try {
|
|
28
|
+
entries = await readdir(join(root, directory), { withFileTypes: true });
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") continue;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (entry.isFile() && isAdapterFile(entry.name)) paths.push(join(root, directory, entry.name));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return paths.sort();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function loadPackageAdapterExtensions(pi: ExtensionAPI, root: string = packageRoot): Promise<void> {
|
|
41
|
+
const jiti = createJiti(import.meta.url, { moduleCache: false, tryNative: false });
|
|
42
|
+
for (const path of await discoverAdapterPaths(root)) {
|
|
43
|
+
try {
|
|
44
|
+
const factory = (await jiti.import(path, { default: true })) as unknown;
|
|
45
|
+
if (typeof factory !== "function") {
|
|
46
|
+
throw new TypeError("default export must be a Pi extension factory");
|
|
47
|
+
}
|
|
48
|
+
await (factory as AdapterExtensionFactory)(pi);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
warnAdapterLoadIssue(relative(root, path).replaceAll("\\", "/"), error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
package/core/adapter-protocol.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { OfficialModelMeta } from "./official-pricing.ts";
|
|
3
3
|
import type { PreflightAdapter } from "./preflight-manager.ts";
|
|
4
|
-
import type {
|
|
4
|
+
import type { PiProviderDependencies } from "./runtime-config.ts";
|
|
5
5
|
import type { ProviderAdapter, ProviderModelDraft, StatusAdapter, TunerAdapter } from "./types.ts";
|
|
6
6
|
|
|
7
7
|
/** Registration protocol version shared by Host and Adapter Extensions. */
|
|
8
|
-
export const
|
|
8
|
+
export const PI_PROVIDER_ADAPTER_PROTOCOL_VERSION = 2 as const;
|
|
9
9
|
|
|
10
10
|
/** Event-bus channel used for Adapter Extension registration envelopes. */
|
|
11
|
-
export const
|
|
11
|
+
export const PI_PROVIDER_ADAPTER_EVENT = "pi-provider:adapter";
|
|
12
12
|
|
|
13
13
|
/** Event-bus channel used by the Host to expose the factory-stage startup bridge. */
|
|
14
|
-
export const
|
|
14
|
+
export const PI_PROVIDER_STARTUP_BRIDGE_EVENT = "pi-provider:startup-bridge";
|
|
15
15
|
|
|
16
|
-
/** Event-bus channel used to detect more than one Provider
|
|
17
|
-
export const
|
|
16
|
+
/** Event-bus channel used to detect more than one Pi Provider Host. */
|
|
17
|
+
export const PI_PROVIDER_HOST_CLAIM_EVENT = "pi-provider:host-claim";
|
|
18
18
|
|
|
19
19
|
export type AdapterKind = "provider" | "status" | "preflight" | "tuner";
|
|
20
20
|
|
|
21
|
-
export interface AdapterFactoryContext extends
|
|
21
|
+
export interface AdapterFactoryContext extends PiProviderDependencies {
|
|
22
22
|
pi: ExtensionAPI;
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -32,14 +32,14 @@ export type AdapterFactory = (
|
|
|
32
32
|
| Promise<ProviderAdapter | StatusAdapter | PreflightAdapter | TunerAdapter>;
|
|
33
33
|
|
|
34
34
|
export interface AdapterRegistrationEnvelopeBase {
|
|
35
|
-
version: typeof
|
|
35
|
+
version: typeof PI_PROVIDER_ADAPTER_PROTOCOL_VERSION;
|
|
36
36
|
kind: AdapterKind;
|
|
37
37
|
id: string;
|
|
38
38
|
token: object;
|
|
39
39
|
/** Factory retained for Host-side rehydration when Host loaded after Adapter. */
|
|
40
40
|
factory: AdapterFactory;
|
|
41
41
|
/** Exact dependency object used by the initial factory invocation. */
|
|
42
|
-
startupDependencies:
|
|
42
|
+
startupDependencies: PiProviderDependencies;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export interface ProviderAdapterRegistrationEnvelope extends AdapterRegistrationEnvelopeBase {
|
|
@@ -73,7 +73,7 @@ export type AdapterRegistrationEnvelope =
|
|
|
73
73
|
| TunerAdapterRegistrationEnvelope;
|
|
74
74
|
|
|
75
75
|
export interface StartupBridge {
|
|
76
|
-
dependencies:
|
|
76
|
+
dependencies: PiProviderDependencies;
|
|
77
77
|
officialPricing: Promise<Record<string, OfficialModelMeta>>;
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -102,7 +102,7 @@ export function isAdapterRegistrationEnvelope(value: unknown): value is AdapterR
|
|
|
102
102
|
if (value === null || typeof value !== "object") return false;
|
|
103
103
|
const candidate = value as Partial<AdapterRegistrationEnvelope>;
|
|
104
104
|
return (
|
|
105
|
-
candidate.version ===
|
|
105
|
+
candidate.version === PI_PROVIDER_ADAPTER_PROTOCOL_VERSION &&
|
|
106
106
|
(candidate.kind === "provider" ||
|
|
107
107
|
candidate.kind === "status" ||
|
|
108
108
|
candidate.kind === "preflight" ||
|
package/core/definition.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { validateAdapter } from "./adapter-validation.ts";
|
|
|
2
2
|
import type { PreflightAdapter } from "./preflight-manager.ts";
|
|
3
3
|
import type { ProviderAdapter, StatusAdapter, TunerAdapter } from "./types.ts";
|
|
4
4
|
|
|
5
|
-
export interface
|
|
5
|
+
export interface PiProviderDefinition {
|
|
6
6
|
providers: ProviderAdapter[];
|
|
7
7
|
statuses?: StatusAdapter[];
|
|
8
8
|
preflights?: PreflightAdapter[];
|
|
@@ -24,15 +24,15 @@ function rejectDuplicate(ids: Set<string>, id: string, label: string): void {
|
|
|
24
24
|
* Dynamic Hosts use the same adapter validators while resolving conflicts, but
|
|
25
25
|
* keep invalid entries isolated instead of failing the whole registry.
|
|
26
26
|
*/
|
|
27
|
-
export function
|
|
27
|
+
export function validatePiProviderDefinition(value: unknown): asserts value is PiProviderDefinition {
|
|
28
28
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
29
|
-
throw new Error("Provider
|
|
29
|
+
throw new Error("Pi Provider definition must be an object");
|
|
30
30
|
}
|
|
31
|
-
const definition = value as Partial<
|
|
32
|
-
assertArray(definition.providers, "Provider
|
|
33
|
-
if (definition.statuses !== undefined) assertArray(definition.statuses, "Provider
|
|
34
|
-
if (definition.preflights !== undefined) assertArray(definition.preflights, "Provider
|
|
35
|
-
if (definition.tuners !== undefined) assertArray(definition.tuners, "Provider
|
|
31
|
+
const definition = value as Partial<PiProviderDefinition>;
|
|
32
|
+
assertArray(definition.providers, "Pi Provider definition providers");
|
|
33
|
+
if (definition.statuses !== undefined) assertArray(definition.statuses, "Pi Provider definition statuses");
|
|
34
|
+
if (definition.preflights !== undefined) assertArray(definition.preflights, "Pi Provider definition preflights");
|
|
35
|
+
if (definition.tuners !== undefined) assertArray(definition.tuners, "Pi Provider definition tuners");
|
|
36
36
|
|
|
37
37
|
const providerIds = new Set<string>();
|
|
38
38
|
for (const adapter of definition.providers) {
|
package/core/extension.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
export {
|
|
1
|
+
export type { PiProviderDefinition } from "./definition.ts";
|
|
2
|
+
export { validatePiProviderDefinition } from "./definition.ts";
|
|
3
3
|
export {
|
|
4
4
|
normalizeProviderModel,
|
|
5
5
|
normalizeProviderModels,
|
|
@@ -7,14 +7,14 @@ export {
|
|
|
7
7
|
registerProviderAdapter,
|
|
8
8
|
} from "./provider-registration.ts";
|
|
9
9
|
export {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type
|
|
10
|
+
createPiProviderRuntime,
|
|
11
|
+
installPiProviderRuntime,
|
|
12
|
+
type PiProviderRuntimeController,
|
|
13
13
|
} from "./runtime.ts";
|
|
14
|
-
export type {
|
|
14
|
+
export type { PiProviderDependencies, PiProviderLoader } from "./runtime-config.ts";
|
|
15
15
|
export {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
getDefaultPiProviderDependencies,
|
|
17
|
+
resolvePiProviderDependencies,
|
|
18
|
+
validatePiProviderDependencies,
|
|
19
19
|
} from "./runtime-config.ts";
|
|
20
20
|
export { getStatusModeCompletions } from "./status-report.ts";
|
package/core/host.ts
CHANGED
|
@@ -3,26 +3,26 @@ import {
|
|
|
3
3
|
type AdapterRegistrationEnvelope,
|
|
4
4
|
isAdapterRegistrationEnvelope,
|
|
5
5
|
isHostClaimRequest,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
PI_PROVIDER_ADAPTER_EVENT,
|
|
7
|
+
PI_PROVIDER_HOST_CLAIM_EVENT,
|
|
8
|
+
PI_PROVIDER_STARTUP_BRIDGE_EVENT,
|
|
9
9
|
type StartupBridge,
|
|
10
10
|
type StartupBridgeRequest,
|
|
11
11
|
} from "./adapter-protocol.ts";
|
|
12
12
|
import { validateAdapter, validateAdapterIdentity, validateProviderAdapter } from "./adapter-validation.ts";
|
|
13
|
-
import { type
|
|
13
|
+
import { type PiProviderDefinition, validatePiProviderDefinition } from "./definition.ts";
|
|
14
14
|
import {
|
|
15
15
|
getStatusModeCompletions,
|
|
16
|
-
|
|
17
|
-
type
|
|
16
|
+
installPiProviderRuntime,
|
|
17
|
+
type PiProviderRuntimeController,
|
|
18
18
|
prepareProviderRegistration,
|
|
19
19
|
} from "./extension.ts";
|
|
20
20
|
import { fetchOfficialPricing, type OfficialModelMeta, OPENROUTER_MODELS_URL } from "./official-pricing.ts";
|
|
21
21
|
import type { PreflightAdapter } from "./preflight-manager.ts";
|
|
22
22
|
import { refreshProviderRegistrations } from "./provider-registration.ts";
|
|
23
23
|
import { scheduleModelCatalogRefresh } from "./runtime.ts";
|
|
24
|
-
import type {
|
|
25
|
-
import {
|
|
24
|
+
import type { PiProviderDependencies } from "./runtime-config.ts";
|
|
25
|
+
import { resolvePiProviderDependencies } from "./runtime-config.ts";
|
|
26
26
|
import type { ProviderAdapter, ProviderModelDraft, StatusAdapter, TunerAdapter } from "./types.ts";
|
|
27
27
|
|
|
28
28
|
function compareAdapterIds(left: { id: string }, right: { id: string }): number {
|
|
@@ -34,43 +34,43 @@ function adapterKindLabel(kind: AdapterRegistrationEnvelope["kind"]): string {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function warnAdapterIssue(message: string): void {
|
|
37
|
-
console.warn(`[provider
|
|
37
|
+
console.warn(`[pi-provider] ${message}`);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Create the single Provider
|
|
41
|
+
* Create the single Pi Provider Host used by the published Pi entrypoint.
|
|
42
42
|
*
|
|
43
43
|
* The Host deliberately does not assemble adapters during extension factory
|
|
44
44
|
* loading. Adapter factories can run before or after this factory, so the
|
|
45
45
|
* event-bus envelopes are collected and assembled at the first session-level
|
|
46
46
|
* operation after Pi's session_start registration barrier.
|
|
47
47
|
*/
|
|
48
|
-
export function
|
|
49
|
-
const runtime =
|
|
48
|
+
export function createPiProviderHost(dependencies: Partial<PiProviderDependencies> = {}): (pi: ExtensionAPI) => void {
|
|
49
|
+
const runtime = resolvePiProviderDependencies(dependencies);
|
|
50
50
|
return (pi) => {
|
|
51
51
|
const hostToken = {};
|
|
52
52
|
const hostClaim = { token: hostToken, occupied: false };
|
|
53
|
-
const unsubscribeHostClaim = pi.events.on(
|
|
53
|
+
const unsubscribeHostClaim = pi.events.on(PI_PROVIDER_HOST_CLAIM_EVENT, (value) => {
|
|
54
54
|
if (!isHostClaimRequest(value) || value.token === hostToken) return;
|
|
55
55
|
value.occupied = true;
|
|
56
56
|
});
|
|
57
|
-
pi.events.emit(
|
|
57
|
+
pi.events.emit(PI_PROVIDER_HOST_CLAIM_EVENT, hostClaim);
|
|
58
58
|
if (hostClaim.occupied) {
|
|
59
59
|
unsubscribeHostClaim();
|
|
60
|
-
warnAdapterIssue("ignored a second Provider
|
|
60
|
+
warnAdapterIssue("ignored a second Pi Provider Host; only one Host is supported per Pi runtime");
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const registrations = new Map<object, AdapterRegistrationEnvelope>();
|
|
65
|
-
let active:
|
|
66
|
-
let readyPromise: Promise<
|
|
65
|
+
let active: PiProviderRuntimeController | undefined;
|
|
66
|
+
let readyPromise: Promise<PiProviderRuntimeController | undefined> | undefined;
|
|
67
67
|
let disposed = false;
|
|
68
68
|
let lifecycleGeneration = 0;
|
|
69
69
|
let latestBackgroundPricing: Record<string, OfficialModelMeta> | undefined;
|
|
70
70
|
let installedDefinition:
|
|
71
71
|
| {
|
|
72
72
|
generation: number;
|
|
73
|
-
definition:
|
|
73
|
+
definition: PiProviderDefinition;
|
|
74
74
|
providerDrafts: Map<ProviderAdapter, ProviderModelDraft[]>;
|
|
75
75
|
}
|
|
76
76
|
| undefined;
|
|
@@ -97,7 +97,7 @@ export function createProviderKitHost(dependencies: Partial<ProviderKitDependenc
|
|
|
97
97
|
readyPromise = undefined;
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
const unsubscribeBridge = pi.events.on(
|
|
100
|
+
const unsubscribeBridge = pi.events.on(PI_PROVIDER_STARTUP_BRIDGE_EVENT, (value) => {
|
|
101
101
|
if (value === null || typeof value !== "object") return;
|
|
102
102
|
const request = value as StartupBridgeRequest;
|
|
103
103
|
request.bridge ??= bridge;
|
|
@@ -136,7 +136,7 @@ export function createProviderKitHost(dependencies: Partial<ProviderKitDependenc
|
|
|
136
136
|
if (previous === undefined) invalidateRuntime();
|
|
137
137
|
};
|
|
138
138
|
|
|
139
|
-
const unsubscribeRegistrations = pi.events.on(
|
|
139
|
+
const unsubscribeRegistrations = pi.events.on(PI_PROVIDER_ADAPTER_EVENT, receiveRegistration);
|
|
140
140
|
|
|
141
141
|
const groupedWithoutConflicts = <T>(items: T[], key: (item: T) => string, label: string): T[] => {
|
|
142
142
|
const groups = new Map<string, T[]>();
|
|
@@ -173,7 +173,7 @@ export function createProviderKitHost(dependencies: Partial<ProviderKitDependenc
|
|
|
173
173
|
const buildDefinition = async (
|
|
174
174
|
ctx?: ExtensionContext,
|
|
175
175
|
): Promise<{
|
|
176
|
-
definition:
|
|
176
|
+
definition: PiProviderDefinition;
|
|
177
177
|
providerDrafts: Map<ProviderAdapter, ProviderModelDraft[]>;
|
|
178
178
|
pricing: Record<string, OfficialModelMeta>;
|
|
179
179
|
}> => {
|
|
@@ -358,15 +358,15 @@ export function createProviderKitHost(dependencies: Partial<ProviderKitDependenc
|
|
|
358
358
|
preflights: preflightBindings,
|
|
359
359
|
tuners: tuners.sort(compareAdapterIds),
|
|
360
360
|
};
|
|
361
|
-
|
|
361
|
+
validatePiProviderDefinition(definition);
|
|
362
362
|
return { definition, providerDrafts, pricing };
|
|
363
363
|
};
|
|
364
364
|
|
|
365
|
-
const ensureReady = (ctx?: ExtensionContext): Promise<
|
|
365
|
+
const ensureReady = (ctx?: ExtensionContext): Promise<PiProviderRuntimeController | undefined> => {
|
|
366
366
|
if (active) return Promise.resolve(active);
|
|
367
367
|
if (readyPromise) return readyPromise;
|
|
368
368
|
const generation = lifecycleGeneration;
|
|
369
|
-
const pending = (async (): Promise<
|
|
369
|
+
const pending = (async (): Promise<PiProviderRuntimeController | undefined> => {
|
|
370
370
|
if (disposed || generation !== lifecycleGeneration) return undefined;
|
|
371
371
|
const { definition, providerDrafts, pricing } = await buildDefinition(ctx);
|
|
372
372
|
if (disposed || generation !== lifecycleGeneration) return undefined;
|
|
@@ -382,7 +382,7 @@ export function createProviderKitHost(dependencies: Partial<ProviderKitDependenc
|
|
|
382
382
|
for (const providerId of providerIds) pi.unregisterProvider(providerId);
|
|
383
383
|
}
|
|
384
384
|
if (disposed || generation !== lifecycleGeneration) return undefined;
|
|
385
|
-
const controller =
|
|
385
|
+
const controller = installPiProviderRuntime(pi, runtime, definition, pricing, {
|
|
386
386
|
registerHandlers: false,
|
|
387
387
|
providerDrafts,
|
|
388
388
|
});
|
|
@@ -442,7 +442,7 @@ export function createProviderKitHost(dependencies: Partial<ProviderKitDependenc
|
|
|
442
442
|
}
|
|
443
443
|
|
|
444
444
|
function fetchOfficialPricingForHost(
|
|
445
|
-
runtime:
|
|
445
|
+
runtime: PiProviderDependencies,
|
|
446
446
|
onBackgroundRefresh?: (snapshot: Record<string, OfficialModelMeta>) => void,
|
|
447
447
|
) {
|
|
448
448
|
return fetchOfficialPricing(
|
package/core/official-pricing.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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";
|
|
4
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { withDeadline } from "./deadline.ts";
|
|
6
6
|
import type { ModelQualityScore, ProviderCost, ProviderModel, ProviderModelDraft } from "./types.ts";
|
|
7
7
|
|
|
@@ -69,10 +69,7 @@ const pricingRequests = new Map<string, Promise<Record<string, OfficialModelMeta
|
|
|
69
69
|
|
|
70
70
|
/** Default cache for OpenRouter metadata, not Pi's native model catalog. */
|
|
71
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");
|
|
72
|
+
return join(getAgentDir(), "pi-provider", "openrouter-model-metadata.json");
|
|
76
73
|
}
|
|
77
74
|
|
|
78
75
|
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 {
|
package/core/runtime-config.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { isValidTimeoutMs } from "./deadline.ts";
|
|
2
|
-
import type {
|
|
2
|
+
import type { PiProviderDefinition } from "./definition.ts";
|
|
3
3
|
import { getDefaultOpenRouterMetadataCachePath, OPENROUTER_MODELS_URL } from "./official-pricing.ts";
|
|
4
4
|
import { validatePricingPolicy } from "./pricing-adjustments.ts";
|
|
5
5
|
import type { ProviderPricingPolicy } from "./types.ts";
|
|
6
6
|
|
|
7
|
-
export interface
|
|
7
|
+
export interface PiProviderDependencies {
|
|
8
8
|
fetch: typeof globalThis.fetch;
|
|
9
9
|
now: () => number;
|
|
10
10
|
modelDiscoveryTimeoutMs: number;
|
|
@@ -17,13 +17,13 @@ export interface ProviderKitDependencies {
|
|
|
17
17
|
/** Persistent cache for OpenRouter metadata used by the pricing fallback. */
|
|
18
18
|
openRouterMetadataCachePath: string;
|
|
19
19
|
enableOfficialPricingFallback: boolean;
|
|
20
|
-
/** Optional Provider
|
|
20
|
+
/** Optional Pi Provider-level price policies keyed by Provider ID. */
|
|
21
21
|
pricingPolicies?: Record<string, ProviderPricingPolicy>;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export type
|
|
24
|
+
export type PiProviderLoader = (runtime: PiProviderDependencies) => Promise<PiProviderDefinition>;
|
|
25
25
|
|
|
26
|
-
const defaultDependencies:
|
|
26
|
+
const defaultDependencies: PiProviderDependencies = {
|
|
27
27
|
fetch: globalThis.fetch,
|
|
28
28
|
now: Date.now,
|
|
29
29
|
modelDiscoveryTimeoutMs: 3_000,
|
|
@@ -38,37 +38,37 @@ const defaultDependencies: ProviderKitDependencies = {
|
|
|
38
38
|
pricingPolicies: {},
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
export function
|
|
41
|
+
export function getDefaultPiProviderDependencies(): PiProviderDependencies {
|
|
42
42
|
return { ...defaultDependencies };
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export function
|
|
46
|
-
if (typeof runtime.fetch !== "function") throw new Error("Provider
|
|
47
|
-
if (typeof runtime.now !== "function") throw new Error("Provider
|
|
45
|
+
export function validatePiProviderDependencies(runtime: PiProviderDependencies): void {
|
|
46
|
+
if (typeof runtime.fetch !== "function") throw new Error("Pi Provider fetch must be a function");
|
|
47
|
+
if (typeof runtime.now !== "function") throw new Error("Pi Provider now must be a function");
|
|
48
48
|
for (const [name, value] of [
|
|
49
49
|
["modelDiscoveryTimeoutMs", runtime.modelDiscoveryTimeoutMs],
|
|
50
50
|
["statusRequestTimeoutMs", runtime.statusRequestTimeoutMs],
|
|
51
51
|
["liveCheckRequestTimeoutMs", runtime.liveCheckRequestTimeoutMs],
|
|
52
52
|
["officialPricingTimeoutMs", runtime.officialPricingTimeoutMs],
|
|
53
53
|
] as const) {
|
|
54
|
-
if (!isValidTimeoutMs(value)) throw new Error(`Provider
|
|
54
|
+
if (!isValidTimeoutMs(value)) throw new Error(`Pi Provider ${name} must be a valid timeout`);
|
|
55
55
|
}
|
|
56
56
|
for (const [name, value] of [
|
|
57
57
|
["officialPricingCacheTtlMs", runtime.officialPricingCacheTtlMs],
|
|
58
58
|
["officialPricingMaxStaleMs", runtime.officialPricingMaxStaleMs],
|
|
59
59
|
] as const) {
|
|
60
60
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
61
|
-
throw new Error(`Provider
|
|
61
|
+
throw new Error(`Pi Provider ${name} must be a finite non-negative number`);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
if (typeof runtime.officialPricingUrl !== "string" || runtime.officialPricingUrl.trim() === "") {
|
|
65
|
-
throw new Error("Provider
|
|
65
|
+
throw new Error("Pi Provider officialPricingUrl must be a non-empty string");
|
|
66
66
|
}
|
|
67
67
|
if (typeof runtime.openRouterMetadataCachePath !== "string" || runtime.openRouterMetadataCachePath.trim() === "") {
|
|
68
|
-
throw new Error("Provider
|
|
68
|
+
throw new Error("Pi Provider openRouterMetadataCachePath must be a non-empty path");
|
|
69
69
|
}
|
|
70
70
|
if (typeof runtime.enableOfficialPricingFallback !== "boolean") {
|
|
71
|
-
throw new Error("Provider
|
|
71
|
+
throw new Error("Pi Provider enableOfficialPricingFallback must be a boolean");
|
|
72
72
|
}
|
|
73
73
|
if (runtime.pricingPolicies !== undefined) {
|
|
74
74
|
if (
|
|
@@ -76,20 +76,20 @@ export function validateProviderKitDependencies(runtime: ProviderKitDependencies
|
|
|
76
76
|
typeof runtime.pricingPolicies !== "object" ||
|
|
77
77
|
Array.isArray(runtime.pricingPolicies)
|
|
78
78
|
) {
|
|
79
|
-
throw new Error("Provider
|
|
79
|
+
throw new Error("Pi Provider pricingPolicies must be an object");
|
|
80
80
|
}
|
|
81
81
|
for (const [providerId, policy] of Object.entries(runtime.pricingPolicies)) {
|
|
82
|
-
if (providerId.trim() === "") throw new Error("Provider
|
|
83
|
-
validatePricingPolicy(policy, `Provider
|
|
82
|
+
if (providerId.trim() === "") throw new Error("Pi Provider pricingPolicies has an empty Provider ID");
|
|
83
|
+
validatePricingPolicy(policy, `Pi Provider pricingPolicies.${providerId}`);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
export function
|
|
89
|
-
dependencies: Partial<
|
|
90
|
-
):
|
|
88
|
+
export function resolvePiProviderDependencies(
|
|
89
|
+
dependencies: Partial<PiProviderDependencies> = {},
|
|
90
|
+
): PiProviderDependencies {
|
|
91
91
|
const runtime = { ...defaultDependencies, ...dependencies };
|
|
92
92
|
if (runtime.pricingPolicies === undefined) runtime.pricingPolicies = {};
|
|
93
|
-
|
|
93
|
+
validatePiProviderDependencies(runtime);
|
|
94
94
|
return runtime;
|
|
95
95
|
}
|
package/core/runtime.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { readStoredCredential } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
4
|
-
import type {
|
|
5
|
-
import {
|
|
4
|
+
import type { PiProviderDefinition } from "./definition.ts";
|
|
5
|
+
import { validatePiProviderDefinition } from "./definition.ts";
|
|
6
6
|
import { LiveCheckManager, type LiveCheckResult } from "./live-check-manager.ts";
|
|
7
7
|
import {
|
|
8
8
|
fetchOfficialPricing,
|
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
import type { PreflightContextLike } from "./preflight-manager.ts";
|
|
15
15
|
import { PreflightManager } from "./preflight-manager.ts";
|
|
16
16
|
import { refreshProviderRegistrations, registerProviderAdapter } from "./provider-registration.ts";
|
|
17
|
-
import type {
|
|
18
|
-
import {
|
|
17
|
+
import type { PiProviderDependencies, PiProviderLoader } from "./runtime-config.ts";
|
|
18
|
+
import { resolvePiProviderDependencies } from "./runtime-config.ts";
|
|
19
19
|
import type { StatusContextLike } from "./status-manager.ts";
|
|
20
20
|
import { StatusManager } from "./status-manager.ts";
|
|
21
21
|
import {
|
|
@@ -39,7 +39,7 @@ type ActiveModel = NonNullable<ExtensionContext["model"]>;
|
|
|
39
39
|
type StatusNotificationContext = Pick<ExtensionContext, "modelRegistry" | "ui"> & {
|
|
40
40
|
mode?: ExtensionContext["mode"];
|
|
41
41
|
};
|
|
42
|
-
const STATUS_WIDGET_KEY = "provider-
|
|
42
|
+
const STATUS_WIDGET_KEY = "pi-provider-status";
|
|
43
43
|
|
|
44
44
|
function readProviderCredentialMetadata(provider: string): unknown {
|
|
45
45
|
try {
|
|
@@ -103,7 +103,7 @@ export function scheduleModelCatalogRefresh(ctx: Pick<ExtensionContext, "modelRe
|
|
|
103
103
|
.catch(() => undefined);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
export interface
|
|
106
|
+
export interface PiProviderRuntimeController {
|
|
107
107
|
resetForSession(): void;
|
|
108
108
|
updateOfficialPricing?(snapshot: Record<string, OfficialModelMeta>): void;
|
|
109
109
|
shutdown(): void;
|
|
@@ -126,7 +126,7 @@ function cloneProviderCost(cost: ProviderCost): ProviderCost {
|
|
|
126
126
|
|
|
127
127
|
function getOfficialMetadataStatus(
|
|
128
128
|
snapshot: Record<string, OfficialModelMeta>,
|
|
129
|
-
runtime:
|
|
129
|
+
runtime: PiProviderDependencies,
|
|
130
130
|
): ModelMetadataStatus | undefined {
|
|
131
131
|
const source = runtime.officialPricingUrl === OPENROUTER_MODELS_URL ? "AA/OpenRouter" : "Official metadata";
|
|
132
132
|
if (!runtime.enableOfficialPricingFallback && Object.keys(snapshot).length === 0) return undefined;
|
|
@@ -185,17 +185,17 @@ function getNativeModelMetadata(
|
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
export function
|
|
188
|
+
export function installPiProviderRuntime(
|
|
189
189
|
pi: ExtensionAPI,
|
|
190
|
-
runtime:
|
|
191
|
-
definition:
|
|
190
|
+
runtime: PiProviderDependencies,
|
|
191
|
+
definition: PiProviderDefinition,
|
|
192
192
|
officialPricing: Record<string, OfficialModelMeta> = {},
|
|
193
193
|
options: {
|
|
194
194
|
registerHandlers?: boolean;
|
|
195
195
|
providerDrafts?: ReadonlyMap<ProviderAdapter, ProviderModelDraft[]>;
|
|
196
196
|
} = {},
|
|
197
|
-
):
|
|
198
|
-
|
|
197
|
+
): PiProviderRuntimeController {
|
|
198
|
+
validatePiProviderDefinition(definition);
|
|
199
199
|
const registerHandlers = options.registerHandlers ?? true;
|
|
200
200
|
let currentOfficialPricing = officialPricing;
|
|
201
201
|
let currentOfficialMetadataStatus = getOfficialMetadataStatus(officialPricing, runtime);
|
|
@@ -387,7 +387,7 @@ export function installProviderKitRuntime(
|
|
|
387
387
|
liveCheckManager.clear();
|
|
388
388
|
};
|
|
389
389
|
|
|
390
|
-
const controller:
|
|
390
|
+
const controller: PiProviderRuntimeController = {
|
|
391
391
|
resetForSession,
|
|
392
392
|
updateOfficialPricing(snapshot) {
|
|
393
393
|
currentOfficialPricing = snapshot;
|
|
@@ -425,15 +425,15 @@ export function installProviderKitRuntime(
|
|
|
425
425
|
return controller;
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
-
export function
|
|
429
|
-
loadDefinition:
|
|
430
|
-
dependencies: Partial<
|
|
428
|
+
export function createPiProviderRuntime(
|
|
429
|
+
loadDefinition: PiProviderLoader,
|
|
430
|
+
dependencies: Partial<PiProviderDependencies> = {},
|
|
431
431
|
): (pi: ExtensionAPI) => Promise<void> {
|
|
432
|
-
const runtime =
|
|
432
|
+
const runtime = resolvePiProviderDependencies(dependencies);
|
|
433
433
|
return async (pi) => {
|
|
434
434
|
let latestBackgroundPricing: Record<string, OfficialModelMeta> | undefined;
|
|
435
|
-
let installedDefinition:
|
|
436
|
-
let installedController:
|
|
435
|
+
let installedDefinition: PiProviderDefinition | undefined;
|
|
436
|
+
let installedController: PiProviderRuntimeController | undefined;
|
|
437
437
|
let disposed = false;
|
|
438
438
|
const onBackgroundRefresh = (snapshot: Record<string, OfficialModelMeta>): void => {
|
|
439
439
|
latestBackgroundPricing = snapshot;
|
|
@@ -462,8 +462,8 @@ export function createProviderKitRuntime(
|
|
|
462
462
|
: Promise.resolve({});
|
|
463
463
|
const definitionPromise = loadDefinition(runtime);
|
|
464
464
|
const [officialPricing, definition] = await Promise.all([officialPricingPromise, definitionPromise]);
|
|
465
|
-
|
|
466
|
-
installedController =
|
|
465
|
+
validatePiProviderDefinition(definition);
|
|
466
|
+
installedController = installPiProviderRuntime(pi, runtime, definition, officialPricing);
|
|
467
467
|
installedDefinition = definition;
|
|
468
468
|
if (latestBackgroundPricing !== undefined) onBackgroundRefresh(latestBackgroundPricing);
|
|
469
469
|
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";
|
package/index.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { loadPackageAdapterExtensions } from "./core/adapter-loader.ts";
|
|
3
|
+
import { createPiProviderHost } from "./core/host.ts";
|
|
4
|
+
import type { PiProviderDependencies } from "./core/runtime-config.ts";
|
|
2
5
|
|
|
3
6
|
export type {
|
|
4
7
|
AdapterExtensionContext,
|
|
@@ -16,22 +19,22 @@ export {
|
|
|
16
19
|
export type { ProviderDataErrorLike } from "./core/errors.ts";
|
|
17
20
|
export { isProviderDataError, ProviderDataError } from "./core/errors.ts";
|
|
18
21
|
export type {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
PiProviderDefinition,
|
|
23
|
+
PiProviderDependencies,
|
|
24
|
+
PiProviderLoader,
|
|
25
|
+
PiProviderRuntimeController,
|
|
23
26
|
} from "./core/extension.ts";
|
|
24
27
|
export {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
createPiProviderRuntime,
|
|
29
|
+
getDefaultPiProviderDependencies,
|
|
30
|
+
installPiProviderRuntime,
|
|
28
31
|
prepareProviderRegistration,
|
|
29
32
|
registerProviderAdapter,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
resolvePiProviderDependencies,
|
|
34
|
+
validatePiProviderDefinition,
|
|
35
|
+
validatePiProviderDependencies,
|
|
33
36
|
} from "./core/extension.ts";
|
|
34
|
-
export {
|
|
37
|
+
export { createPiProviderHost } from "./core/host.ts";
|
|
35
38
|
export type {
|
|
36
39
|
LiveCheckContextLike,
|
|
37
40
|
LiveCheckDiagnostics,
|
|
@@ -105,4 +108,22 @@ export type {
|
|
|
105
108
|
TunerContext,
|
|
106
109
|
} from "./core/types.ts";
|
|
107
110
|
|
|
108
|
-
export
|
|
111
|
+
export interface PiProviderExtensionOptions {
|
|
112
|
+
/** Package root containing providers, status, preflight, and tuners directories. */
|
|
113
|
+
adapterRoot?: string;
|
|
114
|
+
/** Host runtime dependency overrides. */
|
|
115
|
+
dependencies?: Partial<PiProviderDependencies>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Create one Pi extension that discovers the current Adapter files when it loads. */
|
|
119
|
+
export function createPiProviderExtension(
|
|
120
|
+
options: PiProviderExtensionOptions = {},
|
|
121
|
+
): (pi: ExtensionAPI) => Promise<void> {
|
|
122
|
+
const piProviderHost = createPiProviderHost(options.dependencies);
|
|
123
|
+
return async (pi) => {
|
|
124
|
+
piProviderHost(pi);
|
|
125
|
+
await loadPackageAdapterExtensions(pi, options.adapterRoot);
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default createPiProviderExtension();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyav/pi-provider",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"pi-package",
|
|
26
26
|
"pi",
|
|
27
27
|
"provider",
|
|
28
|
-
"provider
|
|
28
|
+
"pi-provider",
|
|
29
29
|
"charm-hyper",
|
|
30
30
|
"llm"
|
|
31
31
|
],
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"audit:all": "npm audit --audit-level=high",
|
|
38
38
|
"artifact:check": "node scripts/check-package.mjs",
|
|
39
39
|
"release:check": "node scripts/check-release.mjs",
|
|
40
|
+
"release:notes": "node scripts/write-release-notes.mjs",
|
|
40
41
|
"prepublishOnly": "npm run audit:runtime && npm run audit:all && npm run release:check && npm run check && npm test && npm run artifact:check"
|
|
41
42
|
},
|
|
42
43
|
"engines": {
|
|
@@ -56,11 +57,7 @@
|
|
|
56
57
|
},
|
|
57
58
|
"pi": {
|
|
58
59
|
"extensions": [
|
|
59
|
-
"./index.ts"
|
|
60
|
-
"./providers/*.ts",
|
|
61
|
-
"./status/*.ts",
|
|
62
|
-
"./preflight/*.ts",
|
|
63
|
-
"./tuners/*.ts"
|
|
60
|
+
"./index.ts"
|
|
64
61
|
]
|
|
65
62
|
},
|
|
66
63
|
"files": [
|
|
@@ -77,5 +74,8 @@
|
|
|
77
74
|
"SECURITY.md",
|
|
78
75
|
"SUPPORT.md",
|
|
79
76
|
"LICENSE"
|
|
80
|
-
]
|
|
77
|
+
],
|
|
78
|
+
"dependencies": {
|
|
79
|
+
"jiti": "2.7.0"
|
|
80
|
+
}
|
|
81
81
|
}
|