import type { Api, Context, Model, ModelSpec, RemoteCompactionConfig, SimpleStreamOptions, ThinkingConfig } from "@oh-my-pi/pi-ai/types";
import type { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-stream";
import { type ModelRefreshStrategy } from "@oh-my-pi/pi-catalog/model-manager";
import type { ApiKeyResolver, FetchImpl } from "@oh-my-pi/pi-ai";
import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/oauth/types";
import type { AuthStorage } from "../session/auth-storage.js";
import { type ApiKeyResolverModel, type ApiKeyResolverOptions } from "./api-key-resolver.js";
import type { ConfigError } from "./config-file.js";
export declare const kNoAuth = "N/A";
export declare function isAuthenticated(apiKey: string | undefined | null): apiKey is string;
/** Provider override config (baseUrl, headers, apiKey, compat, transport) without custom models */
interface ProviderOverride {
    baseUrl?: string;
    headers?: Record<string, string>;
    apiKey?: string;
    authHeader?: boolean;
    compat?: ModelSpec<Api>["compat"];
    remoteCompaction?: RemoteCompactionConfig<Api>;
    transport?: Model<Api>["transport"];
}
/**
 * Merge a freshly discovered model with the matching bundled/configured entry
 * (or a runtime provider override when no bundled entry exists).
 *
 * `baseUrl` resolution priority:
 *   1. User-set `providerOverride.baseUrl` (explicit override in models.json)
 *   2. Discovered baseUrl (xiaomi `tp-` token-plan keys resolve to
 *      `token-plan-sgp.xiaomimimo.com` at discovery time)
 *   3. Existing bundled baseUrl (the host baked into `models.json`)
 *
 * `transport` resolution priority:
 *   1. `providerOverride.transport` (e.g. `pi-native` for auth-gateway users)
 *   2. `existing.transport` (carried over from boot-time override application)
 *   3. `model.transport` (rarely set — discovery defaults omit it)
 *
 * Without (1), the user's override would lose to discovery; without (2)
 * preferred over (3), the bundled `api.xiaomimimo.com` would shadow the
 * tp- token-plan host and produce 401s on the first stream call.
 * Without explicit transport propagation, an openrouter (or any) entry
 * marked `transport: pi-native` in models.yml silently reverts to the
 * default openai-completions transport after the background catalog
 * refresh — so the first `/model` switch after boot hits the raw OpenAI
 * chat-completions URL instead of the gateway's `/v1/pi/stream` (#2555).
 * See `xiaomi-tp-discovery-merge.test.ts` and the `refresh()` baseUrl-override
 * regression in `model-registry.test.ts`.
 */
export declare function mergeDiscoveredModel<TApi extends Api>(model: Model<TApi>, existing: Model<Api> | undefined, providerOverride?: Pick<ProviderOverride, "baseUrl" | "headers" | "remoteCompaction" | "transport">): Model<TApi>;
export type ProviderDiscoveryStatus = "idle" | "ok" | "empty" | "cached" | "unavailable" | "unauthenticated";
export interface ProviderDiscoveryState {
    provider: string;
    status: ProviderDiscoveryStatus;
    optional: boolean;
    stale: boolean;
    fetchedAt?: number;
    models: string[];
    error?: string;
}
/**
 * Model registry - loads and manages models, resolves API keys via AuthStorage.
 */
export declare class ModelRegistry {
    #private;
    readonly authStorage: AuthStorage;
    /**
     * @param authStorage - Auth storage for API key resolution
     *
     * Sync constructor — eagerly loads bundled + cached models so tests and
     * synchronous callers see a fully-populated registry immediately. Production
     * boot paths SHOULD prefer {@link ModelRegistry.create} so the YAML/JSONC
     * migration step lands off the event loop's hot path before the first
     * `tryLoad()` runs.
     */
    constructor(authStorage: AuthStorage, modelsPath?: string, options?: {
        fetch?: FetchImpl;
    });
    /**
     * Reload models from disk (built-in + custom from models.json).
     */
    refresh(strategy?: ModelRefreshStrategy): Promise<void>;
    refreshInBackground(strategy?: ModelRefreshStrategy): void;
    refreshProvider(providerId: string, strategy?: ModelRefreshStrategy): Promise<void>;
    /**
     * Refresh dynamic metadata that can appear only after a local model loads.
     */
    refreshSelectedModelMetadata(model: Model<Api>): Promise<Model<Api>>;
    /**
     * Discover models for providers registered at runtime via `fetchDynamicModels`
     * (extension providers). Merges the discovered catalog into the existing model
     * set without reloading static models, so dynamically-discovered models from
     * other providers are preserved. No-op when no runtime providers are registered.
     *
     * Drives the same SQLite model cache as built-in providers, so the default
     * `online-if-uncached` strategy fetches at most once per cache TTL (24 h).
     */
    refreshRuntimeProviders(strategy?: ModelRefreshStrategy): Promise<void>;
    /**
     * Get any error from loading models.json (undefined if no error).
     */
    getError(): ConfigError | undefined;
    /**
     * Get all models (built-in + custom).
     * If models.json had errors, returns only built-in models.
     */
    getAll(): Model<Api>[];
    /**
     * Get only models that have auth configured.
     * This is a fast check that doesn't refresh OAuth tokens.
     */
    getAvailable(): Model<Api>[];
    /**
     * Check whether auth is configured for a model's provider.
     *
     * Mirrors the upstream `@mariozechner/pi-coding-agent` API surface so that
     * external plugins/extensions and downstream wrappers (e.g. subagent launch
     * paths that pre-flight auth before model resolution) can probe a model
     * without resolving an API key. Returns true for keyless providers as well
     * as providers with stored credentials. See issue #993.
     *
     * Side-effect-free and synchronous: a command-backed key (`!cmd`) counts as
     * configured by its presence alone — the program is NOT executed — and OAuth
     * tokens are NOT refreshed (`authStorage.hasAuth`). This is what keeps the
     * model-switch pre-flight off the event loop's hot path; the real key
     * (command execution + OAuth refresh) is resolved lazily per request via
     * {@link ModelRegistry.resolver}.
     */
    hasConfiguredAuth(model: Model<Api>): boolean;
    getDiscoverableProviders(): string[];
    getProviderDiscoveryState(provider: string): ProviderDiscoveryState | undefined;
    /**
     * Find a model by provider and ID.
     */
    find(provider: string, modelId: string): Model<Api> | undefined;
    /**
     * Get the base URL associated with a provider, if any model defines one.
     */
    getProviderBaseUrl(provider: string): string | undefined;
    /**
     * Get API key for a model.
     */
    getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined>;
    /**
     * Get API key for a provider (e.g., "openai").
     *
     * `options.forceRefresh` powers step (b) of the auth-retry policy — it
     * re-mints the session-sticky OAuth token even when the cached copy still
     * looks valid. `options.signal` is threaded into any broker-bound refresh.
     */
    getApiKeyForProvider(provider: string, sessionId?: string, options?: {
        baseUrl?: string;
        modelId?: string;
        forceRefresh?: boolean;
        signal?: AbortSignal;
    }): Promise<string | undefined>;
    /**
     * Build an {@link ApiKeyResolver} implementing the central a/b/c auth-retry
     * policy. Accepts a provider id with options, or a model with an optional
     * session id (`resolver(model, sessionId)`) which derives `baseUrl`/`modelId`
     * from the model. Callers that need the initial key for a guard can call
     * `resolveApiKeyOnce(resolver)`.
     */
    resolver(provider: string, options?: ApiKeyResolverOptions): ApiKeyResolver;
    resolver(model: ApiKeyResolverModel, sessionId?: string): ApiKeyResolver;
    /**
     * Check if a model is using OAuth credentials (subscription).
     */
    isUsingOAuth(model: Model<Api>): boolean;
    /**
     * Remove custom API/OAuth registrations for a specific extension source.
     */
    clearSourceRegistrations(sourceId: string): void;
    /**
     * Remove registrations for extension sources that are no longer active.
     */
    syncExtensionSources(activeSourceIds: string[]): void;
    /**
     * Register a provider dynamically (from extensions).
     *
     * If provider has models: replaces all existing models for this provider.
     * If provider has only baseUrl/headers: overrides existing models' URLs.
     * If provider has streamSimple: registers a custom API streaming function.
     * If provider has oauth: registers OAuth provider for /login support.
     */
    registerProvider(providerName: string, config: ProviderConfigInput, sourceId?: string): void;
    /**
     * Suppress a specific model selector (e.g., "provider/id") until a specific timestamp.
     */
    suppressSelector(selector: string, untilMs: number): void;
    /**
     * Check if a model selector is currently suppressed due to rate limits.
     */
    isSelectorSuppressed(selector: string): boolean;
    /**
     * Clear the cooldown suppression for one selector after an explicit user selection.
     */
    clearSuppressedSelector(selector: string): void;
    /**
     * Clear all cooldown suppressions recorded via {@link suppressSelector}.
     * Used to reset retry-fallback cooldown state without a full {@link refresh}.
     */
    clearSuppressedSelectors(): void;
}
/**
 * Input type for registerProvider API (from extensions).
 */
export interface ProviderConfigInput {
    baseUrl?: string;
    apiKey?: string;
    api?: Api;
    streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
    headers?: Record<string, string>;
    compat?: ModelSpec<Api>["compat"];
    remoteCompaction?: RemoteCompactionConfig<Api>;
    authHeader?: boolean;
    /** Streaming transport override — see {@link Model.transport}. */
    transport?: Model<Api>["transport"];
    oauth?: {
        name: string;
        login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials | string>;
        refreshToken?(credentials: OAuthCredentials): Promise<OAuthCredentials>;
        getApiKey?(credentials: OAuthCredentials): string;
        modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];
    };
    /**
     * Async factory that fetches the live model list from the provider endpoint.
     * When present, the result is run through the same SQLite model-cache as
     * built-in providers (keyed by provider name, default 24 h TTL).
     * The factory receives the resolved API key (undefined when unauthenticated).
     */
    fetchDynamicModels?: (apiKey: string | undefined) => Promise<readonly NonNullable<ProviderConfigInput["models"]>[number][]>;
    models?: Array<{
        id: string;
        name: string;
        api?: Api;
        baseUrl?: string;
        reasoning: boolean;
        thinking?: ThinkingConfig;
        input: ("text" | "image")[];
        supportsTools?: boolean;
        cost: {
            input: number;
            output: number;
            cacheRead: number;
            cacheWrite: number;
        };
        contextWindow: number;
        maxTokens: number;
        headers?: Record<string, string>;
        compat?: ModelSpec<Api>["compat"];
        contextPromotionTarget?: string;
        compactionModel?: string;
        remoteCompaction?: RemoteCompactionConfig<Api>;
        premiumMultiplier?: number;
    }>;
}
export {};
