import type { Api, AssistantMessage, AssistantMessageEventStream, AuthResult, Context, Model, ModelsApiStreamOptions, ModelsRefreshOptions, ModelsRefreshResult, ModelsSimpleStreamOptions, Provider, ProviderHeaders, } from "@earendil-works/pi-ai"; import type { ModelRuntime } from "./model-runtime.ts"; import type { AuthStatus, ProviderConfigInput } from "./provider-composer.ts"; export type { ProviderConfigInput } from "./provider-composer.ts"; export type ResolvedRequestAuth = | { ok: true; apiKey?: string; headers?: ProviderHeaders; baseUrl?: string; env?: Record; } | { ok: false; error: string }; export { clearApiKeyCache } from "./provider-composer.ts"; /** * Synchronous compatibility facade exposed to extensions. * Coding-agent internals use ModelRuntime directly. */ export class ModelRegistry { private readonly runtime: ModelRuntime; constructor(runtime: ModelRuntime) { this.runtime = runtime; } /** Reload models.json asynchronously. Await before making synchronous registry reads. */ refresh(options?: ModelsRefreshOptions): Promise { return this.runtime.refresh(options); } getError(): string | undefined { return this.runtime.getError(); } getAll(): Model[] { return [...this.runtime.getModels()]; } getAvailable(): Model[] { return [...this.runtime.getAvailableSnapshot()]; } find(provider: string, modelId: string): Model | undefined { return this.runtime.getModel(provider, modelId); } hasConfiguredAuth(model: Model): boolean { return this.runtime.hasConfiguredAuth(model.provider); } async getApiKeyAndHeaders(model: Model): Promise { try { const resolution = await this.runtime.getAuth(model); if (!resolution) { const compatibility = this.runtime.getCompatibilityRequestConfig(model); if (compatibility.authHeader) { return { ok: false, error: `No API key found for "${model.provider}"` }; } return { ok: true, headers: compatibility.headers }; } return { ok: true, apiKey: resolution.auth.apiKey, headers: resolution.auth.headers, ...(resolution.auth.baseUrl ? { baseUrl: resolution.auth.baseUrl } : {}), env: resolution.env, }; } catch (error) { const cause = error instanceof Error ? error.cause : undefined; const message = cause instanceof Error ? cause.message : error instanceof Error ? error.message : String(error); return { ok: false, error: message === "authHeader requires a resolved API key" ? `No API key found for "${model.provider}"` : message, }; } } getProviderAuthStatus(provider: string): AuthStatus { return this.runtime.getProviderAuthStatus(provider); } getProvider(provider: string): Provider | undefined { return this.runtime.getProvider(provider); } /** Stream through the configured provider with request-time authentication. */ stream( model: Model, context: Context, options?: ModelsApiStreamOptions, ): AssistantMessageEventStream { return this.runtime.stream(model, context, options); } /** Stream with provider-neutral options and request-time authentication. */ streamSimple(model: Model, context: Context, options?: ModelsSimpleStreamOptions): AssistantMessageEventStream { return this.runtime.streamSimple(model, context, options); } complete( model: Model, context: Context, options?: ModelsApiStreamOptions, ): Promise { return this.runtime.complete(model, context, options); } getProviderDisplayName(provider: string): string { return this.runtime.getProvider(provider)?.name ?? provider; } getProviderAuth(provider: string): Promise { return this.runtime.getAuth(provider); } async getApiKeyForProvider(provider: string): Promise { try { return (await this.runtime.getAuth(provider))?.auth.apiKey; } catch { return undefined; } } isUsingOAuth(model: Model): boolean { return this.runtime.isUsingOAuth(model.provider); } registerProvider(provider: Provider): void; registerProvider(providerName: string, config: ProviderConfigInput): void; registerProvider(providerOrName: Provider | string, config?: ProviderConfigInput): void { if (typeof providerOrName === "string") { if (!config) throw new Error("Provider config is required when registering by name"); this.runtime.registerProvider(providerOrName, config); return; } this.runtime.registerNativeProvider(providerOrName); } unregisterProvider(providerName: string): void { this.runtime.unregisterProvider(providerName); } getRegisteredProviderConfig(providerName: string): ProviderConfigInput | undefined { return this.runtime.getRegisteredProviderConfig(providerName); } getRegisteredNativeProvider(providerName: string): Provider | undefined { return this.runtime.getRegisteredNativeProvider(providerName); } getRegisteredProviderIds(): readonly string[] { return this.runtime.getRegisteredProviderIds(); } }