@hyav/pi-provider 0.1.0-oidc-bootstrap.0

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/CONTRIBUTING.md +63 -0
  3. package/LICENSE +21 -0
  4. package/README.md +61 -0
  5. package/README.zh-CN.md +61 -0
  6. package/SECURITY.md +36 -0
  7. package/SUPPORT.md +25 -0
  8. package/core/adapter-extensions.ts +175 -0
  9. package/core/adapter-protocol.ts +120 -0
  10. package/core/adapter-validation.ts +241 -0
  11. package/core/deadline.ts +78 -0
  12. package/core/definition.ts +64 -0
  13. package/core/errors.ts +38 -0
  14. package/core/extension.ts +20 -0
  15. package/core/host.ts +462 -0
  16. package/core/live-check-manager.ts +263 -0
  17. package/core/official-pricing.ts +881 -0
  18. package/core/opencode-preflight.ts +66 -0
  19. package/core/preflight-manager.ts +251 -0
  20. package/core/pricing-adjustments.ts +118 -0
  21. package/core/provider-registration.ts +261 -0
  22. package/core/retry-after.ts +24 -0
  23. package/core/runtime-config.ts +95 -0
  24. package/core/runtime.ts +473 -0
  25. package/core/status-manager.ts +332 -0
  26. package/core/status-report.ts +592 -0
  27. package/core/tuner-manager.ts +34 -0
  28. package/core/types.ts +175 -0
  29. package/index.ts +108 -0
  30. package/package.json +81 -0
  31. package/preflight/charm-hyper.ts +62 -0
  32. package/preflight/deepseek.ts +73 -0
  33. package/preflight/google.ts +89 -0
  34. package/preflight/openai-codex.ts +88 -0
  35. package/preflight/opencode-go.ts +27 -0
  36. package/preflight/opencode.ts +27 -0
  37. package/providers/charm-hyper/constants.ts +31 -0
  38. package/providers/charm-hyper/oauth.ts +360 -0
  39. package/providers/charm-hyper.ts +536 -0
  40. package/status/charm-hyper.ts +76 -0
  41. package/status/deepseek.ts +102 -0
  42. package/status/openai-codex.ts +224 -0
  43. package/status/opencode-go.ts +133 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ This file is the authoritative user-facing release history for `@hyav/pi-provider`.
