import { type Api, type AssistantMessageEventStream, type AuthCredentialSelector, type CacheRetention, type Context, type Model, type ModelRefreshStrategy, type ModelRequestTransform, type SimpleStreamOptions, type ThinkingConfig } from "@gajae-code/ai/core"; import type { OAuthCredentials, OAuthLoginCallbacks } from "@gajae-code/ai/utils/oauth/types"; import { type ThemeColor } from "../modes/theme/theme"; import { type ActiveProviderDescriptor } from "../sdk/providers"; import type { AuthStorage } from "../session/auth-storage"; import type { ActiveSearchModelContext, WebSearchMode } from "../web/search/types"; import { type ConfigError, ConfigFile } from "./config-file"; import { isAuthenticated, kNoAuth } from "./model-auth"; import { type ConfiguredModelBindings } from "./model-bindings-applier"; import { type ProviderDiscoveryState } from "./model-discovery-manager"; export type { ProviderDiscoveryState, ProviderDiscoveryStatus } from "./model-discovery-manager"; import { type CanonicalModelIndex, type CanonicalModelRecord, type CanonicalModelVariant, type ModelEquivalenceConfig } from "./model-equivalence"; import { type ModelProfileDefinition } from "./model-profiles"; import { GJC_MODEL_ASSIGNMENT_TARGET_IDS, type ModelProfileConfig } from "./models-config-schema"; import { type EffectiveProviderAuth } from "./provider-selection-policy"; import { type Settings } from "./settings"; export type { EffectiveProviderAuth, ProviderSelectionPolicy } from "./provider-selection-policy"; export type { CanonicalModelIndex, CanonicalModelRecord, CanonicalModelVariant, ModelEquivalenceConfig }; export { isAuthenticated, kNoAuth }; export type ModelRole = "default"; export interface ModelRoleInfo { tag?: string; name: string; color?: ThemeColor; } export declare const MODEL_ROLES: Record; export declare const MODEL_ROLE_IDS: ModelRole[]; export declare const MODEL_PROFILE_NAME_PATTERN: RegExp; export declare const MODEL_PROFILE_NAME_PATTERN_DESCRIPTION = "lowercase letters, numbers, dots, underscores, or hyphens"; export type GjcModelAssignmentTargetId = (typeof GJC_MODEL_ASSIGNMENT_TARGET_IDS)[number]; export interface GjcModelAssignmentTargetInfo extends ModelRoleInfo { id: GjcModelAssignmentTargetId; settingsPath: "modelRoles" | "task.agentModelOverrides"; } export { GJC_MODEL_ASSIGNMENT_TARGET_IDS }; export declare const GJC_MODEL_ASSIGNMENT_TARGETS: Record; export declare function requiresExplicitThinkingChoice(model: Model, role: GjcModelAssignmentTargetId | null): boolean; /** Alias for ModelRoleInfo - used for both built-in and custom roles */ export type RoleInfo = ModelRoleInfo; /** * Return the canonical set of known roles for selector/carousel UI. * * Built-ins always come first. Configured cycle order, model assignments, and * tag metadata can introduce additional custom roles without requiring duplicate * entries across settings. */ export declare function getKnownRoleIds(settings: Settings): string[]; /** * Get role info for a role name (built-in or custom). * Configured metadata overrides built-in defaults when present. */ export declare function getRoleInfo(role: string, settings: Settings): RoleInfo; export declare const ModelsConfigFile: ConfigFile<{ providers?: Record | undefined; compat?: { supportsStore?: boolean | undefined; supportsDeveloperRole?: boolean | undefined; sendSessionHeaders?: boolean | undefined; supportsResponsesSessionAffinity?: boolean | undefined; supportsServiceTier?: boolean | undefined; supportsMultipleSystemMessages?: boolean | undefined; supportsReasoningEffort?: boolean | undefined; reasoningEffortMap?: { minimal?: string | undefined; low?: string | undefined; medium?: string | undefined; high?: string | undefined; xhigh?: string | undefined; max?: string | undefined; } | undefined; maxTokensField?: "max_completion_tokens" | "max_tokens" | undefined; supportsUsageInStreaming?: boolean | undefined; requiresToolResultName?: boolean | undefined; requiresMistralToolIds?: boolean | undefined; requiresAssistantAfterToolResult?: boolean | undefined; requiresThinkingAsText?: boolean | undefined; reasoningContentField?: "reasoning" | "reasoning_content" | "reasoning_text" | undefined; requiresReasoningContentForToolCalls?: boolean | undefined; allowsSyntheticReasoningContentForToolCalls?: boolean | undefined; requiresAssistantContentForToolCalls?: boolean | undefined; supportsToolChoice?: boolean | undefined; supportsForcedToolChoice?: boolean | undefined; toolChoiceSupport?: "auto" | "named" | "none" | "required" | undefined; disableReasoningOnForcedToolChoice?: boolean | undefined; disableReasoningOnToolChoice?: boolean | undefined; thinkingFormat?: "openai" | "openrouter" | "qwen" | "qwen-chat-template" | "zai" | undefined; openRouterRouting?: { only?: string[] | undefined; order?: string[] | undefined; } | undefined; vercelGatewayRouting?: { only?: string[] | undefined; order?: string[] | undefined; } | undefined; extraBody?: Record | undefined; supportsStrictMode?: boolean | undefined; toolStrictMode?: "all_strict" | "none" | undefined; supportsLongCacheRetention?: boolean | undefined; promptCacheMode?: "automatic" | "explicit" | "none" | undefined; } | undefined; webSearch?: "auto" | "off" | "on" | undefined; authHeader?: boolean | undefined; auth?: "apiKey" | "none" | "oauth" | undefined; discovery?: { type: "llama.cpp" | "lm-studio" | "models-dev" | "ollama" | "omlx" | "openai-models-list" | "sglang" | "vllm"; apiByModelPrefix?: Record | undefined; modelsDevProvider?: string | undefined; } | undefined; requestTransform?: { profile?: "openai-proxy" | undefined; stripHeaders?: string[] | undefined; setHeaders?: Record | undefined; extraBody?: Record | undefined; } | undefined; models?: { id: string; name?: string | undefined; api?: "anthropic-messages" | "azure-openai-responses" | "bedrock-converse-stream" | "cursor-agent" | "google-gemini-cli" | "google-generative-ai" | "google-vertex" | "ollama-chat" | "openai-codex-responses" | "openai-completions" | "openai-responses" | undefined; baseUrl?: string | undefined; reasoning?: boolean | undefined; thinking?: { minLevel: "high" | "low" | "max" | "medium" | "minimal" | "xhigh"; maxLevel: "high" | "low" | "max" | "medium" | "minimal" | "xhigh"; mode: "anthropic-adaptive" | "anthropic-budget-effort" | "budget" | "effort" | "google-level"; defaultLevel?: "high" | "low" | "max" | "medium" | "minimal" | "xhigh" | undefined; levels?: ("high" | "low" | "max" | "medium" | "minimal" | "xhigh")[] | undefined; } | undefined; input?: ("image" | "text")[] | undefined; output?: ("image" | "text")[] | undefined; cost?: { input: number; output: number; cacheRead: number; cacheWrite: number; } | undefined; premiumMultiplier?: number | undefined; contextWindow?: number | undefined; maxTokens?: number | undefined; headers?: Record | undefined; compat?: { supportsStore?: boolean | undefined; supportsDeveloperRole?: boolean | undefined; sendSessionHeaders?: boolean | undefined; supportsResponsesSessionAffinity?: boolean | undefined; supportsServiceTier?: boolean | undefined; supportsMultipleSystemMessages?: boolean | undefined; supportsReasoningEffort?: boolean | undefined; reasoningEffortMap?: { minimal?: string | undefined; low?: string | undefined; medium?: string | undefined; high?: string | undefined; xhigh?: string | undefined; max?: string | undefined; } | undefined; maxTokensField?: "max_completion_tokens" | "max_tokens" | undefined; supportsUsageInStreaming?: boolean | undefined; requiresToolResultName?: boolean | undefined; requiresMistralToolIds?: boolean | undefined; requiresAssistantAfterToolResult?: boolean | undefined; requiresThinkingAsText?: boolean | undefined; reasoningContentField?: "reasoning" | "reasoning_content" | "reasoning_text" | undefined; requiresReasoningContentForToolCalls?: boolean | undefined; allowsSyntheticReasoningContentForToolCalls?: boolean | undefined; requiresAssistantContentForToolCalls?: boolean | undefined; supportsToolChoice?: boolean | undefined; supportsForcedToolChoice?: boolean | undefined; toolChoiceSupport?: "auto" | "named" | "none" | "required" | undefined; disableReasoningOnForcedToolChoice?: boolean | undefined; disableReasoningOnToolChoice?: boolean | undefined; thinkingFormat?: "openai" | "openrouter" | "qwen" | "qwen-chat-template" | "zai" | undefined; openRouterRouting?: { only?: string[] | undefined; order?: string[] | undefined; } | undefined; vercelGatewayRouting?: { only?: string[] | undefined; order?: string[] | undefined; } | undefined; extraBody?: Record | undefined; supportsStrictMode?: boolean | undefined; toolStrictMode?: "all_strict" | "none" | undefined; supportsLongCacheRetention?: boolean | undefined; promptCacheMode?: "automatic" | "explicit" | "none" | undefined; } | undefined; contextPromotionTarget?: string | undefined; wireModelId?: string | undefined; requestTransform?: { profile?: "openai-proxy" | undefined; stripHeaders?: string[] | undefined; setHeaders?: Record | undefined; extraBody?: Record | undefined; } | undefined; cacheRetention?: "long" | "none" | "short" | undefined; }[] | undefined; modelOverrides?: Record | undefined; compat?: { supportsStore?: boolean | undefined; supportsDeveloperRole?: boolean | undefined; sendSessionHeaders?: boolean | undefined; supportsResponsesSessionAffinity?: boolean | undefined; supportsServiceTier?: boolean | undefined; supportsMultipleSystemMessages?: boolean | undefined; supportsReasoningEffort?: boolean | undefined; reasoningEffortMap?: { minimal?: string | undefined; low?: string | undefined; medium?: string | undefined; high?: string | undefined; xhigh?: string | undefined; max?: string | undefined; } | undefined; maxTokensField?: "max_completion_tokens" | "max_tokens" | undefined; supportsUsageInStreaming?: boolean | undefined; requiresToolResultName?: boolean | undefined; requiresMistralToolIds?: boolean | undefined; requiresAssistantAfterToolResult?: boolean | undefined; requiresThinkingAsText?: boolean | undefined; reasoningContentField?: "reasoning" | "reasoning_content" | "reasoning_text" | undefined; requiresReasoningContentForToolCalls?: boolean | undefined; allowsSyntheticReasoningContentForToolCalls?: boolean | undefined; requiresAssistantContentForToolCalls?: boolean | undefined; supportsToolChoice?: boolean | undefined; supportsForcedToolChoice?: boolean | undefined; toolChoiceSupport?: "auto" | "named" | "none" | "required" | undefined; disableReasoningOnForcedToolChoice?: boolean | undefined; disableReasoningOnToolChoice?: boolean | undefined; thinkingFormat?: "openai" | "openrouter" | "qwen" | "qwen-chat-template" | "zai" | undefined; openRouterRouting?: { only?: string[] | undefined; order?: string[] | undefined; } | undefined; vercelGatewayRouting?: { only?: string[] | undefined; order?: string[] | undefined; } | undefined; extraBody?: Record | undefined; supportsStrictMode?: boolean | undefined; toolStrictMode?: "all_strict" | "none" | undefined; supportsLongCacheRetention?: boolean | undefined; promptCacheMode?: "automatic" | "explicit" | "none" | undefined; } | undefined; contextPromotionTarget?: string | undefined; wireModelId?: string | undefined; requestTransform?: { profile?: "openai-proxy" | undefined; stripHeaders?: string[] | undefined; setHeaders?: Record | undefined; extraBody?: Record | undefined; } | undefined; cacheRetention?: "long" | "none" | "short" | undefined; }> | undefined; disableStrictTools?: boolean | undefined; transport?: "pi-native" | undefined; cacheRetention?: "long" | "none" | "short" | undefined; openaiCompat?: { baseUrl: string; apiKey?: string | undefined; apiKeyEnv?: string | undefined; } | undefined; }> | undefined; modelBindings?: { modelRoles?: Record | undefined; agentModelOverrides?: Record | undefined; } | undefined; equivalence?: { overrides?: Record | undefined; exclude?: string[] | undefined; } | undefined; profiles?: Record>; }> | undefined; }>; /** Provider override config (baseUrl, headers, apiKey, compat, transport) without custom models */ interface ProviderOverride { baseUrl?: string; headers?: Record; apiKey?: string; authHeader?: boolean; compat?: Model["compat"]; transport?: Model["transport"]; requestTransform?: ModelRequestTransform; cacheRetention?: CacheRetention; } /** * 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`) * * 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. * See `xiaomi-tp-discovery-merge.test.ts` and the `refresh()` baseUrl-override * regression in `model-registry.test.ts`. */ export declare function mergeDiscoveredModel(model: Model, existing: Model | undefined, providerOverride?: Pick): Model; export interface CanonicalModelQueryOptions { availableOnly?: boolean; candidates?: readonly Model[]; /** Stable session identity used to keep a canonical variant sticky within a session. */ sessionId?: string; /** Credential-selection session used to classify effective provider auth. Defaults to sessionId. */ credentialSessionId?: string; } /** One canonical record with its winning variant resolved, from a batch query. */ export interface CanonicalModelSelection { record: CanonicalModelRecord; model: Model | undefined; } /** * 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 */ constructor(authStorage: AuthStorage, modelsPath?: string, registrySettings?: Pick); onCatalogChanged(listener: () => void): () => void; /** Replace the read-only settings snapshot used by profile-scoped resolution. */ setScopedSettings(settingsReader: Pick): void; /** * Reload models from disk (built-in + custom from models.json). */ refresh(strategy?: ModelRefreshStrategy): Promise; refreshInBackground(strategy?: ModelRefreshStrategy): void; refreshProvider(providerId: string, strategy?: ModelRefreshStrategy): Promise; /** * Get any error from loading models.json (undefined if no error). */ getError(): ConfigError | undefined; getModelProfiles(): Map; getModelProfile(name: string): ModelProfileDefinition | undefined; getAvailableModelProfileNames(): string[]; saveCustomModelProfile(name: string, definition: ModelProfileConfig): Promise; renameCustomModelProfile(name: string, displayName: string): Promise; deleteCustomModelProfile(name: string): Promise; applyConfiguredModelBindings(targetSettings: Settings): void; /** * Re-assert configured modelBindings into the target override slots after a * session-scoped profile reset removed profile-installed keys. Bypasses the * user-edit heuristic so the startup role/agent routing is restored. */ reapplyConfiguredModelBindings(targetSettings: Settings): void; /** The currently configured modelBindings, for pre-profile baseline lookup. */ getConfiguredModelBindings(): ConfiguredModelBindings | undefined; /** * Get all models (built-in + custom). * If models.json had errors, returns only built-in models. */ getAll(): Model[]; /** Provider ids declared in models.yml, including override-only providers. */ getConfiguredProviderIds(): readonly string[]; /** * Deterministic provider priority for autorouting tier generation: configured * `modelProviderOrder` first, then first-wins catalog order. * * Deliberately takes no session and never touches `authStorage`. It bypasses * `#buildProviderSelectionPolicy` entirely so no `effectiveAuth` map is even * assembled — auth-independence is structural here, not a convention. Ranking * that *is* auth-aware stays private to the policy. * * Providers absent from the catalog are dropped so a dead declaration cannot * pollute the generated setup's `declarationFingerprint`. Returned ids use the * catalog's original spelling because the generator matches provider prefixes * with case-sensitive exact strings. */ autoroutingProviderOrder(): readonly string[]; getCanonicalModels(options?: CanonicalModelQueryOptions): CanonicalModelRecord[]; /** * Batch form of {@link resolveCanonicalModel} over every canonical record: * one candidate-key set, one provider policy, and one catalog order for the * whole query instead of per record. `model` is `undefined` only when a * record has surviving variants but none can win resolution. */ getCanonicalModelSelections(options?: CanonicalModelQueryOptions): CanonicalModelSelection[]; getCanonicalVariants(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[]; /** * Resolve an exact canonical id to a concrete model. * * Canonical ids remain exact lookup keys, but their provider variant uses the * same provider-rank-first policy as preset aliases. Availability filtering * remains opt-in through `availableOnly`; final-segment aliases never fall * back implicitly — use {@link resolveModelByLookupAlias} for alias intent. */ resolveCanonicalModel(canonicalId: string, options?: CanonicalModelQueryOptions): Model | undefined; /** * Resolve a final-slash-segment alias to a concrete model, explicitly. * * The alias gathers every matching variant selector from the variant-level * alias index — never sibling variants in the same canonical record that end * in a different segment — then ranks all eligible variants together by the * centralized provider policy (provider-rank-first axis order), with the * canonical/exactness axis preserved relative to the alias lookup key * (`model.id === alias` beats slash-prefixed ids before source/cost/catalog * ties). Fails closed: availability/disabled filtering applies even to * supplied candidate arrays, alias variants are intersected with the * filtered candidate selectors before ranking, and zero eligible candidates * returns an authoritative `undefined` without rewriting the variant's * model/wire ids. Winners stay sticky per session. */ resolveModelByLookupAlias(alias: string, options?: CanonicalModelQueryOptions): Model | undefined; /** * Whether a final-slash-segment alias is known in the current canonical * index. Knownness is decided from the full multi-target alias index and is * independent of availability: a known-but-unavailable alias still reports * `true` while {@link resolveModelByLookupAlias} returns an authoritative * `undefined`. */ lookupAliasExists(alias: string): boolean; /** * Effective credential provenance for a provider, derived from existing * AuthStorage/session credential surfaces (never from token shape). * Session-specific provenance wins; API-key surfaces (runtime, config, * custom/manual, stored api_key) beat OAuth presence; OAuth remains the * provenance when it is the effective remaining stored credential; * unknown/keyless providers fall back to non-OAuth. */ getEffectiveProviderAuth(provider: string, sessionId?: string): EffectiveProviderAuth; /** * Forget a session's remembered canonical variant so the next resolution * for that session re-ranks from scratch (explicit reselection * integration). Returns whether an entry was actually removed. */ clearCanonicalVariant(sessionId: string): boolean; /** * Snapshot a session's remembered sticky canonical variant selector — the exact * concrete "provider/id" selector, captured verbatim rather than re-derived * from any live model. Returns undefined when the session has no remembered * variant. The caller owns restoring it later via * {@link restoreSessionCanonicalVariant}. */ getSessionCanonicalVariant(sessionId: string): string | undefined; /** * Restore a session's sticky canonical variant selector exactly, preserving the * concrete provider/model previously remembered (never re-derived from a live * model). The selector must still be present in the canonical index, otherwise * the stale variant is left untouched and `false` is returned. */ restoreSessionCanonicalVariant(sessionId: string, selector: string): boolean; getCanonicalId(model: Model): string | undefined; /** * Seed a child canonical scope from a concrete parent model without touching * the parent's canonical selection. */ seedCanonicalVariant(sessionId: string, model: Model): boolean; /** * Get only models that have auth configured. * This is a fast check that doesn't refresh OAuth tokens. */ getAvailable(): Model[]; /** * Get authenticated models, excluding bundled entries that a fresh provider * catalog has positively shown to be unavailable. Bundled entries remain * usable until live catalog evidence exists so offline startup is unchanged. */ getAvailableForProfileActivation(): Model[]; getActiveProviders(): ActiveProviderDescriptor[]; /** * Check whether auth is configured for a model's provider. * * Mirrors the upstream `@mariozechner/gajae-code` 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. */ hasConfiguredAuth(model: Model): boolean; /** * Check whether auth is configured for a provider. */ hasConfiguredProviderAuth(provider: string): boolean; isCredentiallessProvider(provider: string): boolean; getDiscoverableProviders(): string[]; getProviderDiscoveryState(provider: string): ProviderDiscoveryState | undefined; /** * Find a model by provider and ID. */ find(provider: string, modelId: string): Model | undefined; /** * Get the base URL associated with a provider, if any model defines one. */ getProviderBaseUrl(provider: string): string | undefined; getProviderWebSearchMode(provider: string): WebSearchMode | undefined; getActiveSearchModelContext(model: Model): ActiveSearchModelContext; /** * Get API key for a model. */ getApiKey(model: Model, sessionId?: string, options?: { credentialSelector?: AuthCredentialSelector; preferredCredentialSelector?: AuthCredentialSelector; signal?: AbortSignal; }): Promise; /** * Get API key for a provider (e.g., "openai"). */ getApiKeyForProvider(provider: string, sessionId?: string, baseUrl?: string, options?: { credentialSelector?: AuthCredentialSelector; preferredCredentialSelector?: AuthCredentialSelector; signal?: AbortSignal; }): Promise; /** * Check if a model is using OAuth credentials (subscription). */ isUsingOAuth(model: Model): boolean; getSessionCredentialType(provider: string, sessionId?: string): "api_key" | "oauth" | undefined; /** * 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; /** Return whether a selector has an active, expired, or no rate-limit suppression. */ getSelectorSuppressionStatus(selector: string): "active" | "expired" | "none"; } /** * Input type for registerProvider API (from extensions). */ export interface ProviderConfigInput { baseUrl?: string; apiKey?: string; api?: Api; streamSimple?: (model: Model, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream; headers?: Record; compat?: Model["compat"]; requestTransform?: ModelRequestTransform; authHeader?: boolean; /** Streaming transport override — see {@link Model.transport}. */ transport?: Model["transport"]; oauth?: { name: string; login(callbacks: OAuthLoginCallbacks): Promise; refreshToken?(credentials: OAuthCredentials): Promise; getApiKey?(credentials: OAuthCredentials): string; modifyModels?(models: Model[], credentials: OAuthCredentials): Model[]; }; models?: Array<{ id: string; name: string; api?: Api; baseUrl?: string; reasoning: boolean; thinking?: ThinkingConfig; input: ("text" | "image")[]; cost: { input: number; output: number; cacheRead: number; cacheWrite: number; }; contextWindow: number; maxTokens: number; headers?: Record; compat?: Model["compat"]; requestTransform?: ModelRequestTransform; wireModelId?: string; contextPromotionTarget?: string; premiumMultiplier?: number; }>; }