@hyav/pi-provider 0.1.3 → 0.1.5

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 (52) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +8 -1
  3. package/README.zh-CN.md +8 -1
  4. package/core/adapter-validation.ts +23 -6
  5. package/core/catalog-preflight.ts +142 -0
  6. package/core/credential-type.ts +13 -0
  7. package/core/diagnostic-auth.ts +103 -0
  8. package/core/host.ts +26 -4
  9. package/core/live-check-manager.ts +2 -1
  10. package/core/official-pricing.ts +39 -17
  11. package/core/preflight-manager.ts +40 -8
  12. package/core/provider-registration.ts +105 -6
  13. package/core/public-adapters.ts +10 -0
  14. package/core/ratelimit-headers.ts +72 -0
  15. package/core/runtime-config.ts +3 -0
  16. package/core/runtime.ts +55 -34
  17. package/core/status-manager.ts +34 -9
  18. package/core/types.ts +22 -2
  19. package/index.ts +12 -1
  20. package/package.json +1 -1
  21. package/preflight/anthropic.ts +42 -0
  22. package/preflight/cerebras.ts +27 -0
  23. package/preflight/charm-hyper.ts +2 -1
  24. package/preflight/deepseek.ts +2 -1
  25. package/preflight/github-copilot.ts +87 -0
  26. package/preflight/google.ts +2 -1
  27. package/preflight/groq.ts +73 -0
  28. package/preflight/huggingface.ts +27 -0
  29. package/preflight/mistral.ts +27 -0
  30. package/preflight/moonshotai-cn.ts +27 -0
  31. package/preflight/moonshotai.ts +37 -0
  32. package/preflight/nvidia.ts +27 -0
  33. package/preflight/openai-codex.ts +2 -1
  34. package/preflight/openai.ts +27 -0
  35. package/preflight/openrouter.ts +112 -0
  36. package/preflight/vercel-ai-gateway.ts +81 -0
  37. package/preflight/xai.ts +71 -0
  38. package/providers/charm-hyper.ts +4 -1
  39. package/status/anthropic.ts +262 -0
  40. package/status/charm-hyper.ts +3 -2
  41. package/status/deepseek.ts +2 -1
  42. package/status/github-copilot.ts +189 -0
  43. package/status/groq.ts +89 -0
  44. package/status/huggingface.ts +95 -0
  45. package/status/moonshotai-cn.ts +26 -0
  46. package/status/moonshotai.ts +151 -0
  47. package/status/openai-codex.ts +2 -1
  48. package/status/opencode-go.ts +2 -1
  49. package/status/openrouter.ts +173 -0
  50. package/status/vercel-ai-gateway/constants.ts +3 -0
  51. package/status/vercel-ai-gateway.ts +95 -0
  52. package/status/xai.ts +74 -0
package/index.ts CHANGED
@@ -17,7 +17,16 @@ export {
17
17
  defineStatusExtension,
18
18
  defineTunerExtension,
19
19
  } from "./core/adapter-extensions.ts";
20
+ export { MAX_PROVIDER_MODEL_COUNT } from "./core/adapter-validation.ts";
21
+ export { createCatalogPreflightAdapter } from "./core/catalog-preflight.ts";
20
22
  export { withDeadline } from "./core/deadline.ts";
23
+ export {
24
+ appendBaseUrlPath,
25
+ authDefinesHeader,
26
+ getContextAuth,
27
+ hasBaseUrlOrigin,
28
+ mergeDiagnosticHeaders,
29
+ } from "./core/diagnostic-auth.ts";
21
30
  export type { ProviderDataErrorLike } from "./core/errors.ts";
22
31
  export { isProviderDataError, ProviderDataError } from "./core/errors.ts";