4
+
5
+ ## 0.1.0 - 2026-08-16
6
+
7
+ - Initial public release of `@hyav/pi-provider`.
8
+ - Provider Kit host for dynamic providers, status, preflight, live checks, and request tuners.
9
+ - Manifest-discovered adapter extensions with deterministic ordering, reload isolation, validation, and conflict handling.
10
+ - Cached model catalogs, bounded background refresh, pricing metadata, and explicit free-versus-billable diagnostics.
11
+ - Built-in Charm Hyper, DeepSeek, Google Gemini, OpenAI Codex, OpenCode Zen, and OpenCode Go integrations.
@@ -0,0 +1,63 @@
1
+ # Contributing
2
+
3
+ Thank you for helping improve `@hyav/pi-provider`. The canonical user contract is in [README.md](README.md); adapter extension contracts are in [`docs/adapter-extensions.md`](docs/adapter-extensions.md).
4
+
5
+ ## Before you start
6
+
7
+ - Search existing [GitHub issues](https://github.com/hyav/pi-provider/issues) before opening a new one.
8
+ - For suspected vulnerabilities, follow [SECURITY.md](SECURITY.md) instead of using a public issue.
9
+ - Keep changes focused. Do not include credentials, personal data, private fixtures, generated local state, or ignored private overlays.
10
+
11
+ ## Development setup
12
+
13
+ Use Node.js `22.19.0` or later with npm. The tested Pi host baseline is `0.84.1`:
14
+
15
+ ```sh
16
+ npm ci --ignore-scripts
17
+ ```
18
+
19
+ Pi loads the published TypeScript source through its extension loader. Do not add a compiled `dist/` tree unless the package contract is deliberately changed and documented.
20
+
21
+ ## Required checks
22
+
23
+ Run the same deterministic gate used by CI:
24
+
25
+ ```sh
26
+ npm run audit:runtime
27
+ npm run audit:all
28
+ npm run check
29
+ npm test
30
+ npm run artifact:check
31
+ ```
32
+
33
+ - `npm run audit:runtime` checks the published dependency boundary for high-severity advisories.
34
+ - `npm run audit:all` also checks the development and tested-host dependency tree.
35
+ - `npm run check` runs Biome and TypeScript type checking.
36
+ - `npm test` runs Node test runner suites under `test/` against local servers, isolated caches, and deterministic fixtures.
37
+ - `npm run artifact:check` builds a real npm tarball, rejects repository-only files, installs it in a temporary consumer, and loads the published Pi entry points.
38
+
39
+ The ordinary gate must not make real model requests or require billable credentials.
40
+
41
+ ## Changes and review
42
+
43
+ - Public behavior changes must include behavior-focused tests and documentation updates.
44
+ - Keep `README.md` canonical and update `README.zh-CN.md` when user-visible behavior changes.
45
+ - Update [CHANGELOG.md](CHANGELOG.md) for release-relevant behavior, compatibility, security, or migration changes.
46
+ - Preserve the source-package boundary in `package.json.files`; tests, fixtures, scripts, and local caches must not enter the npm artifact.
47
+ - Treat model IDs, pricing metadata, and provider status as security-sensitive input. Keep validation, deadlines, and footer boundaries intact.
48
+
49
+ ## Reporting defects
50
+
51
+ Use a public [GitHub issue](https://github.com/hyav/pi-provider/issues) for reproducible defects and include, when safe:
52
+
53
+ - package version or commit;
54
+ - Node.js, Pi, and Provider versions;
55
+ - Provider and model IDs, without API keys or OAuth tokens;
56
+ - expected and actual behavior;
57
+ - a minimal reproduction and redacted logs.
58
+
59
+ Do not disclose vulnerability details or credentials publicly; follow [SECURITY.md](SECURITY.md).
60
+
61
+ ## License
62
+
63
+ By contributing, you agree that your contribution is provided under the repository's [MIT License](LICENSE).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hyav
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @hyav/pi-provider
2
+
3
+ [简体中文](README.zh-CN.md)
4
+
5
+ A provider extension toolkit for [Pi](https://pi.dev). It registers LLM providers, discovers model catalogs, tunes requests, and reports cached or explicitly refreshed account status while preserving Pi's native footer.
6
+
7
+ [Adapter contract](https://github.com/hyav/pi-provider/blob/main/docs/adapter-extensions.md) · [Support](SUPPORT.md) · [Contributing](CONTRIBUTING.md) · [Changelog](CHANGELOG.md) · [Security](SECURITY.md)
8
+
9
+ ## Highlights
10
+
11
+ - One Provider Kit Host for registration, status, preflight checks, live checks, and request tuners
12
+ - Manifest-discovered provider, status, preflight, and tuner adapters reloaded with `/reload`
13
+ - Resilient model catalogs with cached fallback, bounded background refresh, and failure retention
14
+ - Provider-first pricing metadata with optional OpenRouter completion and quality indicators
15
+ - Explicit diagnostics: cached `/status`, free `/status refresh`, and potentially billable `/status check`
16
+ - Built-in integrations for Charm Hyper, DeepSeek, Google Gemini, OpenAI Codex, OpenCode Zen, and OpenCode Go
17
+
18
+ ## Install
19
+
20
+ Requires Node.js 22.19.0 or newer, Pi, and credentials for the providers you use.
21
+
22
+ ```sh
23
+ pi install npm:@hyav/pi-provider
24
+ ```
25
+
26
+
27
+ ## Quick start
28
+
29
+ 1. Configure credentials in Pi. Charm Hyper accepts `HYPER_API_KEY` or Pi's `/login` OAuth flow.
30
+ 2. Select a model, for example:
31
+
32
+ ```text
33
+ /model charm-hyper/deepseek-v4-pro
34
+ ```
35
+
36
+ 3. Inspect the cached report:
37
+
38
+ ```text
39
+ /status
40
+ ```
41
+
42
+ Use `/status refresh` for free endpoint, authentication, catalog, and account checks. Use `/status check` only when you explicitly accept a real model request and possible usage charges.
43
+
44
+ ## Common configuration
45
+
46
+ | Name | Required | Default | Effect |
47
+ |---|---:|---|---|
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 the base directory for public OpenRouter metadata cache |
50
+
51
+ Programmatic integrations can configure pricing fallback, pricing policies, request timeouts, metadata URLs, and cache paths through `createProviderKitRuntime()` or `createProviderKitHost()`. The source definition [`ProviderKitDependencies`](core/runtime-config.ts) is authoritative.
52
+
53
+ Trusted packages can add adapters through manifest entries under `providers/`, `status/`, `preflight/`, and `tuners/`. 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
+
55
+ ## Before you use it
56
+
57
+ `/status` is offline, `/status refresh` performs free remote checks, and `/status check` sends a live model request that may consume quota. Configured credentials are sent only to the corresponding provider endpoints and are omitted from status output.
58
+
59
+ ## License
60
+
61
+ [MIT](LICENSE)
@@ -0,0 +1,61 @@
1
+ # @hyav/pi-provider
2
+
3
+ [English](README.md)
4
+
5
+ 为 [Pi](https://pi.dev) 提供 Provider 扩展工具包,用于注册 LLM Provider、发现模型目录、调优请求,以及查看缓存或显式刷新的账户状态,同时保留 Pi 原生 Footer。
6
+
7
+ [适配器契约](https://github.com/hyav/pi-provider/blob/main/docs/adapter-extensions.zh-CN.md) · [支持策略](SUPPORT.md) · [参与贡献](CONTRIBUTING.md) · [更新记录](CHANGELOG.md) · [安全策略](SECURITY.md)
8
+
9
+ ## 核心能力
10
+
11
+ - 由一个 Provider Kit Host 统一负责注册、Status、Preflight、实时检查和请求 Tuner
12
+ - 通过 manifest 发现 Provider、Status、Preflight 和 Tuner Adapter,并在 `/reload` 后重新加载
13
+ - 通过缓存回退、有界后台刷新和失败保留提供可靠的模型目录
14
+ - 优先采用 Provider 价格元数据,并可由 OpenRouter 补全价格和质量指标
15
+ - 显式诊断:缓存 `/status`、免费 `/status refresh` 和可能计费的 `/status check`
16
+ - 内置 Charm Hyper、DeepSeek、Google Gemini、OpenAI Codex、OpenCode Zen 和 OpenCode Go 集成
17
+
18
+ ## 安装
19
+
20
+ 需要 Node.js 22.19.0 或更高版本、Pi,以及所用 Provider 的凭据。
21
+
22
+ ```sh
23
+ pi install npm:@hyav/pi-provider
24
+ ```
25
+
26
+
27
+ ## 快速开始
28
+
29
+ 1. 在 Pi 中配置凭据。Charm Hyper 接受 `HYPER_API_KEY` 或 Pi 的 `/login` OAuth 流程。
30
+ 2. 选择模型,例如:
31
+
32
+ ```text
33
+ /model charm-hyper/deepseek-v4-pro
34
+ ```
35
+
36
+ 3. 查看缓存报告:
37
+
38
+ ```text
39
+ /status
40
+ ```
41
+
42
+ 使用 `/status refresh` 执行免费的端点、鉴权、目录和账户检查。只有明确接受一次真实模型请求及其可能产生的用量费用时,才使用 `/status check`。
43
+
44
+ ## 常用配置
45
+
46
+ | 名称 | 必需 | 默认值 | 作用 |
47
+ |---|---:|---|---|
48
+ | `HYPER_API_KEY` | Charm Hyper API Key 鉴权需要 | 无 | 为内置 `charm-hyper` Provider 提供凭据;OAuth 用户可以使用 `/login` |
49
+ | `PI_CODING_AGENT_DIR` | 否 | `~/.pi/agent` | 修改公开 OpenRouter 元数据缓存的基础目录 |
50
+
51
+ 程序化集成可以通过 `createProviderKitRuntime()` 或 `createProviderKitHost()` 配置价格回退、价格策略、请求超时、元数据 URL 和缓存路径。源码定义 [`ProviderKitDependencies`](core/runtime-config.ts) 是权威依据。
52
+
53
+ 可信包可以通过 manifest 中的 `providers/`、`status/`、`preflight/` 和 `tuners/` 条目添加 Adapter。Helper、校验、冲突、reload 行为和生命周期边界见 [Adapter Extension 契约](https://github.com/hyav/pi-provider/blob/main/docs/adapter-extensions.zh-CN.md)。根目录 [`index.ts`](index.ts) 定义公开 TypeScript 导出。
54
+
55
+ ## 使用须知
56
+
57
+ `/status` 离线运行,`/status refresh` 执行免费远程检查,`/status check` 会发送可能消耗配额的真实模型请求。配置的凭据只会发送给对应 Provider 端点,并且不会出现在 Status 输出中。
58
+
59
+ ## 许可证
60
+
61
+ [MIT](LICENSE)
package/SECURITY.md ADDED
@@ -0,0 +1,36 @@
1
+ # Security Policy
2
+
3
+ `@hyav/pi-provider` is a Pi extension. Its TypeScript source runs with the user's system privileges, can access configured Provider endpoints and credentials, and may perform explicitly requested account or model checks. Review the source and package artifact before installing extensions from untrusted sources.
4
+
5
+ ## Supported versions
6
+
7
+ | Version or branch | Support |
8
+ |---|---|
9
+ | Latest published release | Best-effort security fixes |
10
+ | Older published releases | Not supported |
11
+ | Unreleased `main` | No compatibility or response-time promise |
12
+
13
+ There is no long-term-support branch. Upgrade to the latest release before reporting whether a problem is still present.
14
+
15
+ ## Reporting a vulnerability
16
+
17
+ Do **not** report suspected vulnerabilities in a public issue, pull request, chat, or forum.
18
+
19
+ Please report vulnerabilities privately via [GitHub Security Advisories](https://github.com/hyav/pi-provider/security/advisories/new). Include:
20
+
21
+ - affected package version, commit, or published artifact;
22
+ - reproduction steps or a minimal proof of concept;
23
+ - impact, prerequisites, and affected trust boundary;
24
+ - logs or traces with credentials, tokens, private endpoints, and personal data removed;
25
+ - a safe way to contact you for follow-up.
26
+
27
+ ## Response and disclosure
28
+
29
+ Reports are handled on a best-effort basis; no acknowledgement, remediation, or disclosure deadline is guaranteed. The maintainer will coordinate a fix and public disclosure when affected users have a reasonable mitigation or upgrade path. Please do not publish details before then.
30
+
31
+ ## Scope and dependency boundary
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 Kit 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
+
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
+
package/SUPPORT.md ADDED
@@ -0,0 +1,25 @@
1
+ # Support
2
+
3
+ Support is best effort for the latest published release line. This project has no long-term-support branch or guaranteed response SLA. Older release lines are unsupported. The `main` branch may contain unreleased behavior and is not a compatibility promise.
4
+
5
+ ## Where to ask
6
+
7
+ - **Reproducible bugs:** open a [GitHub issue](https://github.com/hyav/pi-provider/issues).
8
+ - **Feature requests:** open an issue with the user problem, proposed behavior, and compatibility impact.
9
+ - **Usage questions:** open an issue after checking the [README](README.md) and [adapter documentation](docs/adapter-extensions.md).
10
+ - **Security vulnerabilities:** do not use a public issue; follow [SECURITY.md](SECURITY.md).
11
+ - **Provider outages or billing disputes:** contact the affected Provider. This package only reports the upstream data it can retrieve.
12
+
13
+ ## Include in a useful report
14
+
15
+ Please provide:
16
+
17
+ - the package version or commit;
18
+ - Node.js and Pi versions;
19
+ - operating system and installation method;
20
+ - Provider and model IDs, without API keys or OAuth tokens;
21
+ - expected and actual behavior;
22
+ - a minimal reproduction and redacted logs.
23
+
24
+ Avoid posting prompts, account IDs, cookies, authorization headers, private source code, or personal data. If a report contains sensitive material, close it and follow [SECURITY.md](SECURITY.md) instead.
25
+
@@ -0,0 +1,175 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import {
3
+ type AdapterKind,
4
+ type AdapterRegistrationEnvelope,
5
+ PROVIDER_KIT_ADAPTER_EVENT,
6
+ PROVIDER_KIT_ADAPTER_PROTOCOL_VERSION,
7
+ PROVIDER_KIT_STARTUP_BRIDGE_EVENT,
8
+ type StartupBridge,
9
+ type StartupBridgeRequest,
10
+ } from "./adapter-protocol.ts";
11
+ import { isStableAdapterId, validateAdapter, validateAdapterIdentity } from "./adapter-validation.ts";
12
+ import type { PreflightAdapter } from "./preflight-manager.ts";
13
+ import { registerProviderAdapter } from "./provider-registration.ts";
14
+ import type { ProviderKitDependencies } from "./runtime-config.ts";
15
+ import { getDefaultProviderKitDependencies } from "./runtime-config.ts";
16
+ import type { ProviderAdapter, StatusAdapter, TunerAdapter } from "./types.ts";
17
+
18
+ /** Context supplied to an Adapter Extension factory. */
19
+ export interface AdapterExtensionContext extends ProviderKitDependencies {
20
+ /** The Pi API that owns this extension factory. */
21
+ pi: ExtensionAPI;
22
+ }
23
+
24
+ export interface ProviderExtensionDefinition {
25
+ id: string;
26
+ create(context: AdapterExtensionContext): ProviderAdapter | Promise<ProviderAdapter>;
27
+ }
28
+
29
+ export interface StatusExtensionDefinition {
30
+ id: string;
31
+ providerId: string;
32
+ create(context: AdapterExtensionContext): StatusAdapter | Promise<StatusAdapter>;
33
+ }
34
+
35
+ export interface PreflightExtensionDefinition {
36
+ id: string;
37
+ providerId: string;
38
+ create(context: AdapterExtensionContext): PreflightAdapter | Promise<PreflightAdapter>;
39
+ }
40
+
41
+ export interface TunerExtensionDefinition {
42
+ id: string;
43
+ create(context: AdapterExtensionContext): TunerAdapter | Promise<TunerAdapter>;
44
+ }
45
+
46
+ function validateStaticIdentity(kind: AdapterKind, id: string, providerId?: string): void {
47
+ if (!isStableAdapterId(id)) throw new Error(`${kind} static ID must be a non-empty ID without whitespace`);
48
+ if ((kind === "status" || kind === "preflight") && !isStableAdapterId(providerId)) {
49
+ throw new Error(`${kind} static provider ID must be a non-empty ID without whitespace`);
50
+ }
51
+ }
52
+
53
+ function getStartupBridge(pi: ExtensionAPI): StartupBridge {
54
+ const request: StartupBridgeRequest = {};
55
+ pi.events.emit(PROVIDER_KIT_STARTUP_BRIDGE_EVENT, request);
56
+ return (
57
+ request.bridge ?? {
58
+ dependencies: getDefaultProviderKitDependencies(),
59
+ officialPricing: Promise.resolve({}),
60
+ }
61
+ );
62
+ }
63
+
64
+ function emitRegistration(pi: ExtensionAPI, envelope: AdapterRegistrationEnvelope): void {
65
+ pi.events.emit(PROVIDER_KIT_ADAPTER_EVENT, envelope);
66
+ pi.on("session_start", () => {
67
+ pi.events.emit(PROVIDER_KIT_ADAPTER_EVENT, envelope);
68
+ });
69
+ }
70
+
71
+ function createAdapterExtension<TAdapter extends ProviderAdapter | StatusAdapter | PreflightAdapter | TunerAdapter>(
72
+ kind: AdapterKind,
73
+ id: string,
74
+ providerId: string | undefined,
75
+ create: (context: AdapterExtensionContext) => TAdapter | Promise<TAdapter>,
76
+ ): (pi: ExtensionAPI) => Promise<void> {
77
+ validateStaticIdentity(kind, id, providerId);
78
+ const token = {};
79
+ return async (pi) => {
80
+ const bridge = getStartupBridge(pi);
81
+ const createAdapter = (context: AdapterExtensionContext) => create(context);
82
+ const adapter = await createAdapter({ ...bridge.dependencies, pi });
83
+ if (kind === "provider") {
84
+ const providerAdapter = adapter as ProviderAdapter;
85
+ validateAdapter("provider", providerAdapter);
86
+ validateAdapterIdentity("provider", id, providerAdapter);
87
+ const modelDrafts = providerAdapter.provider.models;
88
+ // The startup registration is intentionally independent of Host
89
+ // acknowledgement. Host re-registers the accepted catalog after the
90
+ // session_start barrier with official pricing and conflict resolution.
91
+ // Keep the pre-normalization drafts in the plain event envelope: the
92
+ // Host runs in a different Pi module context and cannot use this
93
+ // extension's module-local state. The factory is retained so a Host
94
+ // loaded later can recreate the adapter with its configured runtime.
95
+ registerProviderAdapter(pi, providerAdapter, bridge.dependencies, {}, modelDrafts);
96
+ emitRegistration(pi, {
97
+ version: PROVIDER_KIT_ADAPTER_PROTOCOL_VERSION,
98
+ kind: "provider",
99
+ id,
100
+ token,
101
+ adapter: providerAdapter,
102
+ factory: createAdapter,
103
+ startupDependencies: bridge.dependencies,
104
+ modelDrafts,
105
+ });
106
+ return;
107
+ }
108
+ if (kind === "status") {
109
+ const statusAdapter = adapter as StatusAdapter;
110
+ validateAdapter("status", statusAdapter);
111
+ validateAdapterIdentity("status", id, providerId, statusAdapter);
112
+ emitRegistration(pi, {
113
+ version: PROVIDER_KIT_ADAPTER_PROTOCOL_VERSION,
114
+ kind: "status",
115
+ id,
116
+ providerId: providerId!,
117
+ token,
118
+ adapter: statusAdapter,
119
+ factory: createAdapter,
120
+ startupDependencies: bridge.dependencies,
121
+ });
122
+ return;
123
+ }
124
+ if (kind === "preflight") {
125
+ const preflightAdapter = adapter as PreflightAdapter;
126
+ validateAdapter("preflight", preflightAdapter);
127
+ validateAdapterIdentity("preflight", id, providerId, preflightAdapter);
128
+ emitRegistration(pi, {
129
+ version: PROVIDER_KIT_ADAPTER_PROTOCOL_VERSION,
130
+ kind: "preflight",
131
+ id,
132
+ providerId: providerId!,
133
+ token,
134
+ adapter: preflightAdapter,
135
+ factory: createAdapter,
136
+ startupDependencies: bridge.dependencies,
137
+ });
138
+ return;
139
+ }
140
+ const tunerAdapter = adapter as TunerAdapter;
141
+ validateAdapter("tuner", tunerAdapter);
142
+ validateAdapterIdentity("tuner", id, tunerAdapter);
143
+ emitRegistration(pi, {
144
+ version: PROVIDER_KIT_ADAPTER_PROTOCOL_VERSION,
145
+ kind: "tuner",
146
+ id,
147
+ token,
148
+ adapter: tunerAdapter,
149
+ factory: createAdapter,
150
+ startupDependencies: bridge.dependencies,
151
+ });
152
+ };
153
+ }
154
+
155
+ /** Define a Pi extension factory that contributes one Provider Adapter. */
156
+ export function defineProviderExtension(definition: ProviderExtensionDefinition): (pi: ExtensionAPI) => Promise<void> {
157
+ return createAdapterExtension("provider", definition.id, undefined, definition.create);
158
+ }
159
+
160
+ /** Define a Pi extension factory that contributes one Status Adapter. */
161
+ export function defineStatusExtension(definition: StatusExtensionDefinition): (pi: ExtensionAPI) => Promise<void> {
162
+ return createAdapterExtension("status", definition.id, definition.providerId, definition.create);
163
+ }
164
+
165
+ /** Define a Pi extension factory that contributes one Preflight Adapter. */
166
+ export function definePreflightExtension(
167
+ definition: PreflightExtensionDefinition,
168
+ ): (pi: ExtensionAPI) => Promise<void> {
169
+ return createAdapterExtension("preflight", definition.id, definition.providerId, definition.create);
170
+ }
171
+
172
+ /** Define a Pi extension factory that contributes one Tuner Adapter. */
173
+ export function defineTunerExtension(definition: TunerExtensionDefinition): (pi: ExtensionAPI) => Promise<void> {
174
+ return createAdapterExtension("tuner", definition.id, undefined, definition.create);
175
+ }
@@ -0,0 +1,120 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import type { OfficialModelMeta } from "./official-pricing.ts";
3
+ import type { PreflightAdapter } from "./preflight-manager.ts";
4
+ import type { ProviderKitDependencies } from "./runtime-config.ts";
5
+ import type { ProviderAdapter, ProviderModelDraft, StatusAdapter, TunerAdapter } from "./types.ts";
6
+
7
+ /** Registration protocol version shared by Host and Adapter Extensions. */
8
+ export const PROVIDER_KIT_ADAPTER_PROTOCOL_VERSION = 2 as const;
9
+
10
+ /** Event-bus channel used for Adapter Extension registration envelopes. */
11
+ export const PROVIDER_KIT_ADAPTER_EVENT = "provider-kit:adapter";
12
+
13
+ /** Event-bus channel used by the Host to expose the factory-stage startup bridge. */
14
+ export const PROVIDER_KIT_STARTUP_BRIDGE_EVENT = "provider-kit:startup-bridge";
15
+
16
+ /** Event-bus channel used to detect more than one Provider Kit Host. */
17
+ export const PROVIDER_KIT_HOST_CLAIM_EVENT = "provider-kit:host-claim";
18
+
19
+ export type AdapterKind = "provider" | "status" | "preflight" | "tuner";
20
+
21
+ export interface AdapterFactoryContext extends ProviderKitDependencies {
22
+ pi: ExtensionAPI;
23
+ }
24
+
25
+ export type AdapterFactory = (
26
+ context: AdapterFactoryContext,
27
+ ) =>
28
+ | ProviderAdapter
29
+ | StatusAdapter
30
+ | PreflightAdapter
31
+ | TunerAdapter
32
+ | Promise<ProviderAdapter | StatusAdapter | PreflightAdapter | TunerAdapter>;
33
+
34
+ export interface AdapterRegistrationEnvelopeBase {
35
+ version: typeof PROVIDER_KIT_ADAPTER_PROTOCOL_VERSION;
36
+ kind: AdapterKind;
37
+ id: string;
38
+ token: object;
39
+ /** Factory retained for Host-side rehydration when Host loaded after Adapter. */
40
+ factory: AdapterFactory;
41
+ /** Exact dependency object used by the initial factory invocation. */
42
+ startupDependencies: ProviderKitDependencies;
43
+ }
44
+
45
+ export interface ProviderAdapterRegistrationEnvelope extends AdapterRegistrationEnvelopeBase {
46
+ kind: "provider";
47
+ adapter: ProviderAdapter;
48
+ /** Original model drafts, before the startup catalog is normalized. */
49
+ modelDrafts?: ProviderModelDraft[];
50
+ }
51
+
52
+ export interface StatusAdapterRegistrationEnvelope extends AdapterRegistrationEnvelopeBase {
53
+ kind: "status";
54
+ providerId: string;
55
+ adapter: StatusAdapter;
56
+ }
57
+
58
+ export interface PreflightAdapterRegistrationEnvelope extends AdapterRegistrationEnvelopeBase {
59
+ kind: "preflight";
60
+ providerId: string;
61
+ adapter: PreflightAdapter;
62
+ }
63
+
64
+ export interface TunerAdapterRegistrationEnvelope extends AdapterRegistrationEnvelopeBase {
65
+ kind: "tuner";
66
+ adapter: TunerAdapter;
67
+ }
68
+
69
+ export type AdapterRegistrationEnvelope =
70
+ | ProviderAdapterRegistrationEnvelope
71
+ | StatusAdapterRegistrationEnvelope
72
+ | PreflightAdapterRegistrationEnvelope
73
+ | TunerAdapterRegistrationEnvelope;
74
+
75
+ export interface StartupBridge {
76
+ dependencies: ProviderKitDependencies;
77
+ officialPricing: Promise<Record<string, OfficialModelMeta>>;
78
+ }
79
+
80
+ export interface StartupBridgeRequest {
81
+ bridge?: StartupBridge;
82
+ }
83
+
84
+ export interface HostClaimRequest {
85
+ token: object;
86
+ occupied: boolean;
87
+ }
88
+
89
+ export function isHostClaimRequest(value: unknown): value is HostClaimRequest {
90
+ return (
91
+ value !== null &&
92
+ typeof value === "object" &&
93
+ "token" in value &&
94
+ (value as { token?: unknown }).token !== null &&
95
+ typeof (value as { token?: unknown }).token === "object" &&
96
+ "occupied" in value &&
97
+ typeof (value as { occupied?: unknown }).occupied === "boolean"
98
+ );
99
+ }
100
+
101
+ export function isAdapterRegistrationEnvelope(value: unknown): value is AdapterRegistrationEnvelope {
102
+ if (value === null || typeof value !== "object") return false;
103
+ const candidate = value as Partial<AdapterRegistrationEnvelope>;
104
+ return (
105
+ candidate.version === PROVIDER_KIT_ADAPTER_PROTOCOL_VERSION &&
106
+ (candidate.kind === "provider" ||
107
+ candidate.kind === "status" ||
108
+ candidate.kind === "preflight" ||
109
+ candidate.kind === "tuner") &&
110
+ typeof candidate.id === "string" &&
111
+ candidate.token !== null &&
112
+ typeof candidate.token === "object" &&
113
+ typeof candidate.factory === "function" &&
114
+ candidate.startupDependencies !== null &&
115
+ typeof candidate.startupDependencies === "object" &&
116
+ candidate.adapter !== null &&
117
+ typeof candidate.adapter === "object" &&
118
+ (candidate.kind !== "provider" || candidate.modelDrafts === undefined || Array.isArray(candidate.modelDrafts))
119
+ );
120
+ }