@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/host.ts
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
type AdapterRegistrationEnvelope,
|
|
4
|
+
isAdapterRegistrationEnvelope,
|
|
5
|
+
isHostClaimRequest,
|
|
6
|
+
PROVIDER_KIT_ADAPTER_EVENT,
|
|
7
|
+
PROVIDER_KIT_HOST_CLAIM_EVENT,
|
|
8
|
+
PROVIDER_KIT_STARTUP_BRIDGE_EVENT,
|
|
9
|
+
type StartupBridge,
|
|
10
|
+
type StartupBridgeRequest,
|
|
11
|
+
} from "./adapter-protocol.ts";
|
|
12
|
+
import { validateAdapter, validateAdapterIdentity, validateProviderAdapter } from "./adapter-validation.ts";
|
|
13
|
+
import { type ProviderKitDefinition, validateProviderKitDefinition } from "./definition.ts";
|
|
14
|
+
import {
|
|
15
|
+
getStatusModeCompletions,
|
|
16
|
+
installProviderKitRuntime,
|
|
17
|
+
type ProviderKitRuntimeController,
|
|
18
|
+
prepareProviderRegistration,
|
|
19
|
+
} from "./extension.ts";
|
|
20
|
+
import { fetchOfficialPricing, type OfficialModelMeta, OPENROUTER_MODELS_URL } from "./official-pricing.ts";
|
|
21
|
+
import type { PreflightAdapter } from "./preflight-manager.ts";
|
|
22
|
+
import { refreshProviderRegistrations } from "./provider-registration.ts";
|
|
23
|
+
import { scheduleModelCatalogRefresh } from "./runtime.ts";
|
|
24
|
+
import type { ProviderKitDependencies } from "./runtime-config.ts";
|
|
25
|
+
import { resolveProviderKitDependencies } from "./runtime-config.ts";
|
|
26
|
+
import type { ProviderAdapter, ProviderModelDraft, StatusAdapter, TunerAdapter } from "./types.ts";
|
|
27
|
+
|
|
28
|
+
function compareAdapterIds(left: { id: string }, right: { id: string }): number {
|
|
29
|
+
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function adapterKindLabel(kind: AdapterRegistrationEnvelope["kind"]): string {
|
|
33
|
+
return `${kind} adapter`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function warnAdapterIssue(message: string): void {
|
|
37
|
+
console.warn(`[provider-kit] ${message}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create the single Provider Kit Host used by the published Pi entrypoint.
|
|
42
|
+
*
|
|
43
|
+
* The Host deliberately does not assemble adapters during extension factory
|
|
44
|
+
* loading. Adapter factories can run before or after this factory, so the
|
|
45
|
+
* event-bus envelopes are collected and assembled at the first session-level
|
|
46
|
+
* operation after Pi's session_start registration barrier.
|
|
47
|
+
*/
|
|
48
|
+
export function createProviderKitHost(dependencies: Partial<ProviderKitDependencies> = {}): (pi: ExtensionAPI) => void {
|
|
49
|
+
const runtime = resolveProviderKitDependencies(dependencies);
|
|
50
|
+
return (pi) => {
|
|
51
|
+
const hostToken = {};
|
|
52
|
+
const hostClaim = { token: hostToken, occupied: false };
|
|
53
|
+
const unsubscribeHostClaim = pi.events.on(PROVIDER_KIT_HOST_CLAIM_EVENT, (value) => {
|
|
54
|
+
if (!isHostClaimRequest(value) || value.token === hostToken) return;
|
|
55
|
+
value.occupied = true;
|
|
56
|
+
});
|
|
57
|
+
pi.events.emit(PROVIDER_KIT_HOST_CLAIM_EVENT, hostClaim);
|
|
58
|
+
if (hostClaim.occupied) {
|
|
59
|
+
unsubscribeHostClaim();
|
|
60
|
+
warnAdapterIssue("ignored a second Provider Kit Host; only one Host is supported per Pi runtime");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const registrations = new Map<object, AdapterRegistrationEnvelope>();
|
|
65
|
+
let active: ProviderKitRuntimeController | undefined;
|
|
66
|
+
let readyPromise: Promise<ProviderKitRuntimeController | undefined> | undefined;
|
|
67
|
+
let disposed = false;
|
|
68
|
+
let lifecycleGeneration = 0;
|
|
69
|
+
let latestBackgroundPricing: Record<string, OfficialModelMeta> | undefined;
|
|
70
|
+
let installedDefinition:
|
|
71
|
+
| {
|
|
72
|
+
generation: number;
|
|
73
|
+
definition: ProviderKitDefinition;
|
|
74
|
+
providerDrafts: Map<ProviderAdapter, ProviderModelDraft[]>;
|
|
75
|
+
}
|
|
76
|
+
| undefined;
|
|
77
|
+
|
|
78
|
+
const onBackgroundRefresh = (snapshot: Record<string, OfficialModelMeta>): void => {
|
|
79
|
+
latestBackgroundPricing = snapshot;
|
|
80
|
+
if (disposed || installedDefinition === undefined || installedDefinition.generation !== lifecycleGeneration)
|
|
81
|
+
return;
|
|
82
|
+
active?.updateOfficialPricing?.(snapshot);
|
|
83
|
+
// Re-register from the adapter's current registration state. A dynamic
|
|
84
|
+
// refreshModels() may have replaced the startup drafts since assembly.
|
|
85
|
+
refreshProviderRegistrations(pi, installedDefinition.definition.providers, runtime, snapshot);
|
|
86
|
+
};
|
|
87
|
+
const officialPricing = runtime.enableOfficialPricingFallback
|
|
88
|
+
? fetchOfficialPricingForHost(runtime, onBackgroundRefresh)
|
|
89
|
+
: Promise.resolve({});
|
|
90
|
+
const bridge: StartupBridge = { dependencies: runtime, officialPricing };
|
|
91
|
+
|
|
92
|
+
const invalidateRuntime = (): void => {
|
|
93
|
+
lifecycleGeneration++;
|
|
94
|
+
installedDefinition = undefined;
|
|
95
|
+
active?.shutdown();
|
|
96
|
+
active = undefined;
|
|
97
|
+
readyPromise = undefined;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const unsubscribeBridge = pi.events.on(PROVIDER_KIT_STARTUP_BRIDGE_EVENT, (value) => {
|
|
101
|
+
if (value === null || typeof value !== "object") return;
|
|
102
|
+
const request = value as StartupBridgeRequest;
|
|
103
|
+
request.bridge ??= bridge;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const receiveRegistration = (value: unknown): void => {
|
|
107
|
+
if (!isAdapterRegistrationEnvelope(value)) {
|
|
108
|
+
warnAdapterIssue("ignored a malformed registration envelope");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
if (value.kind === "provider") {
|
|
113
|
+
validateAdapter("provider", value.adapter);
|
|
114
|
+
validateAdapterIdentity("provider", value.id, value.adapter);
|
|
115
|
+
} else if (value.kind === "status") {
|
|
116
|
+
validateAdapter("status", value.adapter);
|
|
117
|
+
validateAdapterIdentity("status", value.id, value.providerId, value.adapter);
|
|
118
|
+
} else if (value.kind === "preflight") {
|
|
119
|
+
validateAdapter("preflight", value.adapter);
|
|
120
|
+
validateAdapterIdentity("preflight", value.id, value.providerId, value.adapter);
|
|
121
|
+
} else {
|
|
122
|
+
validateAdapter("tuner", value.adapter);
|
|
123
|
+
validateAdapterIdentity("tuner", value.id, value.adapter);
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
warnAdapterIssue(
|
|
127
|
+
`ignored invalid ${adapterKindLabel(value.kind)} ${JSON.stringify(value.id)}: ${
|
|
128
|
+
error instanceof Error ? error.message : String(error)
|
|
129
|
+
}`,
|
|
130
|
+
);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const previous = registrations.get(value.token);
|
|
135
|
+
registrations.set(value.token, value);
|
|
136
|
+
if (previous === undefined) invalidateRuntime();
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const unsubscribeRegistrations = pi.events.on(PROVIDER_KIT_ADAPTER_EVENT, receiveRegistration);
|
|
140
|
+
|
|
141
|
+
const groupedWithoutConflicts = <T>(items: T[], key: (item: T) => string, label: string): T[] => {
|
|
142
|
+
const groups = new Map<string, T[]>();
|
|
143
|
+
for (const item of items) {
|
|
144
|
+
const group = groups.get(key(item));
|
|
145
|
+
if (group) group.push(item);
|
|
146
|
+
else groups.set(key(item), [item]);
|
|
147
|
+
}
|
|
148
|
+
const result: T[] = [];
|
|
149
|
+
for (const [id, group] of groups) {
|
|
150
|
+
if (group.length > 1) {
|
|
151
|
+
warnAdapterIssue(`excluded ${group.length} colliding ${label} entries for ${JSON.stringify(id)}`);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
result.push(group[0]!);
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const nativeProviderExists = (ctx: ExtensionContext | undefined, providerId: string): boolean => {
|
|
160
|
+
if (!ctx || typeof ctx.modelRegistry.getProvider !== "function") return false;
|
|
161
|
+
try {
|
|
162
|
+
return ctx.modelRegistry.getProvider(providerId) !== undefined;
|
|
163
|
+
} catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const materializeAdapter = async (entry: AdapterRegistrationEnvelope): Promise<unknown> => {
|
|
169
|
+
if (entry.startupDependencies === runtime) return entry.adapter;
|
|
170
|
+
return entry.factory({ ...runtime, pi });
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const buildDefinition = async (
|
|
174
|
+
ctx?: ExtensionContext,
|
|
175
|
+
): Promise<{
|
|
176
|
+
definition: ProviderKitDefinition;
|
|
177
|
+
providerDrafts: Map<ProviderAdapter, ProviderModelDraft[]>;
|
|
178
|
+
pricing: Record<string, OfficialModelMeta>;
|
|
179
|
+
}> => {
|
|
180
|
+
const envelopes = [...registrations.values()];
|
|
181
|
+
const providerEnvelopes = groupedWithoutConflicts(
|
|
182
|
+
envelopes.filter(
|
|
183
|
+
(entry): entry is Extract<AdapterRegistrationEnvelope, { kind: "provider" }> =>
|
|
184
|
+
entry.kind === "provider",
|
|
185
|
+
),
|
|
186
|
+
(entry) => entry.id,
|
|
187
|
+
"provider",
|
|
188
|
+
);
|
|
189
|
+
const pricingPromise = officialPricing.catch(() => ({}));
|
|
190
|
+
const providerResultsPromise = Promise.all(
|
|
191
|
+
providerEnvelopes.map(async (entry) => {
|
|
192
|
+
try {
|
|
193
|
+
const adapter = (await materializeAdapter(entry)) as ProviderAdapter;
|
|
194
|
+
validateProviderAdapter(adapter);
|
|
195
|
+
validateAdapterIdentity("provider", entry.id, adapter);
|
|
196
|
+
return { entry, adapter };
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return { entry, error };
|
|
199
|
+
}
|
|
200
|
+
}),
|
|
201
|
+
);
|
|
202
|
+
const [pricing, providerResults] = await Promise.all([pricingPromise, providerResultsPromise]);
|
|
203
|
+
const providers: ProviderAdapter[] = [];
|
|
204
|
+
const providerDrafts = new Map<ProviderAdapter, ProviderModelDraft[]>();
|
|
205
|
+
for (const result of providerResults) {
|
|
206
|
+
if (!("adapter" in result) || result.adapter === undefined) {
|
|
207
|
+
warnAdapterIssue(
|
|
208
|
+
`excluded provider ${JSON.stringify(result.entry.id)}: ${
|
|
209
|
+
result.error instanceof Error ? result.error.message : String(result.error)
|
|
210
|
+
}`,
|
|
211
|
+
);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const adapter = result.adapter;
|
|
215
|
+
try {
|
|
216
|
+
const modelDrafts =
|
|
217
|
+
result.entry.adapter.registration?.modelDrafts ?? result.entry.modelDrafts ?? adapter.provider.models;
|
|
218
|
+
prepareProviderRegistration(adapter, runtime, pricing, modelDrafts);
|
|
219
|
+
providerDrafts.set(adapter, modelDrafts);
|
|
220
|
+
providers.push(adapter);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
warnAdapterIssue(
|
|
223
|
+
`excluded provider ${JSON.stringify(result.entry.id)}: ${
|
|
224
|
+
error instanceof Error ? error.message : String(error)
|
|
225
|
+
}`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
providers.sort(compareAdapterIds);
|
|
230
|
+
const providerIds = new Set(providers.map(({ id }) => id));
|
|
231
|
+
const dynamicProviderIds = new Set(
|
|
232
|
+
envelopes
|
|
233
|
+
.filter(
|
|
234
|
+
(entry): entry is Extract<AdapterRegistrationEnvelope, { kind: "provider" }> =>
|
|
235
|
+
entry.kind === "provider",
|
|
236
|
+
)
|
|
237
|
+
.map(({ id }) => id),
|
|
238
|
+
);
|
|
239
|
+
const resolvesProvider = (providerId: string): boolean =>
|
|
240
|
+
providerIds.has(providerId) ||
|
|
241
|
+
(!dynamicProviderIds.has(providerId) && nativeProviderExists(ctx, providerId));
|
|
242
|
+
|
|
243
|
+
const statusEntries = groupedWithoutConflicts(
|
|
244
|
+
envelopes.filter(
|
|
245
|
+
(entry): entry is Extract<AdapterRegistrationEnvelope, { kind: "status" }> => entry.kind === "status",
|
|
246
|
+
),
|
|
247
|
+
(entry) => entry.id,
|
|
248
|
+
"status ID",
|
|
249
|
+
);
|
|
250
|
+
const preflightEntries = groupedWithoutConflicts(
|
|
251
|
+
envelopes.filter(
|
|
252
|
+
(entry): entry is Extract<AdapterRegistrationEnvelope, { kind: "preflight" }> =>
|
|
253
|
+
entry.kind === "preflight",
|
|
254
|
+
),
|
|
255
|
+
(entry) => entry.id,
|
|
256
|
+
"preflight ID",
|
|
257
|
+
);
|
|
258
|
+
const tunerEntries = groupedWithoutConflicts(
|
|
259
|
+
envelopes.filter(
|
|
260
|
+
(entry): entry is Extract<AdapterRegistrationEnvelope, { kind: "tuner" }> => entry.kind === "tuner",
|
|
261
|
+
),
|
|
262
|
+
(entry) => entry.id,
|
|
263
|
+
"tuner",
|
|
264
|
+
);
|
|
265
|
+
const statusResultsPromise = Promise.all(
|
|
266
|
+
statusEntries
|
|
267
|
+
.filter((item) => resolvesProvider(item.providerId))
|
|
268
|
+
.map(async (entry) => {
|
|
269
|
+
try {
|
|
270
|
+
const adapter = (await materializeAdapter(entry)) as StatusAdapter;
|
|
271
|
+
validateAdapter("status", adapter);
|
|
272
|
+
validateAdapterIdentity("status", entry.id, entry.providerId, adapter);
|
|
273
|
+
return { entry, adapter };
|
|
274
|
+
} catch (error) {
|
|
275
|
+
return { entry, error };
|
|
276
|
+
}
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
279
|
+
const preflightResultsPromise = Promise.all(
|
|
280
|
+
preflightEntries
|
|
281
|
+
.filter((item) => resolvesProvider(item.providerId))
|
|
282
|
+
.map(async (entry) => {
|
|
283
|
+
try {
|
|
284
|
+
const adapter = (await materializeAdapter(entry)) as PreflightAdapter;
|
|
285
|
+
validateAdapter("preflight", adapter);
|
|
286
|
+
validateAdapterIdentity("preflight", entry.id, entry.providerId, adapter);
|
|
287
|
+
return { entry, adapter };
|
|
288
|
+
} catch (error) {
|
|
289
|
+
return { entry, error };
|
|
290
|
+
}
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
293
|
+
const tunerResultsPromise = Promise.all(
|
|
294
|
+
tunerEntries.map(async (entry) => {
|
|
295
|
+
try {
|
|
296
|
+
const adapter = (await materializeAdapter(entry)) as TunerAdapter;
|
|
297
|
+
validateAdapter("tuner", adapter);
|
|
298
|
+
validateAdapterIdentity("tuner", entry.id, adapter);
|
|
299
|
+
return { entry, adapter };
|
|
300
|
+
} catch (error) {
|
|
301
|
+
return { entry, error };
|
|
302
|
+
}
|
|
303
|
+
}),
|
|
304
|
+
);
|
|
305
|
+
const [statusResults, preflightResults, tunerResults] = await Promise.all([
|
|
306
|
+
statusResultsPromise,
|
|
307
|
+
preflightResultsPromise,
|
|
308
|
+
tunerResultsPromise,
|
|
309
|
+
]);
|
|
310
|
+
|
|
311
|
+
const statuses: StatusAdapter[] = [];
|
|
312
|
+
for (const result of statusResults) {
|
|
313
|
+
if ("adapter" in result && result.adapter !== undefined) statuses.push(result.adapter);
|
|
314
|
+
else {
|
|
315
|
+
warnAdapterIssue(
|
|
316
|
+
`excluded status ${JSON.stringify(result.entry.id)}: ${
|
|
317
|
+
result.error instanceof Error ? result.error.message : String(result.error)
|
|
318
|
+
}`,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const statusBindings = groupedWithoutConflicts(statuses, (entry) => entry.providerId, "status binding").sort(
|
|
323
|
+
compareAdapterIds,
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const preflights: PreflightAdapter[] = [];
|
|
327
|
+
for (const result of preflightResults) {
|
|
328
|
+
if ("adapter" in result && result.adapter !== undefined) preflights.push(result.adapter);
|
|
329
|
+
else {
|
|
330
|
+
warnAdapterIssue(
|
|
331
|
+
`excluded preflight ${JSON.stringify(result.entry.id)}: ${
|
|
332
|
+
result.error instanceof Error ? result.error.message : String(result.error)
|
|
333
|
+
}`,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const preflightBindings = groupedWithoutConflicts(
|
|
338
|
+
preflights,
|
|
339
|
+
(entry) => entry.providerId,
|
|
340
|
+
"preflight binding",
|
|
341
|
+
).sort(compareAdapterIds);
|
|
342
|
+
|
|
343
|
+
const tuners: TunerAdapter[] = [];
|
|
344
|
+
for (const result of tunerResults) {
|
|
345
|
+
if ("adapter" in result && result.adapter !== undefined) tuners.push(result.adapter);
|
|
346
|
+
else {
|
|
347
|
+
warnAdapterIssue(
|
|
348
|
+
`excluded tuner ${JSON.stringify(result.entry.id)}: ${
|
|
349
|
+
result.error instanceof Error ? result.error.message : String(result.error)
|
|
350
|
+
}`,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const definition = {
|
|
356
|
+
providers,
|
|
357
|
+
statuses: statusBindings,
|
|
358
|
+
preflights: preflightBindings,
|
|
359
|
+
tuners: tuners.sort(compareAdapterIds),
|
|
360
|
+
};
|
|
361
|
+
validateProviderKitDefinition(definition);
|
|
362
|
+
return { definition, providerDrafts, pricing };
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const ensureReady = (ctx?: ExtensionContext): Promise<ProviderKitRuntimeController | undefined> => {
|
|
366
|
+
if (active) return Promise.resolve(active);
|
|
367
|
+
if (readyPromise) return readyPromise;
|
|
368
|
+
const generation = lifecycleGeneration;
|
|
369
|
+
const pending = (async (): Promise<ProviderKitRuntimeController | undefined> => {
|
|
370
|
+
if (disposed || generation !== lifecycleGeneration) return undefined;
|
|
371
|
+
const { definition, providerDrafts, pricing } = await buildDefinition(ctx);
|
|
372
|
+
if (disposed || generation !== lifecycleGeneration) return undefined;
|
|
373
|
+
const providerIds = new Set(
|
|
374
|
+
[...registrations.values()]
|
|
375
|
+
.filter(
|
|
376
|
+
(entry): entry is Extract<AdapterRegistrationEnvelope, { kind: "provider" }> =>
|
|
377
|
+
entry.kind === "provider",
|
|
378
|
+
)
|
|
379
|
+
.map((entry) => entry.id),
|
|
380
|
+
);
|
|
381
|
+
if (typeof pi.unregisterProvider === "function") {
|
|
382
|
+
for (const providerId of providerIds) pi.unregisterProvider(providerId);
|
|
383
|
+
}
|
|
384
|
+
if (disposed || generation !== lifecycleGeneration) return undefined;
|
|
385
|
+
const controller = installProviderKitRuntime(pi, runtime, definition, pricing, {
|
|
386
|
+
registerHandlers: false,
|
|
387
|
+
providerDrafts,
|
|
388
|
+
});
|
|
389
|
+
if (disposed || generation !== lifecycleGeneration) {
|
|
390
|
+
controller.shutdown();
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
393
|
+
active = controller;
|
|
394
|
+
installedDefinition = { generation, definition, providerDrafts };
|
|
395
|
+
if (latestBackgroundPricing !== undefined) onBackgroundRefresh(latestBackgroundPricing);
|
|
396
|
+
return controller;
|
|
397
|
+
})();
|
|
398
|
+
readyPromise = pending.catch((error) => {
|
|
399
|
+
if (generation !== lifecycleGeneration || disposed) return undefined;
|
|
400
|
+
readyPromise = undefined;
|
|
401
|
+
warnAdapterIssue(
|
|
402
|
+
`failed to assemble the Host registry: ${error instanceof Error ? error.message : String(error)}`,
|
|
403
|
+
);
|
|
404
|
+
return undefined;
|
|
405
|
+
});
|
|
406
|
+
return readyPromise;
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
pi.on("input", (_event, ctx) => {
|
|
410
|
+
active?.clearStatusPresentation(ctx);
|
|
411
|
+
});
|
|
412
|
+
pi.on("session_start", (event, ctx) => {
|
|
413
|
+
invalidateRuntime();
|
|
414
|
+
scheduleModelCatalogRefresh(ctx, event.reason);
|
|
415
|
+
});
|
|
416
|
+
pi.on("before_provider_request", async (event, ctx) => {
|
|
417
|
+
const controller = await ensureReady(ctx);
|
|
418
|
+
if (!controller || !ctx.model) return;
|
|
419
|
+
return controller.applyTunerPayload(event.payload, ctx.model);
|
|
420
|
+
});
|
|
421
|
+
pi.on("model_select", (event, ctx) => {
|
|
422
|
+
void ensureReady(ctx).then((controller) => {
|
|
423
|
+
if (controller) controller.handleModelSelect(event.model, ctx);
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
pi.on("session_shutdown", () => {
|
|
427
|
+
disposed = true;
|
|
428
|
+
invalidateRuntime();
|
|
429
|
+
unsubscribeHostClaim();
|
|
430
|
+
unsubscribeBridge();
|
|
431
|
+
unsubscribeRegistrations();
|
|
432
|
+
});
|
|
433
|
+
pi.registerCommand("status", {
|
|
434
|
+
description: "Show status and diagnostics for the active provider",
|
|
435
|
+
getArgumentCompletions: getStatusModeCompletions,
|
|
436
|
+
handler: async (args, ctx) => {
|
|
437
|
+
const controller = await ensureReady(ctx);
|
|
438
|
+
if (controller) await controller.handleStatusCommand(args, ctx);
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function fetchOfficialPricingForHost(
|
|
445
|
+
runtime: ProviderKitDependencies,
|
|
446
|
+
onBackgroundRefresh?: (snapshot: Record<string, OfficialModelMeta>) => void,
|
|
447
|
+
) {
|
|
448
|
+
return fetchOfficialPricing(
|
|
449
|
+
runtime.fetch,
|
|
450
|
+
runtime.officialPricingUrl,
|
|
451
|
+
runtime.officialPricingTimeoutMs,
|
|
452
|
+
runtime.officialPricingCacheTtlMs,
|
|
453
|
+
runtime.officialPricingMaxStaleMs,
|
|
454
|
+
runtime.now,
|
|
455
|
+
{
|
|
456
|
+
cachePath:
|
|
457
|
+
runtime.officialPricingUrl === OPENROUTER_MODELS_URL ? runtime.openRouterMetadataCachePath : undefined,
|
|
458
|
+
background: runtime.officialPricingUrl === OPENROUTER_MODELS_URL,
|
|
459
|
+
onBackgroundRefresh,
|
|
460
|
+
},
|
|
461
|
+
);
|
|
462
|
+
}
|