import { Api, AssistantMessage, AuthResult, Credential, Model, Provider, ProviderAuth, ProviderHeaders, StreamOptions, TranscriptContext } from "@earendil-works/pi-ai"; import { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; //#region src/types.d.ts type AuthKind = 'api-key' | 'oauth' | 'service-account' | 'custom'; type SelectionPolicy = 'round-robin' | 'weighted-round-robin' | 'least-inflight' | 'priority'; type FailureKind = 'rate-limit' | 'quota' | 'auth' | 'transient' | 'fatal'; type SelectionBias = 'first-account' | 'none'; interface ProviderAttemptFailure { message: string; status?: number; headers?: Readonly>; assistantMessage?: AssistantMessage; cause?: unknown; outputStarted: boolean; } interface ProviderAccount { id: string; label: string; authKind: AuthKind; credentialRef: TCredentialRef; enabled?: boolean; weight?: number; priority?: number; metadata?: Readonly>; } interface FailureDisposition { kind: FailureKind; retryable: boolean; cooldownMs?: number; } interface ProviderRegistration { id: string; label: string; accounts: () => readonly ProviderAccount[] | Promise[]>; classifyFailure?: (failure: ProviderAttemptFailure, account: ProviderAccount) => FailureDisposition | undefined; managementHint?: string; selectionBias?: SelectionBias; } interface AccountPreference { accountId: string; enabled: boolean; weight: number; priority: number; } interface PoolPreference { providerId: string; policy: SelectionPolicy; affinity: boolean; accounts: AccountPreference[]; } interface AffinityPin { accountId: string; explicit: boolean; } interface AcquireOptions { providerId: string; affinityKey?: string; excludeAccountIds?: Iterable; } interface LeaseOutcomeSuccess { status: 'success'; } interface LeaseOutcomeFailure { status: 'failure'; error: ProviderAttemptFailure; } interface LeaseOutcomeCancelled { status: 'cancelled'; } type LeaseOutcome = LeaseOutcomeSuccess | LeaseOutcomeFailure | LeaseOutcomeCancelled; interface AccountLease { readonly id: string; readonly providerId: string; readonly accountId: string; readonly account: ProviderAccount; readonly credentialRef: TCredentialRef; readonly acquiredAt: number; release(outcome?: LeaseOutcome): FailureDisposition | undefined; } type PublicAccountStatus = 'ready' | 'cooldown' | 'disabled'; interface PublicAccountSnapshot { id: string; label: string; authKind: AuthKind; enabled: boolean; weight: number; priority: number; status: PublicAccountStatus; inFlight: number; consecutiveFailures: number; cooldownUntil?: number; lastSelectedAt?: number; lastFailureKind?: FailureKind; metadata: Readonly>; } interface PublicPoolSnapshot { id: string; label: string; policy: SelectionPolicy; affinity: boolean; firstAccountBias: boolean; managementHint?: string; accounts: PublicAccountSnapshot[]; } interface MultiProviderSnapshot { providers: PublicPoolSnapshot[]; } /** * Fired when a pooled account is abandoned after its final tolerated error * and the stream is about to move to another account. Returning true tells * the stream to surface the buffered error instead of rotating accounts * inline — an external handler (e.g. compact-then-retry) will re-enter the * pool with fresh context. */ interface FailoverInfo { providerId: string; fromAccountId: string; failure: ProviderAttemptFailure; errorsOnAccount: number; } interface SchedulerSettings { rateLimitCooldownMs?: number; quotaCooldownMs?: number; authCooldownMs?: number; transientBaseCooldownMs?: number; maxCooldownMs?: number; errorsBeforeSwitch?: number; } type SchedulerSettingsPatch = { [K in keyof SchedulerSettings]?: SchedulerSettings[K] | undefined; }; interface VirtualModelTemplate { api: Model['api']; baseUrl: string; reasoning: boolean; thinkingLevelMap?: Model['thinkingLevelMap']; input: Model['input']; cost: Model['cost']; contextWindow: number; maxTokens: number; } interface VirtualBackend { providerId: string; modelId: string; enabled?: boolean; weight?: number; template?: VirtualModelTemplate; } interface VirtualModelConfig { id: string; label?: string; backends: VirtualBackend[]; } interface VirtualProviderConfig { id: string; label: string; models: VirtualModelConfig[]; } declare const SCHEDULER_SETTING_KEYS: readonly ["rateLimitCooldownMs", "quotaCooldownMs", "authCooldownMs", "transientBaseCooldownMs", "maxCooldownMs", "errorsBeforeSwitch"]; interface SchedulerOptions { defaultPolicy?: SelectionPolicy; affinity?: boolean; rateLimitCooldownMs?: number; quotaCooldownMs?: number; authCooldownMs?: number; transientBaseCooldownMs?: number; maxCooldownMs?: number; errorsBeforeSwitch?: number; now?: () => number; randomId?: () => string; randomInt?: (maxExclusive: number) => number; } interface AccountRequestContext { provider: Provider; model: Model; context: TranscriptContext; requestOptions: Readonly>; signal: AbortSignal; } interface AccountAttemptContext extends AccountRequestContext { account: ProviderAccount; resolution: AuthResult; } interface LiftProviderOptions { auth?: ProviderAuth; resolveAuth: (account: ProviderAccount, signal: AbortSignal, request: AccountRequestContext) => AuthResult | Promise; excludeAccountIds?: (request: AccountRequestContext) => Iterable | Promise>; sanitizeRequestOptions?: (attempt: AccountAttemptContext) => StreamOptions & Record; affinityKey?: (input: { provider: Provider; model: Model; context: TranscriptContext; }) => string | undefined; disableProviderRetries?: boolean; maxAccountAttempts?: number; onFailover?: (info: FailoverInfo) => boolean | void; } interface MultiProviderIntegration extends ProviderRegistration, LiftProviderOptions {} declare const MULTIPROVIDER_REGISTER_EVENT = "pi-multiprovider:register"; declare const MULTIPROVIDER_SERVICE_EVENT = "pi-multiprovider:service"; type MultiProviderServiceContext = Pick; interface ActiveAccount { id: string; label: string; authKind: AuthKind; } interface ActiveAccountAuth { accessToken: string; label: string; source?: string; } interface ActiveAccountChangedEvent { providerId: string; account: ActiveAccount | undefined; ctx: ExtensionContext; } interface MultiProviderServiceAnnouncement { getActiveAccount(providerId: string, ctx: MultiProviderServiceContext): Promise; resolveActiveAccountAuth(providerId: string, ctx: MultiProviderServiceContext, signal?: AbortSignal): Promise; onActiveAccountChanged(providerId: string, callback: (event: ActiveAccountChangedEvent) => void): () => void; } //#endregion //#region src/errors.d.ts declare class UnknownProviderError extends Error { readonly providerId: string; constructor(providerId: string); } declare class UnknownAccountError extends Error { readonly providerId: string; readonly accountId: string; constructor(providerId: string, accountId: string); } declare class NoAccountAvailableError extends Error { readonly providerId: string; readonly nextAvailableAt?: number | undefined; constructor(providerId: string, nextAvailableAt?: number | undefined); } //#endregion //#region src/service.d.ts declare const SCHEDULER_DEFAULTS: Required; declare class MultiProviderService { private readonly providers; private readonly preferences; private readonly runtime; private readonly selectionBias; private readonly affinity; private readonly explicitAffinity; private readonly roundRobinCursor; private readonly smoothScores; private readonly defaults; private readonly now; private readonly randomId; private readonly randomInt; constructor(options?: SchedulerOptions); registerProvider(registration: ProviderRegistration): () => void; hasProvider(providerId: string): boolean; hasEnabledAccounts(providerId: string): Promise; acquire(options: AcquireOptions): Promise>; snapshot(): Promise; updatePool(providerId: string, patch: Partial>): Promise; getPoolPreference(providerId: string): PoolPreference; getErrorsBeforeSwitch(): number; updateSchedulerDefaults(settings: SchedulerSettings): void; resetHealth(providerId: string, accountId: string): void; pinAccount(providerId: string, affinityKey: string, accountId: string): Promise; getAffinity(providerId: string, affinityKey: string): AffinityPin | undefined; clearAffinity(providerId?: string, affinityKey?: string): void; private registration; private pool; private runtimeFor; private effectiveAccounts; private select; private selectWeighted; private recordSuccess; private recordFailure; private defaultCooldown; } //#endregion //#region src/lift.d.ts declare function liftProvider(provider: Provider, service: MultiProviderService, options: LiftProviderOptions): Provider; //#endregion //#region src/virtual.d.ts declare const VIRTUAL_ID_SEPARATOR = "::"; declare const BACKEND_UNAVAILABLE_PREFIX = "multiprovider: virtual backend unavailable"; declare function virtualSchedulerId(virtualProviderId: string, modelId: string): string; declare function virtualBackendAccountId(backend: Pick): string; type AmbientAuthResolution = { ok: true; apiKey?: string; headers?: ProviderHeaders; baseUrl?: string; env?: Record; } | { ok: false; error: string; }; interface VirtualProviderDependencies { service: MultiProviderService; config: VirtualProviderConfig; getAffinityKey: () => string; getBackingProvider: (providerId: string) => Provider | undefined; resolveAmbientAuth: (providerId: string, model: Model, signal: AbortSignal) => Promise; isBackendConfigured?: (providerId: string) => boolean; maxAccountAttempts?: number; onFailover?: (info: FailoverInfo) => boolean | void; } interface VirtualIntegrationOptions { getProviderLabel?: (providerId: string) => string | undefined; maxAccountAttempts?: number; } declare function createVirtualIntegrations(config: VirtualProviderConfig, options?: VirtualIntegrationOptions): ProviderRegistration[]; declare function captureVirtualModelTemplate(model: Model): VirtualModelTemplate; declare function healVirtualTemplates(config: VirtualProviderConfig, resolveTemplate: (providerId: string, modelId: string) => VirtualModelTemplate | undefined): VirtualProviderConfig | undefined; declare function createVirtualProvider(dependencies: VirtualProviderDependencies): Provider; //#endregion //#region src/announcement.d.ts interface AnnouncementDependencies { scheduler: MultiProviderService; getIntegration(providerId: string): MultiProviderIntegration | undefined; getBaseProvider(providerId: string, ctx: MultiProviderServiceContext): Provider | undefined; affinityKeyFor(integration: MultiProviderIntegration, ctx: MultiProviderServiceContext, providerId: string): string; } interface ServiceAnnouncementHandle extends MultiProviderServiceAnnouncement { notifyActiveAccountChanged(providerId: string, ctx: ExtensionContext, account: ActiveAccount | undefined): void; } declare function createServiceAnnouncement(deps: AnnouncementDependencies): ServiceAnnouncementHandle; //#endregion //#region src/register.d.ts declare function registerMultiProvider(pi: ExtensionAPI, integration: MultiProviderIntegration): void; //#endregion //#region src/session-pins.d.ts declare const SESSION_PIN_ENTRY_TYPE = "pi-multiprovider:switch-account"; declare const SESSION_PIN_ENV = "PI_MULTIPROVIDER_SESSION_PINS"; /** One /switch-account decision: the account pinned to one session pool. */ interface SessionPin { pool: string; key: string; /** Pinned account; undefined records an explicit return to automatic selection. */ accountId?: string; /** Account label at switch time, used only to describe restore failures. */ label?: string; } /** Parent /switch-account decision rebound onto a child session. */ interface InheritedSessionPin { pool: string; accountId?: string; label?: string; } interface SessionPinHost { /** Whether the pool's scheduler is registered yet. */ hasPool(pool: string): boolean; pin(pool: string, key: string, accountId: string): void | Promise; clear(pool: string, key: string): void; } /** * Reads the session's custom entries and returns the latest decision per pool * and affinity key, in the order the pools were first decided. Entries written * by other extensions, malformed records, and superseded decisions are ignored. */ declare function sessionPinsFromEntries(entries: Iterable): SessionPin[]; /** * Replays recorded decisions into the scheduler. Pools whose scheduler is not * registered yet are returned so the caller can retry after the next * reconcile; stale records — a removed or disabled account — are reported to * onError and dropped so they never block a later pin. Decisions that reached * the scheduler are reported to onApplied, which is how a resume tells * followers of the session's active account that it changed. */ declare function applySessionPins(pins: readonly SessionPin[], host: SessionPinHost, onError?: (pin: SessionPin, error: unknown) => void, onApplied?: (pin: SessionPin) => void): Promise; /** Latest inherited pin per pool. Foreign, malformed, and empty records are dropped. */ declare function inheritedSessionPinsFromUnknown(value: unknown): InheritedSessionPin[]; declare function inheritedSessionPinsFromEnv(env?: NodeJS.ProcessEnv): InheritedSessionPin[]; declare function serializeInheritedSessionPins(pins: readonly InheritedSessionPin[]): string; //#endregion //#region src/auth-store.d.ts declare const MULTIPROVIDER_AUTH_FILE = "multiprovider-auth.json"; interface MultiAuthAccount { id: string; label: string; authKind: AuthKind; enabled: boolean; weight: number; priority: number; createdAt: string; updatedAt: string; } interface MultiAuthUpstreamPreferences { label?: string; weight?: number; priority?: number; } interface MultiAuthPool { providerId: string; policy: SelectionPolicy; affinity: boolean; includeUpstream: boolean; upstream?: MultiAuthUpstreamPreferences; accounts: MultiAuthAccount[]; } interface AddMultiAuthAccount { label: string; credential: Credential; enabled?: boolean; weight?: number; priority?: number; pool?: MultiAuthPoolSettings; } interface MultiAuthPoolSettings { policy?: SelectionPolicy; affinity?: boolean; includeUpstream?: boolean; upstream?: MultiAuthUpstreamPreferences; } interface MultiAuthAccountSettings { label?: string; enabled?: boolean; weight?: number; priority?: number; } declare function normalizeUpstreamPreferences(input: MultiAuthUpstreamPreferences): MultiAuthUpstreamPreferences; declare function getMultiAuthPath(): string; declare class MultiAuthStore { readonly path: string; private readonly lockPath; constructor(path?: string); listProviderIds(): Promise; getPool(providerId: string): Promise; hasAccounts(providerId: string): Promise; addAccount(providerId: string, input: AddMultiAuthAccount): Promise; /** * Replaces an existing account's credential in place. Reauthentication uses * this instead of remove + add so the account keeps its id, label, weight, * priority, and any session pins pointing at it. */ replaceAccountCredential(providerId: string, accountId: string, credential: Credential): Promise; removeAccount(providerId: string, accountId: string): Promise; updatePool(providerId: string, settings: MultiAuthPoolSettings): Promise; updateAccount(providerId: string, accountId: string, settings: MultiAuthAccountSettings): Promise; getSchedulerSettings(): Promise; updateSchedulerSettings(settings: SchedulerSettingsPatch): Promise; listVirtualProviders(): Promise; getVirtualProvider(id: string): Promise; saveVirtualProvider(input: VirtualProviderConfig): Promise; removeVirtualProvider(id: string): Promise; resolveAccount(provider: Provider, accountId: string, signal: AbortSignal): Promise; private mutate; private readState; private readStateUnlocked; private writeStateUnlocked; private withLock; } //#endregion //#region src/managed.d.ts declare const PI_UPSTREAM_ACCOUNT_ID = "pi:default"; declare function mergeProviderAuth(provider: Provider, hasStoredAccounts: () => Promise): ProviderAuth; declare function createManagedIntegration(provider: Provider, store: MultiAuthStore): MultiProviderIntegration; //#endregion export { AccountAttemptContext, AccountLease, AccountPreference, AccountRequestContext, AcquireOptions, ActiveAccount, ActiveAccountAuth, ActiveAccountChangedEvent, AddMultiAuthAccount, AffinityPin, type AmbientAuthResolution, type AnnouncementDependencies, AuthKind, BACKEND_UNAVAILABLE_PREFIX, FailoverInfo, FailureDisposition, FailureKind, type InheritedSessionPin, LeaseOutcome, LeaseOutcomeCancelled, LeaseOutcomeFailure, LeaseOutcomeSuccess, LiftProviderOptions, MULTIPROVIDER_AUTH_FILE, MULTIPROVIDER_REGISTER_EVENT, MULTIPROVIDER_SERVICE_EVENT, MultiAuthAccount, MultiAuthAccountSettings, MultiAuthPool, MultiAuthPoolSettings, MultiAuthStore, MultiAuthUpstreamPreferences, MultiProviderIntegration, MultiProviderService, MultiProviderServiceAnnouncement, MultiProviderServiceContext, MultiProviderSnapshot, NoAccountAvailableError, PI_UPSTREAM_ACCOUNT_ID, PoolPreference, ProviderAccount, ProviderAttemptFailure, ProviderRegistration, PublicAccountSnapshot, PublicAccountStatus, PublicPoolSnapshot, SCHEDULER_DEFAULTS, SCHEDULER_SETTING_KEYS, SESSION_PIN_ENTRY_TYPE, SESSION_PIN_ENV, SchedulerOptions, SchedulerSettings, SchedulerSettingsPatch, SelectionBias, SelectionPolicy, type ServiceAnnouncementHandle, type SessionPin, type SessionPinHost, UnknownAccountError, UnknownProviderError, VIRTUAL_ID_SEPARATOR, VirtualBackend, type VirtualIntegrationOptions, VirtualModelConfig, VirtualModelTemplate, VirtualProviderConfig, type VirtualProviderDependencies, applySessionPins, captureVirtualModelTemplate, createManagedIntegration, createServiceAnnouncement, createVirtualIntegrations, createVirtualProvider, getMultiAuthPath, healVirtualTemplates, inheritedSessionPinsFromEnv, inheritedSessionPinsFromUnknown, liftProvider, mergeProviderAuth, normalizeUpstreamPreferences, registerMultiProvider, serializeInheritedSessionPins, sessionPinsFromEntries, virtualBackendAccountId, virtualSchedulerId };