23
32
  export type {
@@ -74,6 +83,7 @@ export type {
74
83
  export { getPreflightKey, normalizePreflightSnapshot, PreflightManager } from "./core/preflight-manager.ts";
75
84
  export { applyPricingAdjustment, resolvePricingDetails } from "./core/pricing-adjustments.ts";
76
85
  export { normalizeProviderModels } from "./core/provider-registration.ts";
86
+ export type { RateLimitWindow } from "./core/ratelimit-headers.ts";
77
87
  export { parseRetryAfter } from "./core/retry-after.ts";
78
88
  export type { StatusDiagnostics, StatusErrorState } from "./core/status-manager.ts";
79
89
  export { normalizeStatusSnapshot, StatusManager } from "./core/status-manager.ts";
@@ -101,6 +111,7 @@ export type {
101
111
  ProviderPricingPolicy,
102
112
  ProviderPricingSource,
103
113
  ProviderRefreshContext,
114
+ ProviderRequestAuth,
104
115
  StatusAdapter,
105
116
  StatusAmountEntry,
106
117
  StatusContext,
@@ -114,7 +125,7 @@ export type {
114
125
  } from "./core/types.ts";
115
126
 
116
127
  export interface PiProviderExtensionOptions {
117
- /** User adapter root; replaces the default `<agentDir>/pi-provider` directory. */
128
+ /** User adapter root; replaces the default `<agentDir>/extensions/pi-provider` directory. */
118
129
  adapterRoot?: string;
119
130
  /** Host runtime dependency overrides. */
120
131
  dependencies?: Partial<PiProviderDependencies>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyav/pi-provider",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Provider extension toolkit for Pi to integrate and manage custom LLM providers with dynamic models, request tuners, and account status.",
5
5
  "author": "hyav",
6
6
  "license": "MIT",
@@ -0,0 +1,42 @@
1
+ import type { PreflightAdapter } from "@hyav/pi-provider";
2
+ import { definePreflightExtension } from "@hyav/pi-provider";
3
+ import { createCatalogPreflightAdapter } from "../core/catalog-preflight.ts";
4
+ import { isAnthropicApiKey, isAnthropicOAuthToken } from "../status/anthropic.ts";
5
+
6
+ export const ANTHROPIC_MODELS_URL = "https://api.anthropic.com/v1/models";
7
+ export const ANTHROPIC_API_VERSION = "2023-06-01";
8
+
9
+ /**
10
+ * Anthropic accepts API keys via x-api-key and short-lived OAuth/WIF tokens via
11
+ * Authorization Bearer. Only the user's own default clause receives the key, no
12
+ * unknown/third-party endpoint.
13
+ */
14
+ function anthropicAuthHeaders(apiKey: string, credential: string | undefined): Record<string, string> {
15
+ const isOAuth = credential === "oauth" ? !isAnthropicApiKey(apiKey) : isAnthropicOAuthToken(apiKey);
16
+ if (isOAuth) return { Authorization: `Bearer ${apiKey}` };
17
+ return { "x-api-key": apiKey };
18
+ }
19
+
20
+ export const anthropicPreflightAdapter: PreflightAdapter = createCatalogPreflightAdapter(
21
+ {
22
+ id: "anthropic-preflight",
23
+ providerId: "anthropic",
24
+ name: "Anthropic",
25
+ modelsUrl: ANTHROPIC_MODELS_URL,
26
+ headers: { "anthropic-version": ANTHROPIC_API_VERSION },
27
+ authHeaders: anthropicAuthHeaders,
28
+ },
29
+ 8_000,
30
+ );
31
+
32
+ export function createAnthropicPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
33
+ return { ...anthropicPreflightAdapter, requestTimeoutMs };
34
+ }
35
+
36
+ const anthropicPreflightExtension = definePreflightExtension({
37
+ id: "anthropic-preflight",
38
+ providerId: "anthropic",
39
+ create: ({ statusRequestTimeoutMs }) => createAnthropicPreflightAdapter(statusRequestTimeoutMs),
40
+ });
41
+
42
+ export default anthropicPreflightExtension;
@@ -0,0 +1,27 @@
1
+ import type { PreflightAdapter } from "@hyav/pi-provider";
2
+ import { definePreflightExtension } from "@hyav/pi-provider";
3
+ import { createCatalogPreflightAdapter } from "../core/catalog-preflight.ts";
4
+
5
+ export const CEREBRAS_MODELS_URL = "https://api.cerebras.ai/v1/models";
6
+
7
+ export const cerebrasPreflightAdapter: PreflightAdapter = createCatalogPreflightAdapter(
8
+ {
9
+ id: "cerebras-preflight",
10
+ providerId: "cerebras",
11
+ name: "Cerebras",
12
+ modelsUrl: CEREBRAS_MODELS_URL,
13
+ },
14
+ 8_000,
15
+ );
16
+
17
+ export function createCerebrasPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
18
+ return { ...cerebrasPreflightAdapter, requestTimeoutMs };
19
+ }
20
+
21
+ const cerebrasPreflightExtension = definePreflightExtension({
22
+ id: "cerebras-preflight",
23
+ providerId: "cerebras",
24
+ create: ({ statusRequestTimeoutMs }) => createCerebrasPreflightAdapter(statusRequestTimeoutMs),
25
+ });
26
+
27
+ export default cerebrasPreflightExtension;
@@ -1,5 +1,5 @@
1
1
  import type { PreflightAdapter } from "@hyav/pi-provider";
2
- import { definePreflightExtension, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
2
+ import { definePreflightExtension, hasBaseUrlOrigin, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
3
3
  import { hyperJsonHeaders } from "../providers/charm-hyper/constants.ts";
4
4
  import { HYPER_MODELS_URL, HYPER_PROVIDER_URL, parseHyperModels } from "../providers/charm-hyper.ts";
5
5
 
@@ -10,6 +10,7 @@ export function createCharmHyperPreflightAdapter(requestTimeoutMs: number): Pref
10
10
  name: "Charm Hyper",
11
11
  cacheTtlMs: 30_000,
12
12
  requestTimeoutMs,
13
+ supportsModel: (model) => hasBaseUrlOrigin(model.baseUrl, HYPER_PROVIDER_URL),
13
14
  async fetch(context) {
14
15
  const apiKey = await context.getApiKey();
15
16
  if (!apiKey) return { passed: false, checks: ["auth"], updatedAt: context.now() };
@@ -1,5 +1,5 @@
1
1
  import type { PreflightAdapter } from "@hyav/pi-provider";
2
- import { definePreflightExtension, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
2
+ import { definePreflightExtension, hasBaseUrlOrigin, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
3
3
 
4
4
  export const DEEPSEEK_MODELS_URL = "https://api.deepseek.com/models";
5
5
 
@@ -13,6 +13,7 @@ export const deepSeekPreflightAdapter: PreflightAdapter = {
13
13
  name: "DeepSeek",
14
14
  cacheTtlMs: 30_000,
15
15
  requestTimeoutMs: 8_000,
16
+ supportsModel: (model) => hasBaseUrlOrigin(model.baseUrl, DEEPSEEK_MODELS_URL),
16
17
  async fetch(context) {
17
18
  const apiKey = await context.getApiKey();
18
19
  if (!apiKey || apiKey === "proxy-managed") {
@@ -0,0 +1,87 @@
1
+ import type { PreflightAdapter } from "@hyav/pi-provider";
2
+ import {
3
+ appendBaseUrlPath,
4
+ authDefinesHeader,
5
+ definePreflightExtension,
6
+ getContextAuth,
7
+ mergeDiagnosticHeaders,
8
+ ProviderDataError,
9
+ parseRetryAfter,
10
+ } from "@hyav/pi-provider";
11
+
12
+ export const COPILOT_BASE_URL = "https://api.individual.githubcopilot.com";
13
+ export const COPILOT_MODELS_URL = `${COPILOT_BASE_URL}/models`;
14
+
15
+ function isRecord(value: unknown): value is Record<string, unknown> {
16
+ return value !== null && typeof value === "object" && !Array.isArray(value);
17
+ }
18
+
19
+ export const githubCopilotPreflightAdapter: PreflightAdapter = {
20
+ id: "github-copilot-preflight",
21
+ providerId: "github-copilot",
22
+ name: "GitHub Copilot",
23
+ cacheTtlMs: 30_000,
24
+ requestTimeoutMs: 8_000,
25
+ async fetch(context) {
26
+ const auth = await getContextAuth(context);
27
+ const apiKey = auth.apiKey;
28
+ if (!apiKey || apiKey === "proxy-managed") {
29
+ return { passed: false, checks: ["auth"], updatedAt: context.now() };
30
+ }
31
+ const url = appendBaseUrlPath(context.model.baseUrl ?? auth.baseUrl, "models", COPILOT_BASE_URL);
32
+ if (!url) throw new ProviderDataError("GitHub Copilot model endpoint is unavailable", "unsupported");
33
+ const headers = mergeDiagnosticHeaders(auth, {
34
+ Accept: "application/json",
35
+ "Accept-Encoding": "identity",
36
+ "User-Agent": "@hyav/pi-provider",
37
+ });
38
+ if (!authDefinesHeader(auth, "Authorization")) headers.set("Authorization", `Bearer ${apiKey}`);
39
+ const response = await context.fetch(url, {
40
+ headers,
41
+ signal: context.signal,
42
+ });
43
+ if (!response.ok) {
44
+ throw new ProviderDataError(
45
+ `GitHub Copilot preflight failed: HTTP ${response.status}`,
46
+ `http${response.status}`,
47
+ parseRetryAfter(response.headers.get("retry-after"), context.now()),
48
+ response.status,
49
+ );
50
+ }
51
+ let payload: unknown;
52
+ try {
53
+ payload = await response.json();
54
+ } catch {
55
+ throw new ProviderDataError("GitHub Copilot preflight returned invalid JSON", "badjson");
56
+ }
57
+ if (!isRecord(payload) || !Array.isArray(payload.data)) {
58
+ throw new ProviderDataError("GitHub Copilot preflight returned invalid catalog data", "badjson");
59
+ }
60
+ const modelIds = new Set(
61
+ payload.data.flatMap((model) => {
62
+ if (!isRecord(model) || typeof model.id !== "string") return [];
63
+ const id = model.id.trim();
64
+ if (id === "") return [];
65
+ return [id];
66
+ }),
67
+ );
68
+ return {
69
+ passed: modelIds.has(context.model.id),
70
+ checks: ["endpoint", "catalog", "auth"],
71
+ updatedAt: context.now(),
72
+ httpStatus: response.status,
73
+ };
74
+ },
75
+ };
76
+
77
+ export function createGithubCopilotPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
78
+ return { ...githubCopilotPreflightAdapter, requestTimeoutMs };
79
+ }
80
+
81
+ const githubCopilotPreflightExtension = definePreflightExtension({
82
+ id: "github-copilot-preflight",
83
+ providerId: "github-copilot",
84
+ create: ({ statusRequestTimeoutMs }) => createGithubCopilotPreflightAdapter(statusRequestTimeoutMs),
85
+ });
86
+
87
+ export default githubCopilotPreflightExtension;
@@ -1,5 +1,5 @@
1
1
  import type { PreflightAdapter } from "@hyav/pi-provider";
2
- import { definePreflightExtension, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
2
+ import { definePreflightExtension, hasBaseUrlOrigin, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
3
3
 
4
4
  export const GOOGLE_MODELS_URL = "https://generativelanguage.googleapis.com/v1beta/models";
5
5
 
@@ -28,6 +28,7 @@ export const googlePreflightAdapter: PreflightAdapter = {
28
28
  name: "Google Gemini",
29
29
  cacheTtlMs: 30_000,
30
30
  requestTimeoutMs: 8_000,
31
+ supportsModel: (model) => hasBaseUrlOrigin(model.baseUrl, GOOGLE_MODELS_URL),
31
32
  async fetch(context) {
32
33
  const apiKey = await context.getApiKey();
33
34
  if (!apiKey || apiKey === "proxy-managed") {
@@ -0,0 +1,73 @@
1
+ import type { PreflightAdapter } from "@hyav/pi-provider";
2
+ import { definePreflightExtension, hasBaseUrlOrigin, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
3
+ import { GROQ_MODELS_URL } from "../status/groq.ts";
4
+
5
+ function isRecord(value: unknown): value is Record<string, unknown> {
6
+ return value !== null && typeof value === "object" && !Array.isArray(value);
7
+ }
8
+
9
+ export const groqPreflightAdapter: PreflightAdapter = {
10
+ id: "groq-preflight",
11
+ providerId: "groq",
12
+ name: "Groq",
13
+ cacheTtlMs: 30_000,
14
+ requestTimeoutMs: 8_000,
15
+ supportsModel: (model) => hasBaseUrlOrigin(model.baseUrl, GROQ_MODELS_URL),
16
+ async fetch(context) {
17
+ const apiKey = await context.getApiKey();
18
+ if (!apiKey || apiKey === "proxy-managed") {
19
+ return { passed: false, checks: ["auth"], updatedAt: context.now() };
20
+ }
21
+ const response = await context.fetch(GROQ_MODELS_URL, {
22
+ headers: {
23
+ Accept: "application/json",
24
+ "Accept-Encoding": "identity",
25
+ Authorization: `Bearer ${apiKey}`,
26
+ },
27
+ signal: context.signal,
28
+ });
29
+ if (!response.ok) {
30
+ throw new ProviderDataError(
31
+ `Groq preflight failed: HTTP ${response.status}`,
32
+ `http${response.status}`,
33
+ parseRetryAfter(response.headers.get("retry-after"), context.now()),
34
+ response.status,
35
+ );
36
+ }
37
+ let payload: unknown;
38
+ try {
39
+ payload = await response.json();
40
+ } catch {
41
+ throw new ProviderDataError("Groq preflight returned invalid JSON", "badjson");
42
+ }
43
+ if (!isRecord(payload) || !Array.isArray(payload.data)) {
44
+ throw new ProviderDataError("Groq preflight returned invalid catalog data", "badjson");
45
+ }
46
+ const activeIds = new Set(
47
+ payload.data.flatMap((model) => {
48
+ if (!isRecord(model) || typeof model.id !== "string") return [];
49
+ const id = model.id.trim();
50
+ if (id === "" || model.active === false) return [];
51
+ return [id];
52
+ }),
53
+ );
54
+ return {
55
+ passed: activeIds.has(context.model.id),
56
+ checks: ["endpoint", "auth", "catalog"],
57
+ updatedAt: context.now(),
58
+ httpStatus: response.status,
59
+ };
60
+ },
61
+ };
62
+
63
+ export function createGroqPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
64
+ return { ...groqPreflightAdapter, requestTimeoutMs };
65
+ }
66
+
67
+ const groqPreflightExtension = definePreflightExtension({
68
+ id: "groq-preflight",
69
+ providerId: "groq",
70
+ create: ({ statusRequestTimeoutMs }) => createGroqPreflightAdapter(statusRequestTimeoutMs),
71
+ });
72
+
73
+ export default groqPreflightExtension;
@@ -0,0 +1,27 @@
1
+ import type { PreflightAdapter } from "@hyav/pi-provider";
2
+ import { definePreflightExtension } from "@hyav/pi-provider";
3
+ import { createCatalogPreflightAdapter } from "../core/catalog-preflight.ts";
4
+
5
+ export const HF_ROUTER_MODELS_URL = "https://router.huggingface.co/v1/models";
6
+
7
+ export const huggingFacePreflightAdapter: PreflightAdapter = createCatalogPreflightAdapter(
8
+ {
9
+ id: "huggingface-preflight",
10
+ providerId: "huggingface",
11
+ name: "Hugging Face",
12
+ modelsUrl: HF_ROUTER_MODELS_URL,
13
+ },
14
+ 8_000,
15
+ );
16
+
17
+ export function createHuggingFacePreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
18
+ return { ...huggingFacePreflightAdapter, requestTimeoutMs };
19
+ }
20
+
21
+ const huggingFacePreflightExtension = definePreflightExtension({
22
+ id: "huggingface-preflight",
23
+ providerId: "huggingface",
24
+ create: ({ statusRequestTimeoutMs }) => createHuggingFacePreflightAdapter(statusRequestTimeoutMs),
25
+ });
26
+
27
+ export default huggingFacePreflightExtension;
@@ -0,0 +1,27 @@
1
+ import type { PreflightAdapter } from "@hyav/pi-provider";
2
+ import { definePreflightExtension } from "@hyav/pi-provider";
3
+ import { createCatalogPreflightAdapter } from "../core/catalog-preflight.ts";
4
+
5
+ export const MISTRAL_MODELS_URL = "https://api.mistral.ai/v1/models";
6
+
7
+ export const mistralPreflightAdapter: PreflightAdapter = createCatalogPreflightAdapter(
8
+ {
9
+ id: "mistral-preflight",
10
+ providerId: "mistral",
11
+ name: "Mistral",
12
+ modelsUrl: MISTRAL_MODELS_URL,
13
+ },
14
+ 8_000,
15
+ );
16
+
17
+ export function createMistralPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
18
+ return { ...mistralPreflightAdapter, requestTimeoutMs };
19
+ }
20
+
21
+ const mistralPreflightExtension = definePreflightExtension({
22
+ id: "mistral-preflight",
23
+ providerId: "mistral",
24
+ create: ({ statusRequestTimeoutMs }) => createMistralPreflightAdapter(statusRequestTimeoutMs),
25
+ });
26
+
27
+ export default mistralPreflightExtension;
@@ -0,0 +1,27 @@
1
+ import type { PreflightAdapter } from "@hyav/pi-provider";
2
+ import { definePreflightExtension } from "@hyav/pi-provider";
3
+ import { createMoonshotPreflightAdapter, MOONSHOT_CN_MODELS_URL } from "./moonshotai.ts";
4
+
5
+ export const moonshotaiCnPreflightAdapter: PreflightAdapter = createMoonshotPreflightAdapter(
6
+ MOONSHOT_CN_MODELS_URL,
7
+ 8_000,
8
+ "moonshotai-cn-preflight",
9
+ "moonshotai-cn",
10
+ );
11
+
12
+ export function createMoonshotaiCnPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
13
+ return createMoonshotPreflightAdapter(
14
+ MOONSHOT_CN_MODELS_URL,
15
+ requestTimeoutMs,
16
+ "moonshotai-cn-preflight",
17
+ "moonshotai-cn",
18
+ );
19
+ }
20
+
21
+ const moonshotaiCnPreflightExtension = definePreflightExtension({
22
+ id: "moonshotai-cn-preflight",
23
+ providerId: "moonshotai-cn",
24
+ create: ({ statusRequestTimeoutMs }) => createMoonshotaiCnPreflightAdapter(statusRequestTimeoutMs),
25
+ });
26
+
27
+ export default moonshotaiCnPreflightExtension;
@@ -0,0 +1,37 @@
1
+ import type { PreflightAdapter } from "@hyav/pi-provider";
2
+ import { definePreflightExtension } from "@hyav/pi-provider";
3
+ import { createCatalogPreflightAdapter } from "../core/catalog-preflight.ts";
4
+
5
+ export const MOONSHOT_MODELS_URL = "https://api.moonshot.ai/v1/models";
6
+ export const MOONSHOT_CN_MODELS_URL = "https://api.moonshot.cn/v1/models";
7
+
8
+ export function createMoonshotPreflightAdapter(
9
+ modelsUrl: string,
10
+ requestTimeoutMs: number,
11
+ id: "moonshotai-preflight" | "moonshotai-cn-preflight" = "moonshotai-preflight",
12
+ providerId: "moonshotai" | "moonshotai-cn" = "moonshotai",
13
+ ): PreflightAdapter {
14
+ return createCatalogPreflightAdapter(
15
+ {
16
+ id,
17
+ providerId,
18
+ name: providerId === "moonshotai-cn" ? "Moonshot CN (Kimi)" : "Moonshot (Kimi)",
19
+ modelsUrl,
20
+ },
21
+ requestTimeoutMs,
22
+ );
23
+ }
24
+
25
+ export const moonshotaiPreflightAdapter = createMoonshotPreflightAdapter(MOONSHOT_MODELS_URL, 8_000);
26
+
27
+ export function createMoonshotaiPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
28
+ return { ...moonshotaiPreflightAdapter, requestTimeoutMs };
29
+ }
30
+
31
+ const moonshotaiPreflightExtension = definePreflightExtension({
32
+ id: "moonshotai-preflight",
33
+ providerId: "moonshotai",
34
+ create: ({ statusRequestTimeoutMs }) => createMoonshotPreflightAdapter(MOONSHOT_MODELS_URL, statusRequestTimeoutMs),
35
+ });
36
+
37
+ export default moonshotaiPreflightExtension;
@@ -0,0 +1,27 @@
1
+ import type { PreflightAdapter } from "@hyav/pi-provider";
2
+ import { definePreflightExtension } from "@hyav/pi-provider";
3
+ import { createCatalogPreflightAdapter } from "../core/catalog-preflight.ts";
4
+
5
+ export const NVIDIA_MODELS_URL = "https://integrate.api.nvidia.com/v1/models";
6
+
7
+ export const nvidiaPreflightAdapter: PreflightAdapter = createCatalogPreflightAdapter(
8
+ {
9
+ id: "nvidia-preflight",
10
+ providerId: "nvidia",
11
+ name: "NVIDIA NIM",
12
+ modelsUrl: NVIDIA_MODELS_URL,
13
+ },
14
+ 8_000,
15
+ );
16
+
17
+ export function createNvidiaPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
18
+ return { ...nvidiaPreflightAdapter, requestTimeoutMs };
19
+ }
20
+
21
+ const nvidiaPreflightExtension = definePreflightExtension({
22
+ id: "nvidia-preflight",
23
+ providerId: "nvidia",
24
+ create: ({ statusRequestTimeoutMs }) => createNvidiaPreflightAdapter(statusRequestTimeoutMs),
25
+ });
26
+
27
+ export default nvidiaPreflightExtension;
@@ -1,5 +1,5 @@
1
1
  import type { PreflightAdapter } from "@hyav/pi-provider";
2
- import { definePreflightExtension, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
2
+ import { definePreflightExtension, hasBaseUrlOrigin, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
3
3
  import { extractCodexAccountId } from "../status/openai-codex.ts";
4
4
 
5
5
  export const CODEX_MODELS_URL = "https://chatgpt.com/backend-api/codex/models";
@@ -28,6 +28,7 @@ export const openAICodexPreflightAdapter: PreflightAdapter = {
28
28
  name: "OpenAI Codex",
29
29
  cacheTtlMs: 30_000,
30
30
  requestTimeoutMs: 8_000,
31
+ supportsModel: (model) => hasBaseUrlOrigin(model.baseUrl, CODEX_MODELS_URL),
31
32
  async fetch(context) {
32
33
  const apiKey = await context.getApiKey();
33
34
  if (!apiKey || apiKey === "proxy-managed") {
@@ -0,0 +1,27 @@
1
+ import type { PreflightAdapter } from "@hyav/pi-provider";
2
+ import { definePreflightExtension } from "@hyav/pi-provider";
3
+ import { createCatalogPreflightAdapter } from "../core/catalog-preflight.ts";
4
+
5
+ export const OPENAI_MODELS_URL = "https://api.openai.com/v1/models";
6
+
7
+ export const openAIPreflightAdapter: PreflightAdapter = createCatalogPreflightAdapter(
8
+ {
9
+ id: "openai-preflight",
10
+ providerId: "openai",
11
+ name: "OpenAI",
12
+ modelsUrl: OPENAI_MODELS_URL,
13
+ },
14
+ 8_000,
15
+ );
16
+
17
+ export function createOpenAIPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
18
+ return { ...openAIPreflightAdapter, requestTimeoutMs };
19
+ }
20
+
21
+ const openAIPreflightExtension = definePreflightExtension({
22
+ id: "openai-preflight",
23
+ providerId: "openai",
24
+ create: ({ statusRequestTimeoutMs }) => createOpenAIPreflightAdapter(statusRequestTimeoutMs),
25
+ });
26
+
27
+ export default openAIPreflightExtension;
@@ -0,0 +1,112 @@
1
+ import type { PreflightAdapter } from "@hyav/pi-provider";
2
+ import { definePreflightExtension, hasBaseUrlOrigin, ProviderDataError, parseRetryAfter } from "@hyav/pi-provider";
3
+ import { OPENROUTER_KEY_URL } from "../status/openrouter.ts";
4
+
5
+ export const OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
6
+
7
+ function isRecord(value: unknown): value is Record<string, unknown> {
8
+ return value !== null && typeof value === "object" && !Array.isArray(value);
9
+ }
10
+
11
+ async function collectModelIds(context: Parameters<PreflightAdapter["fetch"]>[0]): Promise<Set<string>> {
12
+ const response = await context.fetch(OPENROUTER_MODELS_URL, {
13
+ headers: {
14
+ Accept: "application/json",
15
+ "Accept-Encoding": "identity",
16
+ "User-Agent": "@hyav/pi-provider",
17
+ },
18
+ signal: context.signal,
19
+ });
20
+ if (!response.ok) {
21
+ throw new ProviderDataError(
22
+ `OpenRouter preflight failed: HTTP ${response.status}`,
23
+ `http${response.status}`,
24
+ parseRetryAfter(response.headers.get("retry-after"), context.now()),
25
+ response.status,
26
+ );
27
+ }
28
+ let payload: unknown;
29
+ try {
30
+ payload = await response.json();
31
+ } catch {
32
+ throw new ProviderDataError("OpenRouter preflight returned invalid JSON", "badjson");
33
+ }
34
+ if (!isRecord(payload) || !Array.isArray(payload.data)) {
35
+ throw new ProviderDataError("OpenRouter preflight returned invalid catalog data", "badjson");
36
+ }
37
+ return new Set(
38
+ payload.data
39
+ .filter(isRecord)
40
+ .map((model) => (typeof model.id === "string" ? model.id.trim() : undefined))
41
+ .filter((id): id is string => id !== undefined && id !== ""),
42
+ );
43
+ }
44
+
45
+ async function checkCredential(context: Parameters<PreflightAdapter["fetch"]>[0], apiKey: string): Promise<number> {
46
+ const response = await context.fetch(OPENROUTER_KEY_URL, {
47
+ headers: {
48
+ Accept: "application/json",
49
+ "Accept-Encoding": "identity",
50
+ Authorization: `Bearer ${apiKey}`,
51
+ "User-Agent": "@hyav/pi-provider",
52
+ },
53
+ signal: context.signal,
54
+ });
55
+ if (response.status === 404) return response.status;
56
+ if (!response.ok) {
57
+ throw new ProviderDataError(
58
+ `OpenRouter preflight failed: HTTP ${response.status}`,
59
+ `http${response.status}`,
60
+ parseRetryAfter(response.headers.get("retry-after"), context.now()),
61
+ response.status,
62
+ );
63
+ }
64
+ let payload: unknown;
65
+ try {
66
+ payload = await response.json();
67
+ } catch {
68
+ throw new ProviderDataError("OpenRouter preflight returned invalid JSON", "badjson");
69
+ }
70
+ if (!isRecord(payload) || !isRecord(payload.data) || typeof payload.data.label !== "string") {
71
+ throw new ProviderDataError("OpenRouter preflight returned an invalid key response", "badjson");
72
+ }
73
+ return response.status;
74
+ }
75
+
76
+ export const openRouterPreflightAdapter: PreflightAdapter = {
77
+ id: "openrouter-preflight",
78
+ providerId: "openrouter",
79
+ name: "OpenRouter",
80
+ cacheTtlMs: 30_000,
81
+ requestTimeoutMs: 8_000,
82
+ supportsModel: (model) => hasBaseUrlOrigin(model.baseUrl, OPENROUTER_MODELS_URL),
83
+ async fetch(context) {
84
+ const modelIds = await collectModelIds(context);
85
+ const apiKey = await context.getApiKey();
86
+ const checks: string[] = ["endpoint", "catalog"];
87
+ if (!apiKey || apiKey === "proxy-managed") {
88
+ return { passed: modelIds.has(context.model.id), checks: [...checks, "auth"], updatedAt: context.now() };
89
+ }
90
+ // Management keys use /api/v1/credits as the only key endpoint and get 404 here.
91
+ const authStatus = await checkCredential(context, apiKey);
92
+ const authPassed = authStatus !== 404;
93
+ checks.push("auth");
94
+ return {
95
+ passed: modelIds.has(context.model.id) && authPassed,
96
+ checks,
97
+ updatedAt: context.now(),
98
+ };
99
+ },
100
+ };
101
+
102
+ export function createOpenRouterPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
103
+ return { ...openRouterPreflightAdapter, requestTimeoutMs };
104
+ }
105
+
106
+ const openRouterPreflightExtension = definePreflightExtension({
107
+ id: "openrouter-preflight",
108
+ providerId: "openrouter",
109
+ create: ({ statusRequestTimeoutMs }) => createOpenRouterPreflightAdapter(statusRequestTimeoutMs),
110
+ });
111
+
112
+ export default openRouterPreflightExtension;