/** * Agent Engine Registry. * * Mirrors the CLI_REGISTRY pattern (packages/core/src/terminal/cli-registry.ts) * but is open — anyone can register a custom engine via registerAgentEngine() * from a server plugin at startup. * * Built-in engines (anthropic, ai-sdk) are auto-registered by builtin.ts. */ import type { AgentEngine, EngineCapabilities } from "./types.js"; export interface AgentEngineEntry { /** Unique name, e.g. "anthropic", "ai-sdk:anthropic", "ai-sdk:openai" */ name: string; /** Human-readable label for UI */ label: string; /** Short description for engine picker */ description: string; /** npm package hint displayed in UI when package is missing */ installPackage?: string; /** Engine capabilities */ capabilities: EngineCapabilities; /** Default model string */ defaultModel: string; /** All supported models (shown in model picker) */ supportedModels: readonly string[]; /** Environment variables required for this engine to work */ requiredEnvVars: string[]; /** Create an engine instance from config */ create(config: Record): AgentEngine; } /** * Register a custom agent engine. Called at server startup (e.g., from a * server plugin or builtin.ts). Throws if name is already registered. */ export declare function registerAgentEngine(entry: AgentEngineEntry): void; /** Get a registered engine entry by name, or undefined if not found */ export declare function getAgentEngineEntry(name: string): AgentEngineEntry | undefined; /** List all registered engine entries */ export declare function listAgentEngines(): AgentEngineEntry[]; export declare function isAgentEnginePackageInstalled(entry: AgentEngineEntry): boolean; export interface NormalizeModelOptions { /** * Force unrecognized (custom) model IDs to be kept verbatim, as if * `engine.preserveCustomModels` were set on a live engine instance. * * The settings actions call `normalizeModelForEngine` with a static registry * ENTRY, which never carries the runtime `preserveCustomModels` flag — that * is only set on the engine INSTANCE created with an OpenAI-compatible * `baseUrl`. They resolve the capability with * {@link resolveEnginePreservesCustomModels} and pass it here so a gateway * model (e.g. an Ollama `gemma4`) is not rewritten to the OpenAI default on * save/read. First-party OpenAI (no gateway) leaves this unset, so an unknown * or invalid model still normalizes to a supported one. */ preserveCustomModels?: boolean; } export declare function normalizeModelForEngine(engine: Pick, model: string | null | undefined, options?: NormalizeModelOptions): string; /** * Whether models saved or read for this engine ENTRY should be preserved * verbatim instead of normalized against the built-in catalog. * * `normalizeModelForEngine` honors a live engine's `preserveCustomModels`, but * that flag is only set on an AI SDK engine INSTANCE when the OpenAI provider * is pointed at an OpenAI-compatible gateway (a custom base URL — e.g. Ollama * Cloud or LiteLLM), whose model IDs are not in the built-in OpenAI catalog. * The static registry entry the settings actions pass to * `normalizeModelForEngine` cannot carry that runtime flag, so this async * helper reproduces the same decision — `ai-sdk:openai` AND a resolved base URL * — from the request's stored/deploy config. First-party OpenAI (no gateway) * returns false so an unknown/invalid model still normalizes to a supported one. */ export declare function resolveEnginePreservesCustomModels(entry: Pick): Promise; /** * First registered engine whose requiredEnvVars are all set. Registration * order controls priority — the Builder gateway is registered first so it * wins when the Builder private key is present. * * Escape hatch: AGENT_ENGINE_PREFER_BYO_KEY=true skips the Builder engine * on the first pass, so an explicit provider key (ANTHROPIC_API_KEY etc.) * is picked instead. Builder is still used as the fallback when no other * provider key is set. * * This sync helper is for CLI/status callers that cannot await settings. Prefer * {@link detectEngineFromEnvForRequest} at request time so sticky auth-failure * markers can skip rejected deploy keys. */ export declare function detectEngineFromEnv(): AgentEngineEntry | null; /** * Request-aware env auto-detect. Same priority as {@link detectEngineFromEnv}, * but skips provider keys that currently have an auth-failure marker so a * rejected deploy key does not permanently win selection and leave chat stuck * on `missing_credentials`. */ export declare function detectEngineFromEnvForRequest(): Promise; /** * Detect a usable engine from the current request user's accessible * `app_secrets` rows. Mirrors `detectEngineFromEnv` but consults the * encrypted secret store instead of `process.env`, including org-scoped * credentials shared with the active organization. * * Required because the Builder OAuth callback (and the settings UI's * "paste your own key" flow) writes credentials to app_secrets, not env. * Without this check, a user who connected Builder would see status * "configured" but the next chat turn would fall through to the default * Anthropic engine and hit `missing_api_key` — exactly Brent's symptom * on the docs site (Loom 2026-04-28: "It doesn't seem to realize I'm * connected once I do a chat"). * * Includes the local dev session (`local@localhost`): the Builder * OAuth flow writes credentials scoped to that email when run from * `pnpm dev`, so detection has to consult those rows or the dev user * sees the same "Connect your AI" card after they've already connected * (Sami, 2026-04-30). Org-scoped Builder credentials must also count here: * `/builder/status` resolves them via the same request org context, and the * chat engine picker must not disagree with that card. */ export declare function detectEngineFromUserSecrets(): Promise; /** * Legacy inline API keys on the global `agent-engine` settings row are * intentionally ignored. That row is deployment-wide, so treating * `{ apiKey }` or `{ config: { apiKey } }` as configured would let one * user's pasted key power every other user. Per-user keys live in * `app_secrets` and are resolved separately. */ export declare function isAgentEngineSettingConfigured(stored: unknown): boolean; /** * True when the stored `agent-engine` row points at a registered engine * AND an API key for it is reachable via the engine's required env vars. * Inline keys on the global settings row are ignored; see * `isAgentEngineSettingConfigured`. * * Sync helper for CLI/status. Prefer {@link isStoredEngineUsableForRequest} * so sticky auth-failure markers are respected. */ export declare function isStoredEngineUsable(stored: unknown, entry: AgentEngineEntry): boolean; /** * Request-aware version of `isStoredEngineUsable`. * * The settings row stores the selected engine/model, while credentials may * live in per-user/org `app_secrets`. The sync helper intentionally only sees * deploy env vars; this async helper is what request-time routes should use * when deciding whether a stored engine can actually run for the current user. */ export declare function isStoredEngineUsableForRequest(stored: unknown, entry: AgentEngineEntry): Promise; /** * Request-aware credential preflight for an already-resolved engine instance. * `resolveEngine()` may still return a default or explicitly requested engine * object before credentials are actually usable; call this before starting a * user-visible run so missing providers fail immediately. */ export declare function isResolvedEngineUsableForRequest(engine: AgentEngine, options?: { apiKey?: string; }): Promise; export interface ResolveEngineConfig { /** Explicit engine name or instance from createAgentChatPlugin options */ engineOption?: string | AgentEngine | { name: string; config: Record; }; /** API key (used as config for the resolved engine) */ apiKey?: string; /** Model override (used as part of engine config) */ model?: string; /** App/template id used for org-scoped per-app model defaults. */ appId?: string; } /** * Return the usable engine explicitly selected by the current user/org. * * This is intentionally narrower than {@link resolveEngine}: it only inspects * persisted app/global selections and does not auto-detect credentials or fall * back to deployment defaults. Callers that have their own configured fallback * (notably messaging integrations) can therefore honor the live request's * Agent settings before applying that fallback, while still resolving the API * key for the provider that was actually selected. */ export declare function getConfiguredEngineNameForRequest(options?: { appId?: string; }): Promise; /** * Resolve an AgentEngine from options → explicit env → app default → * settings → request credentials → env → default. * * Resolution order: * 1. Explicit `engineOption` from plugin options (string name, instance, or {name, config}) * 2. Env var AGENT_ENGINE * 3. Org/user app-template default, when usable * 4. Settings store key "agent-engine" → { engine: string }, when usable * 5. Current request's app_secrets; Builder wins by default when connected * 6. Auto-detect deployment env credentials * 7. Default "anthropic" (requires ANTHROPIC_API_KEY) */ export declare function resolveEngine(config: ResolveEngineConfig): Promise; /** * Read the user-selected model for an engine from the `agent-engine` setting. * * The settings UI writes `{engine, model}` via the `manage-agent-engine` action="set", * but `resolveEngine` only uses the stored engine (the model is a separate * per-request concern). Call this helper alongside `resolveEngine` to honor * the user's model choice without requiring a process restart. * * Returns the stored model only when the stored engine name matches `engine` * — otherwise returns `undefined` to avoid applying an Anthropic model string * to, say, an OpenRouter engine. */ export declare function getStoredModelForEngine(engine: AgentEngine | string, options?: { appId?: string; }): Promise; //# sourceMappingURL=registry.d.ts.map