@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.
- package/CHANGELOG.md +11 -0
- package/CONTRIBUTING.md +63 -0
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/README.zh-CN.md +61 -0
- package/SECURITY.md +36 -0
- package/SUPPORT.md +25 -0
- package/core/adapter-extensions.ts +175 -0
- package/core/adapter-protocol.ts +120 -0
- package/core/adapter-validation.ts +241 -0
- package/core/deadline.ts +78 -0
- package/core/definition.ts +64 -0
- package/core/errors.ts +38 -0
- package/core/extension.ts +20 -0
- package/core/host.ts +462 -0
- package/core/live-check-manager.ts +263 -0
- package/core/official-pricing.ts +881 -0
- package/core/opencode-preflight.ts +66 -0
- package/core/preflight-manager.ts +251 -0
- package/core/pricing-adjustments.ts +118 -0
- package/core/provider-registration.ts +261 -0
- package/core/retry-after.ts +24 -0
- package/core/runtime-config.ts +95 -0
- package/core/runtime.ts +473 -0
- package/core/status-manager.ts +332 -0
- package/core/status-report.ts +592 -0
- package/core/tuner-manager.ts +34 -0
- package/core/types.ts +175 -0
- package/index.ts +108 -0
- package/package.json +81 -0
- package/preflight/charm-hyper.ts +62 -0
- package/preflight/deepseek.ts +73 -0
- package/preflight/google.ts +89 -0
- package/preflight/openai-codex.ts +88 -0
- package/preflight/opencode-go.ts +27 -0
- package/preflight/opencode.ts +27 -0
- package/providers/charm-hyper/constants.ts +31 -0
- package/providers/charm-hyper/oauth.ts +360 -0
- package/providers/charm-hyper.ts +536 -0
- package/status/charm-hyper.ts +76 -0
- package/status/deepseek.ts +102 -0
- package/status/openai-codex.ts +224 -0
- package/status/opencode-go.ts +133 -0
package/core/types.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionAPI,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
ProviderConfig,
|
|
5
|
+
ProviderModelConfig,
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
|
+
|
|
8
|
+
export type ThinkingLevel = ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
9
|
+
|
|
10
|
+
export type ProviderCost = ProviderModelConfig["cost"];
|
|
11
|
+
export type ProviderModel = ProviderModelConfig;
|
|
12
|
+
export type PricingSku = "input" | "output" | "cacheRead" | "cacheWrite";
|
|
13
|
+
/** Pricing provenance used by Provider Kit sidecars; not added to Pi model objects. */
|
|
14
|
+
export type ProviderPricingSource = "provider" | "fallback" | "official";
|
|
15
|
+
export type ModelPricingSource = ProviderPricingSource | "native";
|
|
16
|
+
export type ModelFieldSource = ProviderPricingSource | "native" | "default";
|
|
17
|
+
export type ModelMetadataState = "fresh" | "stale" | "checking" | "unavailable";
|
|
18
|
+
|
|
19
|
+
export interface ModelMetadataStatus {
|
|
20
|
+
state: ModelMetadataState;
|
|
21
|
+
updatedAt?: number;
|
|
22
|
+
source?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ModelFieldSources {
|
|
26
|
+
contextWindow?: ModelFieldSource;
|
|
27
|
+
maxTokens?: ModelFieldSource;
|
|
28
|
+
input?: ModelFieldSource;
|
|
29
|
+
reasoning?: ModelFieldSource;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ProviderPricingAdjustment {
|
|
33
|
+
/** 0.8 means a 20% discount; values above 1 represent a markup. */
|
|
34
|
+
multiplier: number;
|
|
35
|
+
label: string;
|
|
36
|
+
source?: string;
|
|
37
|
+
/** Whether this explicit adjustment may be applied to a reference price. */
|
|
38
|
+
appliesToReference?: boolean;
|
|
39
|
+
appliesTo?: readonly PricingSku[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ProviderPricingPolicy {
|
|
43
|
+
defaultAdjustment?: ProviderPricingAdjustment;
|
|
44
|
+
models?: Record<string, ProviderPricingAdjustment>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ModelPricingDetails {
|
|
48
|
+
known: boolean;
|
|
49
|
+
source: ModelPricingSource | "none";
|
|
50
|
+
baseCost?: ProviderCost;
|
|
51
|
+
effectiveCost?: ProviderCost;
|
|
52
|
+
adjustment?: ProviderPricingAdjustment;
|
|
53
|
+
note?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ModelQualityScore {
|
|
57
|
+
source: string;
|
|
58
|
+
benchmark: string;
|
|
59
|
+
category: string;
|
|
60
|
+
metric: "elo" | "rating" | "score" | "ips";
|
|
61
|
+
value: number;
|
|
62
|
+
rank?: number;
|
|
63
|
+
winRate?: number;
|
|
64
|
+
confidenceInterval?: {
|
|
65
|
+
lower: number;
|
|
66
|
+
upper: number;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ProviderModelMetadata {
|
|
71
|
+
pricing: ModelPricingDetails;
|
|
72
|
+
fieldSources?: ModelFieldSources;
|
|
73
|
+
quality?: ModelQualityScore[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type ProviderModelDraft = Partial<ProviderModel> &
|
|
77
|
+
Pick<ProviderModel, "id"> & {
|
|
78
|
+
pricingSource?: ProviderPricingSource;
|
|
79
|
+
pricingAdjustment?: ProviderPricingAdjustment;
|
|
80
|
+
};
|
|
81
|
+
export type ProviderRefreshContext = Parameters<NonNullable<ProviderConfig["refreshModels"]>>[0];
|
|
82
|
+
export type ActiveModel = NonNullable<ExtensionContext["model"]>;
|
|
83
|
+
export type TunerContext = Pick<ExtensionContext, "model">;
|
|
84
|
+
|
|
85
|
+
export type ProviderDefinition = Omit<ProviderConfig, "models" | "refreshModels"> & {
|
|
86
|
+
name: string;
|
|
87
|
+
baseUrl: string;
|
|
88
|
+
apiKey: string;
|
|
89
|
+
api: ProviderConfig["api"];
|
|
90
|
+
headers?: ProviderConfig["headers"];
|
|
91
|
+
oauth?: ProviderConfig["oauth"];
|
|
92
|
+
models: ProviderModelDraft[];
|
|
93
|
+
refreshModels?: (context: ProviderRefreshContext) => Promise<ProviderModelDraft[]>;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type ModelCatalogSource = "static" | "live" | "fallback";
|
|
97
|
+
|
|
98
|
+
export interface ModelCatalogStatus {
|
|
99
|
+
source: ModelCatalogSource;
|
|
100
|
+
modelCount: number;
|
|
101
|
+
updatedAt?: number;
|
|
102
|
+
lastError?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface StatusTextEntry {
|
|
106
|
+
kind: "text";
|
|
107
|
+
id: string;
|
|
108
|
+
label: string;
|
|
109
|
+
value: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface StatusAmountEntry {
|
|
113
|
+
kind: "amount";
|
|
114
|
+
id: string;
|
|
115
|
+
label: string;
|
|
116
|
+
value: number;
|
|
117
|
+
unit: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface StatusWindowEntry {
|
|
121
|
+
kind: "window";
|
|
122
|
+
id: string;
|
|
123
|
+
label: string;
|
|
124
|
+
remainingPercent: number;
|
|
125
|
+
resetAt?: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type StatusEntry = StatusTextEntry | StatusAmountEntry | StatusWindowEntry;
|
|
129
|
+
|
|
130
|
+
export interface StatusSnapshot {
|
|
131
|
+
entries: StatusEntry[];
|
|
132
|
+
updatedAt: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface StatusContext {
|
|
136
|
+
fetch: typeof globalThis.fetch;
|
|
137
|
+
getApiKey: () => Promise<string | undefined>;
|
|
138
|
+
/** Optional non-secret credential metadata for provider-specific account labels. */
|
|
139
|
+
getCredentialMetadata?: () => unknown;
|
|
140
|
+
signal?: AbortSignal;
|
|
141
|
+
now: () => number;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface StatusAdapter {
|
|
145
|
+
id: string;
|
|
146
|
+
providerId: string;
|
|
147
|
+
name: string;
|
|
148
|
+
cacheTtlMs: number;
|
|
149
|
+
requestTimeoutMs: number;
|
|
150
|
+
fetch(context: StatusContext): Promise<StatusSnapshot>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface TunerAdapter {
|
|
154
|
+
id: string;
|
|
155
|
+
/** Lower priorities run first. Ties use deterministic Adapter ID order. */
|
|
156
|
+
priority?: number;
|
|
157
|
+
matches(context: TunerContext, payload: unknown): boolean;
|
|
158
|
+
transform(payload: unknown, context: TunerContext): unknown | undefined | Promise<unknown | undefined>;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface ProviderAdapter {
|
|
162
|
+
id: string;
|
|
163
|
+
provider: ProviderDefinition;
|
|
164
|
+
/** Optional explicit price adjustments owned by this Provider. */
|
|
165
|
+
pricing?: ProviderPricingPolicy;
|
|
166
|
+
catalog?: ModelCatalogStatus;
|
|
167
|
+
/** @internal Draft state shared across isolated Adapter and Host contexts. */
|
|
168
|
+
registration?: {
|
|
169
|
+
modelDrafts: ProviderModelDraft[];
|
|
170
|
+
normalizedModels?: ProviderModel[];
|
|
171
|
+
modelMetadata?: Record<string, ProviderModelMetadata>;
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export type PiApi = ExtensionAPI;
|
package/index.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createProviderKitHost } from "./core/host.ts";
|
|
2
|
+
|
|
3
|
+
export type {
|
|
4
|
+
AdapterExtensionContext,
|
|
5
|
+
PreflightExtensionDefinition,
|
|
6
|
+
ProviderExtensionDefinition,
|
|
7
|
+
StatusExtensionDefinition,
|
|
8
|
+
TunerExtensionDefinition,
|
|
9
|
+
} from "./core/adapter-extensions.ts";
|
|
10
|
+
export {
|
|
11
|
+
definePreflightExtension,
|
|
12
|
+
defineProviderExtension,
|
|
13
|
+
defineStatusExtension,
|
|
14
|
+
defineTunerExtension,
|
|
15
|
+
} from "./core/adapter-extensions.ts";
|
|
16
|
+
export type { ProviderDataErrorLike } from "./core/errors.ts";
|
|
17
|
+
export { isProviderDataError, ProviderDataError } from "./core/errors.ts";
|
|
18
|
+
export type {
|
|
19
|
+
ProviderKitDefinition,
|
|
20
|
+
ProviderKitDependencies,
|
|
21
|
+
ProviderKitLoader,
|
|
22
|
+
ProviderKitRuntimeController,
|
|
23
|
+
} from "./core/extension.ts";
|
|
24
|
+
export {
|
|
25
|
+
createProviderKitRuntime,
|
|
26
|
+
getDefaultProviderKitDependencies,
|
|
27
|
+
installProviderKitRuntime,
|
|
28
|
+
prepareProviderRegistration,
|
|
29
|
+
registerProviderAdapter,
|
|
30
|
+
resolveProviderKitDependencies,
|
|
31
|
+
validateProviderKitDefinition,
|
|
32
|
+
validateProviderKitDependencies,
|
|
33
|
+
} from "./core/extension.ts";
|
|
34
|
+
export { createProviderKitHost } from "./core/host.ts";
|
|
35
|
+
export type {
|
|
36
|
+
LiveCheckContextLike,
|
|
37
|
+
LiveCheckDiagnostics,
|
|
38
|
+
LiveCheckErrorState,
|
|
39
|
+
LiveCheckResult,
|
|
40
|
+
LiveCheckSnapshot,
|
|
41
|
+
} from "./core/live-check-manager.ts";
|
|
42
|
+
export { getLiveCheckKey, LIVE_CHECK_SCOPE, LiveCheckManager } from "./core/live-check-manager.ts";
|
|
43
|
+
export {
|
|
44
|
+
applyOfficialModelCosts,
|
|
45
|
+
clearPricingCache,
|
|
46
|
+
fetchOfficialPricing,
|
|
47
|
+
findOfficialCost,
|
|
48
|
+
findOfficialMeta,
|
|
49
|
+
getDefaultOpenRouterMetadataCachePath,
|
|
50
|
+
getPricingCache,
|
|
51
|
+
getPricingCacheAge,
|
|
52
|
+
type OfficialModelMeta,
|
|
53
|
+
type OfficialPricingFetchOptions,
|
|
54
|
+
OPENROUTER_MODELS_URL,
|
|
55
|
+
parseOpenRouterModels,
|
|
56
|
+
parseOpenRouterPricing,
|
|
57
|
+
setPricingCache,
|
|
58
|
+
} from "./core/official-pricing.ts";
|
|
59
|
+
export type {
|
|
60
|
+
PreflightAdapter,
|
|
61
|
+
PreflightContext,
|
|
62
|
+
PreflightContextLike,
|
|
63
|
+
PreflightDiagnostics,
|
|
64
|
+
PreflightErrorState,
|
|
65
|
+
PreflightModel,
|
|
66
|
+
PreflightSnapshot,
|
|
67
|
+
} from "./core/preflight-manager.ts";
|
|
68
|
+
export { getPreflightKey, normalizePreflightSnapshot, PreflightManager } from "./core/preflight-manager.ts";
|
|
69
|
+
export { applyPricingAdjustment, resolvePricingDetails } from "./core/pricing-adjustments.ts";
|
|
70
|
+
export type { StatusDiagnostics, StatusErrorState } from "./core/status-manager.ts";
|
|
71
|
+
export { normalizeStatusSnapshot, StatusManager } from "./core/status-manager.ts";
|
|
72
|
+
export { applyTunerAdapters, sortTunerAdapters } from "./core/tuner-manager.ts";
|
|
73
|
+
export type {
|
|
74
|
+
ActiveModel,
|
|
75
|
+
ModelCatalogSource,
|
|
76
|
+
ModelCatalogStatus,
|
|
77
|
+
ModelFieldSource,
|
|
78
|
+
ModelFieldSources,
|
|
79
|
+
ModelMetadataState,
|
|
80
|
+
ModelMetadataStatus,
|
|
81
|
+
ModelPricingDetails,
|
|
82
|
+
ModelPricingSource,
|
|
83
|
+
ModelQualityScore,
|
|
84
|
+
PiApi,
|
|
85
|
+
PricingSku,
|
|
86
|
+
ProviderAdapter,
|
|
87
|
+
ProviderCost,
|
|
88
|
+
ProviderDefinition,
|
|
89
|
+
ProviderModel,
|
|
90
|
+
ProviderModelDraft,
|
|
91
|
+
ProviderModelMetadata,
|
|
92
|
+
ProviderPricingAdjustment,
|
|
93
|
+
ProviderPricingPolicy,
|
|
94
|
+
ProviderPricingSource,
|
|
95
|
+
ProviderRefreshContext,
|
|
96
|
+
StatusAdapter,
|
|
97
|
+
StatusAmountEntry,
|
|
98
|
+
StatusContext,
|
|
99
|
+
StatusEntry,
|
|
100
|
+
StatusSnapshot,
|
|
101
|
+
StatusTextEntry,
|
|
102
|
+
StatusWindowEntry,
|
|
103
|
+
ThinkingLevel,
|
|
104
|
+
TunerAdapter,
|
|
105
|
+
TunerContext,
|
|
106
|
+
} from "./core/types.ts";
|
|
107
|
+
|
|
108
|
+
export default createProviderKitHost();
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hyav/pi-provider",
|
|
3
|
+
"version": "0.1.0-oidc-bootstrap.0",
|
|
4
|
+
"description": "Provider extension toolkit for Pi to integrate and manage custom LLM providers with dynamic models, request tuners, and account status.",
|
|
5
|
+
"author": "hyav",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/hyav/pi-provider.git"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/hyav/pi-provider#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/hyav/pi-provider/issues"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./index.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./index.ts",
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"pi-package",
|
|
26
|
+
"pi",
|
|
27
|
+
"provider",
|
|
28
|
+
"provider-kit",
|
|
29
|
+
"charm-hyper",
|
|
30
|
+
"llm"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"test": "tsx --test test/**/*.test.ts",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"check": "biome check . && tsc --noEmit",
|
|
36
|
+
"audit:runtime": "npm audit --omit=dev --audit-level=high",
|
|
37
|
+
"audit:all": "npm audit --audit-level=high",
|
|
38
|
+
"artifact:check": "node scripts/check-package.mjs",
|
|
39
|
+
"release:check": "node scripts/check-release.mjs",
|
|
40
|
+
"prepublishOnly": "npm run audit:runtime && npm run audit:all && npm run release:check && npm run check && npm test && npm run artifact:check"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=22.19.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
47
|
+
"@earendil-works/pi-tui": "*"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@biomejs/biome": "2.3.5",
|
|
51
|
+
"@earendil-works/pi-coding-agent": "0.84.1",
|
|
52
|
+
"@earendil-works/pi-tui": "0.84.1",
|
|
53
|
+
"@types/node": "22.20.1",
|
|
54
|
+
"tsx": "4.23.11",
|
|
55
|
+
"typescript": "5.9.3"
|
|
56
|
+
},
|
|
57
|
+
"pi": {
|
|
58
|
+
"extensions": [
|
|
59
|
+
"./index.ts",
|
|
60
|
+
"./providers/*.ts",
|
|
61
|
+
"./status/*.ts",
|
|
62
|
+
"./preflight/*.ts",
|
|
63
|
+
"./tuners/*.ts"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
"files": [
|
|
67
|
+
"index.ts",
|
|
68
|
+
"core",
|
|
69
|
+
"providers",
|
|
70
|
+
"preflight",
|
|
71
|
+
"status",
|
|
72
|
+
"tuners",
|
|
73
|
+
"README.md",
|
|
74
|
+
"README.zh-CN.md",
|
|
75
|
+
"CHANGELOG.md",
|
|
76
|
+
"CONTRIBUTING.md",
|
|
77
|
+
"SECURITY.md",
|
|
78
|
+
"SUPPORT.md",
|
|
79
|
+
"LICENSE"
|
|
80
|
+
]
|
|
81
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { definePreflightExtension } from "../core/adapter-extensions.ts";
|
|
2
|
+
import { ProviderDataError } from "../core/errors.ts";
|
|
3
|
+
import type { PreflightAdapter } from "../core/preflight-manager.ts";
|
|
4
|
+
import { parseRetryAfter } from "../core/retry-after.ts";
|
|
5
|
+
import { hyperJsonHeaders } from "../providers/charm-hyper/constants.ts";
|
|
6
|
+
import { HYPER_MODELS_URL, HYPER_PROVIDER_URL, parseHyperModels } from "../providers/charm-hyper.ts";
|
|
7
|
+
|
|
8
|
+
export function createCharmHyperPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
|
|
9
|
+
return {
|
|
10
|
+
id: "charm-hyper-preflight",
|
|
11
|
+
providerId: "charm-hyper",
|
|
12
|
+
name: "Charm Hyper",
|
|
13
|
+
cacheTtlMs: 30_000,
|
|
14
|
+
requestTimeoutMs,
|
|
15
|
+
async fetch(context) {
|
|
16
|
+
const apiKey = await context.getApiKey();
|
|
17
|
+
if (!apiKey) return { passed: false, checks: ["auth"], updatedAt: context.now() };
|
|
18
|
+
const headers = new Headers(hyperJsonHeaders());
|
|
19
|
+
if (apiKey !== "proxy-managed") headers.set("Authorization", `Bearer ${apiKey}`);
|
|
20
|
+
let endpoint = HYPER_PROVIDER_URL;
|
|
21
|
+
let response = await context.fetch(endpoint, { headers, signal: context.signal });
|
|
22
|
+
if (response.status === 404) {
|
|
23
|
+
endpoint = HYPER_MODELS_URL;
|
|
24
|
+
response = await context.fetch(endpoint, { headers, signal: context.signal });
|
|
25
|
+
}
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw new ProviderDataError(
|
|
28
|
+
`Charm Hyper preflight failed: HTTP ${response.status}`,
|
|
29
|
+
`http${response.status}`,
|
|
30
|
+
parseRetryAfter(response.headers.get("retry-after"), context.now()),
|
|
31
|
+
response.status,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
let payload: unknown;
|
|
35
|
+
try {
|
|
36
|
+
payload = await response.json();
|
|
37
|
+
} catch {
|
|
38
|
+
throw new ProviderDataError(`Charm Hyper preflight returned invalid JSON from ${endpoint}`, "badjson");
|
|
39
|
+
}
|
|
40
|
+
const models = parseHyperModels(payload);
|
|
41
|
+
if (models.length === 0) {
|
|
42
|
+
throw new ProviderDataError("Charm Hyper preflight returned invalid catalog data", "badjson");
|
|
43
|
+
}
|
|
44
|
+
const modelIds = new Set(models.map(({ id }) => id.toLowerCase()));
|
|
45
|
+
const modelMatched = modelIds.has(context.model.id.toLowerCase());
|
|
46
|
+
return {
|
|
47
|
+
passed: modelMatched,
|
|
48
|
+
checks: ["endpoint", "auth", "catalog"],
|
|
49
|
+
updatedAt: context.now(),
|
|
50
|
+
httpStatus: response.status,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const charmHyperPreflightExtension = definePreflightExtension({
|
|
57
|
+
id: "charm-hyper-preflight",
|
|
58
|
+
providerId: "charm-hyper",
|
|
59
|
+
create: ({ statusRequestTimeoutMs }) => createCharmHyperPreflightAdapter(statusRequestTimeoutMs),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export default charmHyperPreflightExtension;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { definePreflightExtension } from "../core/adapter-extensions.ts";
|
|
2
|
+
import { ProviderDataError } from "../core/errors.ts";
|
|
3
|
+
import type { PreflightAdapter } from "../core/preflight-manager.ts";
|
|
4
|
+
import { parseRetryAfter } from "../core/retry-after.ts";
|
|
5
|
+
|
|
6
|
+
export const DEEPSEEK_MODELS_URL = "https://api.deepseek.com/models";
|
|
7
|
+
|
|
8
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
9
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const deepSeekPreflightAdapter: PreflightAdapter = {
|
|
13
|
+
id: "deepseek-preflight",
|
|
14
|
+
providerId: "deepseek",
|
|
15
|
+
name: "DeepSeek",
|
|
16
|
+
cacheTtlMs: 30_000,
|
|
17
|
+
requestTimeoutMs: 8_000,
|
|
18
|
+
async fetch(context) {
|
|
19
|
+
const apiKey = await context.getApiKey();
|
|
20
|
+
if (!apiKey || apiKey === "proxy-managed") {
|
|
21
|
+
return { passed: false, checks: ["auth"], updatedAt: context.now() };
|
|
22
|
+
}
|
|
23
|
+
const response = await context.fetch(DEEPSEEK_MODELS_URL, {
|
|
24
|
+
headers: {
|
|
25
|
+
Accept: "application/json",
|
|
26
|
+
"Accept-Encoding": "identity",
|
|
27
|
+
Authorization: `Bearer ${apiKey}`,
|
|
28
|
+
},
|
|
29
|
+
signal: context.signal,
|
|
30
|
+
});
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new ProviderDataError(
|
|
33
|
+
`DeepSeek preflight failed: HTTP ${response.status}`,
|
|
34
|
+
`http${response.status}`,
|
|
35
|
+
parseRetryAfter(response.headers.get("retry-after"), context.now()),
|
|
36
|
+
response.status,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
let payload: unknown;
|
|
40
|
+
try {
|
|
41
|
+
payload = await response.json();
|
|
42
|
+
} catch {
|
|
43
|
+
throw new ProviderDataError("DeepSeek preflight returned invalid JSON", "badjson");
|
|
44
|
+
}
|
|
45
|
+
if (!isRecord(payload) || !Array.isArray(payload.data)) {
|
|
46
|
+
throw new ProviderDataError("DeepSeek preflight returned invalid catalog data", "badjson");
|
|
47
|
+
}
|
|
48
|
+
const modelIds = new Set(
|
|
49
|
+
payload.data
|
|
50
|
+
.filter(isRecord)
|
|
51
|
+
.map((model) => (typeof model.id === "string" ? model.id.trim() : undefined))
|
|
52
|
+
.filter((id): id is string => id !== undefined && id !== ""),
|
|
53
|
+
);
|
|
54
|
+
return {
|
|
55
|
+
passed: modelIds.has(context.model.id),
|
|
56
|
+
checks: ["endpoint", "auth", "catalog"],
|
|
57
|
+
updatedAt: context.now(),
|
|
58
|
+
httpStatus: response.status,
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export function createDeepSeekPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
|
|
64
|
+
return { ...deepSeekPreflightAdapter, requestTimeoutMs };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const deepSeekPreflightExtension = definePreflightExtension({
|
|
68
|
+
id: "deepseek-preflight",
|
|
69
|
+
providerId: "deepseek",
|
|
70
|
+
create: ({ statusRequestTimeoutMs }) => createDeepSeekPreflightAdapter(statusRequestTimeoutMs),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export default deepSeekPreflightExtension;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { definePreflightExtension } from "../core/adapter-extensions.ts";
|
|
2
|
+
import { ProviderDataError } from "../core/errors.ts";
|
|
3
|
+
import type { PreflightAdapter } from "../core/preflight-manager.ts";
|
|
4
|
+
import { parseRetryAfter } from "../core/retry-after.ts";
|
|
5
|
+
|
|
6
|
+
export const GOOGLE_MODELS_URL = "https://generativelanguage.googleapis.com/v1beta/models";
|
|
7
|
+
|
|
8
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
9
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isStringArray(value: unknown): value is string[] {
|
|
13
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function catalogModelIds(value: Record<string, unknown>): string[] {
|
|
17
|
+
const ids: string[] = [];
|
|
18
|
+
if (typeof value.baseModelId === "string" && value.baseModelId.trim() !== "") {
|
|
19
|
+
ids.push(value.baseModelId.trim());
|
|
20
|
+
}
|
|
21
|
+
if (typeof value.name === "string" && value.name.trim() !== "") {
|
|
22
|
+
ids.push(value.name.trim().replace(/^models\//, ""));
|
|
23
|
+
}
|
|
24
|
+
return ids;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const googlePreflightAdapter: PreflightAdapter = {
|
|
28
|
+
id: "google-preflight",
|
|
29
|
+
providerId: "google",
|
|
30
|
+
name: "Google Gemini",
|
|
31
|
+
cacheTtlMs: 30_000,
|
|
32
|
+
requestTimeoutMs: 8_000,
|
|
33
|
+
async fetch(context) {
|
|
34
|
+
const apiKey = await context.getApiKey();
|
|
35
|
+
if (!apiKey || apiKey === "proxy-managed") {
|
|
36
|
+
return { passed: false, checks: ["auth"], updatedAt: context.now() };
|
|
37
|
+
}
|
|
38
|
+
const response = await context.fetch(GOOGLE_MODELS_URL, {
|
|
39
|
+
headers: {
|
|
40
|
+
Accept: "application/json",
|
|
41
|
+
"Accept-Encoding": "identity",
|
|
42
|
+
"x-goog-api-key": apiKey,
|
|
43
|
+
},
|
|
44
|
+
signal: context.signal,
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new ProviderDataError(
|
|
48
|
+
`Google preflight failed: HTTP ${response.status}`,
|
|
49
|
+
`http${response.status}`,
|
|
50
|
+
parseRetryAfter(response.headers.get("retry-after"), context.now()),
|
|
51
|
+
response.status,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
let payload: unknown;
|
|
55
|
+
try {
|
|
56
|
+
payload = await response.json();
|
|
57
|
+
} catch {
|
|
58
|
+
throw new ProviderDataError("Google preflight returned invalid JSON", "badjson");
|
|
59
|
+
}
|
|
60
|
+
if (!isRecord(payload) || !Array.isArray(payload.models)) {
|
|
61
|
+
throw new ProviderDataError("Google preflight returned invalid catalog data", "badjson");
|
|
62
|
+
}
|
|
63
|
+
const modelIds = new Set(
|
|
64
|
+
payload.models.flatMap((value) => {
|
|
65
|
+
if (!isRecord(value)) return [];
|
|
66
|
+
const methods = value.supportedGenerationMethods;
|
|
67
|
+
return isStringArray(methods) && methods.includes("generateContent") ? catalogModelIds(value) : [];
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
return {
|
|
71
|
+
passed: modelIds.has(context.model.id),
|
|
72
|
+
checks: ["endpoint", "auth", "catalog"],
|
|
73
|
+
updatedAt: context.now(),
|
|
74
|
+
httpStatus: response.status,
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export function createGooglePreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
|
|
80
|
+
return { ...googlePreflightAdapter, requestTimeoutMs };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const googlePreflightExtension = definePreflightExtension({
|
|
84
|
+
id: "google-preflight",
|
|
85
|
+
providerId: "google",
|
|
86
|
+
create: ({ statusRequestTimeoutMs }) => createGooglePreflightAdapter(statusRequestTimeoutMs),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export default googlePreflightExtension;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { definePreflightExtension } from "../core/adapter-extensions.ts";
|
|
2
|
+
import { ProviderDataError } from "../core/errors.ts";
|
|
3
|
+
import type { PreflightAdapter } from "../core/preflight-manager.ts";
|
|
4
|
+
import { parseRetryAfter } from "../core/retry-after.ts";
|
|
5
|
+
import { extractCodexAccountId } from "../status/openai-codex.ts";
|
|
6
|
+
|
|
7
|
+
export const CODEX_MODELS_URL = "https://chatgpt.com/backend-api/codex/models";
|
|
8
|
+
export const CODEX_MODELS_CLIENT_VERSION = "0.144.1";
|
|
9
|
+
|
|
10
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
11
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function codexModelIds(payload: unknown): Set<string> {
|
|
15
|
+
if (!isRecord(payload) || !Array.isArray(payload.models)) {
|
|
16
|
+
throw new ProviderDataError("OpenAI Codex preflight returned invalid catalog data", "badjson");
|
|
17
|
+
}
|
|
18
|
+
return new Set(
|
|
19
|
+
payload.models
|
|
20
|
+
.filter(isRecord)
|
|
21
|
+
.filter((model) => model.visibility === "list" && model.supported_in_api !== false)
|
|
22
|
+
.map((model) => (typeof model.slug === "string" ? model.slug.trim() : undefined))
|
|
23
|
+
.filter((id): id is string => id !== undefined && id !== ""),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const openAICodexPreflightAdapter: PreflightAdapter = {
|
|
28
|
+
id: "openai-codex-preflight",
|
|
29
|
+
providerId: "openai-codex",
|
|
30
|
+
name: "OpenAI Codex",
|
|
31
|
+
cacheTtlMs: 30_000,
|
|
32
|
+
requestTimeoutMs: 8_000,
|
|
33
|
+
async fetch(context) {
|
|
34
|
+
const apiKey = await context.getApiKey();
|
|
35
|
+
if (!apiKey || apiKey === "proxy-managed") {
|
|
36
|
+
return { passed: false, checks: ["auth"], updatedAt: context.now() };
|
|
37
|
+
}
|
|
38
|
+
const accountId = extractCodexAccountId(apiKey);
|
|
39
|
+
if (!accountId) {
|
|
40
|
+
throw new ProviderDataError("OpenAI Codex OAuth token has no account ID", "auth");
|
|
41
|
+
}
|
|
42
|
+
const url = `${CODEX_MODELS_URL}?client_version=${encodeURIComponent(CODEX_MODELS_CLIENT_VERSION)}`;
|
|
43
|
+
const response = await context.fetch(url, {
|
|
44
|
+
headers: {
|
|
45
|
+
Accept: "application/json",
|
|
46
|
+
"Accept-Encoding": "identity",
|
|
47
|
+
Authorization: `Bearer ${apiKey}`,
|
|
48
|
+
"chatgpt-account-id": accountId,
|
|
49
|
+
originator: "pi",
|
|
50
|
+
"User-Agent": "@hyav/pi-provider",
|
|
51
|
+
},
|
|
52
|
+
signal: context.signal,
|
|
53
|
+
});
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw new ProviderDataError(
|
|
56
|
+
`OpenAI Codex preflight failed: HTTP ${response.status}`,
|
|
57
|
+
`http${response.status}`,
|
|
58
|
+
parseRetryAfter(response.headers.get("retry-after"), context.now()),
|
|
59
|
+
response.status,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
let payload: unknown;
|
|
63
|
+
try {
|
|
64
|
+
payload = await response.json();
|
|
65
|
+
} catch {
|
|
66
|
+
throw new ProviderDataError("OpenAI Codex preflight returned invalid JSON", "badjson");
|
|
67
|
+
}
|
|
68
|
+
const modelIds = codexModelIds(payload);
|
|
69
|
+
return {
|
|
70
|
+
passed: modelIds.has(context.model.id),
|
|
71
|
+
checks: ["endpoint", "auth", "catalog"],
|
|
72
|
+
updatedAt: context.now(),
|
|
73
|
+
httpStatus: response.status,
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export function createOpenAICodexPreflightAdapter(requestTimeoutMs: number): PreflightAdapter {
|
|
79
|
+
return { ...openAICodexPreflightAdapter, requestTimeoutMs };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const openAICodexPreflightExtension = definePreflightExtension({
|
|
83
|
+
id: "openai-codex-preflight",
|
|
84
|
+
providerId: "openai-codex",
|
|
85
|
+
create: ({ statusRequestTimeoutMs }) => createOpenAICodexPreflightAdapter(statusRequestTimeoutMs),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export default openAICodexPreflightExtension;
|