declare const CONTENT_TYPES: readonly ["text", "image", "audio", "video", "document", "entities", "embedding", "decision"]; type ContentType = (typeof CONTENT_TYPES)[number]; declare const INTERACTION_MODES: readonly ["turn", "single", "extraction", "realtime", "embedding", "agent", "decision", "adapter", "detection"]; type InteractionMode = (typeof INTERACTION_MODES)[number]; declare const FEATURE_KEYS: readonly ["streaming", "function_calling", "tool_calling", "thinking", "structured_output", "json_mode", "web_search", "x_search", "vision", "code_execution", "computer_use", "multi_turn", "system_prompt", "embeddings", "dimension_reduction", "fine_tuning", "batch_api", "prompt_caching", "context_caching", "context_management", "partial_mode", "assistant_prefill", "strict_role_alternation", "ner", "classification", "structured_extraction", "relation_extraction", "pii_detection", "image_generation", "image_editing", "inpainting", "image_to_video", "video_generation", "video_editing", "audio_generation", "conversational_editing", "stateful_editing", "dialogue", "file_search", "mcp", "background_execution", "collaborative_planning", "visualization", "citations", "sandboxed_execution", "live_api", "url_context", "grounding_maps", "multimodal_embedding", "music_generation", "real_time_translation", "long_decode"]; type FeatureKey = (typeof FEATURE_KEYS)[number]; interface ModelCapabilities { input: ContentType[]; output: ContentType[]; features: FeatureKey[]; interaction: InteractionMode; /** Model is explicitly multilingual (canonical `capabilities.multilingual`; absent → false). */ multilingual: boolean; } /** * Provider-managed research agents (Google Deep Research) run as an agent's * model through the normal agent run path — a long background interaction * that streams its research and returns a cited report. */ declare function isManagedAgentModelCapability(capabilities: Pick): boolean; /** Decision models have a non-conversational wire contract. */ declare function isDecisionModelCapability(capabilities: Pick): boolean; /** * What a picker is allowed to select, by execution contract. * * chat — a conversational turn only (`/chat`, launchers, prompt runs). * agent — an AGENT's model. An agent is not necessarily a conversation: * its message may carry a `decision_questions` part and be * answered by a decision holder that emits no text at all, or it * may be a provider-managed research agent (Deep Research) that * runs for minutes and returns a cited report. So this purpose * admits all three contracts and the picker labels the rows for * what they are (Arman, 2026-09-26: "An agent can be a text * model, video, image, speech, live, decision, and now a research * model. It makes no difference."). Ruled by * `common-docs/systems/agents/typed-messages/FEATURE.md` * ("the picker shows only parts the selected model can consume" * — the author chooses the model first, so the model list may * never be the thing that hides the decision modality). * decision — a decision holder only (the decision playground, a decision * knob): a conversational model would answer in prose. * admin — catalog editing; every contract stays selectable. * * Whether a model is listed in the catalog never changes the execution contract of a picker. */ type ModelSelectionPurpose = "chat" | "agent" | "decision" | "admin"; declare function modelsForSelectionPurpose>(models: readonly T[], purpose?: ModelSelectionPurpose): T[]; /** * An agent's or a chat turn's instructions are TEXT. A non-live model whose * declared input has no text (speech-to-text: Whisper, Scribe) cannot take them, * whatever its interaction says (2026-09-27: Whisper sat in the chat set). A live * (realtime) session is configured rather than instructed per turn, so it is exempt. * Mirrors aidream `agent_service/resources.py::_is_agent_assignable`. */ declare function acceptsTextInstructions(capabilities: Pick & { input?: readonly string[]; }): boolean; /** Chat and agent launchers may select turn/single models that take text only. */ declare function isConversationalModelCapability(capabilities: Pick & { input?: readonly string[]; }): boolean; /** Fallback/replacement models must keep the same decision wire contract. */ declare function hasCompatibleDecisionInteraction(source: Pick, candidate: Pick): boolean; /** * Safe fallback used by `parseCapabilities` when no signal is available. * A text-only turn-based model — the most common shape. */ declare const DEFAULT_CAPABILITIES: ModelCapabilities; /** True iff `value` is a member of the const tuple `arr`. Type-narrow helper. */ declare function isContentType(value: unknown): value is ContentType; declare function isFeatureKey(value: unknown): value is FeatureKey; declare function isInteractionMode(value: unknown): value is InteractionMode; type CatalogKeyColumn = "id" | "name"; declare function catalogKeyColumn(value: string): CatalogKeyColumn; /** * Normalizes legacy model control keys and prompt settings keys to their * current backend equivalents. * * This runs once at the Redux boundary (hydrate / fetch) so downstream code * never encounters deprecated field names. * * Legacy → Current mapping: * max_tokens → max_output_tokens * output_format → response_format ONLY when it carries text response * formats (text / json_object / json_schema); an image * model's output_format (png / jpeg / webp) is the file * format — its own LLMParams field — and is never renamed. * n → count */ /** * THE output_format rule — every parser of model controls and settings uses it. * `output_format` is ambiguous in the catalog: on a legacy text model it IS the * response format (text / json_object / json_schema) and reads as * `response_format`; on an image model (GPT Image 2: png / jpeg / webp) it is * the image FILE format, a real LLMParams field of its own. Renaming the image * one showed GPT Image 2 a "Response Format" row whose choices stored nothing, * and made a stored `output_format` read as an unknown key. A control with no * enum keeps the legacy (text) reading. */ declare function outputFormatControlKey(key: string, control: unknown): string; interface HasControls { controls: unknown; } /** * Normalize a full model record fresh from the database. * Normalizes legacy keys inside `controls`. */ declare function normalizeModel(model: T): T; /** * Normalize a batch of models (array version of normalizeModel). */ declare function normalizeModels(models: T[]): T[]; /** * Normalize a prompt settings object from the database. * Converts legacy keys so downstream code only sees modern names. */ declare function normalizePromptSettings(settings: Record): Record; /** * `@ai-matrx/agents/models` — the diagnostics port. * * The capability parser screams about vocabulary drift (an unknown value in * `ai.model_definition.capabilities`). The scream always reaches `console.warn`; a host that * keeps an error store binds a sink here and gets the structured entry too. DEFAULT sink: none, * announced ONCE on the console so an unbound host is never silently quiet. State lives on * `globalThis` (dual-loader graphs must share it). */ interface ModelDiagnosticEntry { source: string; relation?: string; message: string; details?: string | undefined; } type ModelDiagnosticsSink = (entry: ModelDiagnosticEntry) => void; /** Bind (or, with `null`, unbind) the host's error store. */ declare function setModelDiagnosticsSink(sink: ModelDiagnosticsSink | null): void; /** One report per key per session; reaches the sink when bound, always the console. Never throws. */ declare function reportModelDiagnostic(dedupeKey: string, entry: ModelDiagnosticEntry): void; /** Identifies the model whose capabilities are being parsed, for loud reporting. */ interface CapabilitiesParseContext { modelId?: string; modelName?: string; } /** * Validate the canonical `{input, output, features, interaction, * multilingual}` object. Anything that is not that object — including `null` * for an unsaved row — yields the text-only turn-based default. * * Pass `context` (model id/name) wherever available — it's what makes the * unknown-value scream actionable. */ declare function parseCapabilities(raw: unknown, context?: CapabilitiesParseContext): ModelCapabilities; /** * The persistence/API boundary for `ai.model_definition.capabilities`. * * Unlike `parseCapabilities`, which is intentionally a resilient display * reader, this refuses a corrupt or provider-shaped value. A Supabase JSONB * column has no application vocabulary enforcement: accepting aliases here * would make them durable and leak them back out through another reader. */ declare function requireCanonicalCapabilities(raw: unknown, context?: CapabilitiesParseContext): ModelCapabilities; /** * Flat audit-shaped record. Re-exported here for callers that want the * derived projection without importing from the audit module. */ type AuditCapabilitiesRecord = Partial<{ text_input: boolean; text_output: boolean; image_input: boolean; image_output: boolean; audio_input: boolean; audio_output: boolean; video_input: boolean; document_input: boolean; code_execution: boolean; function_calling: boolean; streaming: boolean; vision: boolean; web_search: boolean; json_mode: boolean; structured_output: boolean; system_prompt: boolean; multi_turn: boolean; embeddings: boolean; fine_tuning: boolean; batch_api: boolean; }>; /** * Project the canonical shape onto the flat audit record so existing * audit consumers see the same view they always have. */ declare function toAuditRecord(caps: ModelCapabilities): AuditCapabilitiesRecord; /** * Merge an edited flat audit record back INTO the canonical capabilities * object — the write-side inverse of `toAuditRecord`. This exists because * writing the flat 20-boolean projection over the column destroys * `interaction` / `multilingual` / non-text output / the extended feature * vocabulary (the exact silent-drop class TASK-003 killed on the read side). * * Rules: * - Starts from the RAW stored object (arrays cloned) — every key and every * array member the editor doesn't model is preserved VERBATIM, including * values outside the FE vocabulary. * - Only flags whose edited value DIFFERS from the current projection are * applied (as an add/remove on the owning array), so an untouched Save is * a byte-for-byte no-op: `merge(raw, toAuditRecord(parse(raw))) === raw`. * - A flag with no canonical mapping is never written. */ declare function mergeAuditRecordIntoCapabilities(raw: unknown, edited: AuditCapabilitiesRecord, context?: CapabilitiesParseContext): Record; /** * Every value in `raw` that is outside the canonical vocabulary, as * human-readable lines. Empty array = the object is canonical. * * The admin detail panel writes `capabilities` straight to Supabase, so it * does NOT pass the server's write guard * (`matrx_ai/providers/capability_vocabulary.py`, which the agent `sql`/`db_*` * tools and `scripts/set_model_capabilities.py` go through). This is that * door's lock. It REFUSES rather than normalizes on purpose: the alias table * lives server-side and forking it into the browser is how the vocabulary * came apart in the first place. A hand-edit that means a real new capability * belongs in BOTH vocabulary lists first. */ declare function findNonCanonicalCapabilityValues(raw: unknown): string[]; /** * A model's CLASSES — the serving endpoints it is offered through ("Matrx Fast", * "Matrx Lightning", ...). Two classes of one model are different products, so * wherever a surface names the model an agent or run USES, a model with several * classes is named with its class: "Qwen3.8 27B · Matrx Lightning". * * Same grouping as the picker (`collapseTiersByClass` in useModelCatalog): one * class per endpoint (or brand when the endpoint is unknown), preferred * (lowest-priority) first. Every offering — including ones collapsed into a * class — maps to its class, so any stored `offering_id` resolves. */ type ModelOfferingRow = { model_id: string | null; offering_id: string | null; served_via: string | null; served_via_endpoint_id: string | null; priority: number | null; }; type ModelClassEntry = { /** Public Matrx brand of the serving endpoint — never a vendor name. */ servedVia: string; endpointId: string | null; /** The class's preferred offering (what the picker pins). */ offeringId: string; }; type ModelClassIndex = { /** modelId → its classes, preferred first. */ classesByModelId: Record; /** offeringId → { modelId, index into classesByModelId[modelId] }. */ classOfOffering: Record; }; declare function buildModelClassIndex(rows: ModelOfferingRow[]): ModelClassIndex; /** * The class name to show beside a model, or undefined when the model has a * single class (nothing to tell apart). A pin names its class; no pin (or a * pin of another model) means the server runs the preferred class. */ declare function modelClassName(index: ModelClassIndex | null | undefined, modelId: string | null | undefined, offeringId: string | null | undefined): string | undefined; /** "Model · Class" when the model has several classes, else the model name. */ declare function withModelClass(modelLabel: string, className: string | undefined): string; type FeatureBucket = "schema" | "tools" | "search" | "x_search" | "thinking" | "extraction" | "other"; interface FeatureBucketMeta { label: string; description: string; } /** Display order for the buckets (most broadly useful first). */ declare const FEATURE_BUCKET_ORDER: FeatureBucket[]; declare const FEATURE_BUCKETS: Record; interface GroupedFeatures { /** Buckets present on this model, in display order. */ buckets: FeatureBucket[]; /** All raw keys that mapped into each bucket (for the detail card). */ byBucket: Partial>; /** Every raw feature key on the model, unfiltered — nothing hidden. */ raw: string[]; } /** Group a model's raw feature keys into the user-facing buckets. */ declare function groupFeatures(features: string[]): GroupedFeatures; type PriceTier = "$" | "$$" | "$$$" | "$$$$" | "$$$$$" | "$$$$$+"; /** `cost_rating` of the top tier (Mythos, Fable, GPT-6 Astra, every "-max"): approved accounts only. */ declare const TOP_TIER_COST_RATING = 6; /** The one sentence a person sees on a top-tier model they may not run (the server says the same). */ declare const TOP_TIER_LOCKED_TITLE = "This model is limited to approved accounts"; /** True when a stored `cost_rating` is the top tier. */ declare function isTopTierRating(rating: number | null | undefined): boolean; /** Stored `cost_rating` (1-6 smallint; 6 = the "5+" outlier band) → tier. */ declare function costRatingTier(rating: number | null): PriceTier | null; /** Stored `speed_rating` → display label on the 1-5 scale ("5+" for 6). */ declare function speedRatingLabel(rating: number | null): string | null; /** * Compact speed word for dense pickers. Dots and a bare 1–6 number both fail * at row scale — a short word is what Linear/Cursor-class pickers show. * 1–2 Slow · 3 Med · 4 Fast · 5 Fast+ · 6 Max. */ declare function speedRatingWord(rating: number | null): string; /** * Compact RELATIVE-cost word for pickers — the same voice as `speedRatingWord`. * Never a run of dollar signs: members only ever see credits (Arman, * 2026-09-27: "Everyone should see credits/points except for system admins"), * and "$$$$" beside a model reads as money. 1 Low · 2 Low+ · 3 Med · 4 High · * 5 High+ · 6 (the "5+" outlier band) Max. */ declare function costRatingWord(rating: number | null): string; /** A `PriceTier` back to its 1-6 rating (6 = the "5+" band). */ declare function priceTierRating(tier: PriceTier): number; /** * USER-FACING name for the serving-tier concept (`ai.model_offering.served_via` * — the branded endpoint names "Matrx Lightning" / "Matrx Fast" / "Matrx * Standard"). The product word is "Service". Every user-visible surface * (filter labels, detail-card headings, availability chips) MUST read these * constants so the owner can rename the concept in ONE place. * * Reminder: the value shown next to this label is always the BRANDED * `served_via` name — real serving vendors (ai.endpoint.vendor) are secret and * live only behind the super-admin `admin_model_catalog` RPC. */ declare const SERVICE_LABEL = "Service"; declare const SERVICE_LABEL_PLURAL = "Services"; /** Availability chip text, e.g. "3 services". */ declare function serviceCountLabel(count: number): string; /** Canonical entity token for `ai.model_definition` rows. */ declare const MODEL_FAVORITE_ENTITY_TYPE = "ai_model"; declare function reconcileModelFavoriteIds(cached: readonly string[], canonical: readonly string[]): { merged: string[]; missingFromCanonical: string[]; }; declare function favoriteIdsEqual(a: readonly string[], b: readonly string[]): boolean; type ModelFavoritesRpcResult = PromiseLike<{ data: unknown; error: { message: string; code?: string; } | null; }>; interface ModelFavoritesClient { rpc(fn: string, args?: Record): ModelFavoritesRpcResult; /** `users.ues_list` lives in its owner schema (the public forwarder closes). */ schema?(name: string): { rpc?(fn: string, args?: Record): ModelFavoritesRpcResult; }; } interface ModelFavoritesCache { get(): readonly string[]; set(ids: string[]): void; /** Notified when the cache changes from outside (e.g. the preferences store hydrates). */ subscribe?(listener: () => void): () => void; } type ModelFavoritesErrorSink = (event: { code: string; message: string; context?: object; }) => void; interface ModelFavoritesConfig { /** * The Supabase client — or a getter, for a host whose module singleton can be the SSR-built client * with no live session (resolved at CALL time so the write never hangs on a stale one). */ client: ModelFavoritesClient | (() => ModelFavoritesClient); cache?: ModelFavoritesCache; errorSink?: ModelFavoritesErrorSink; } interface ModelFavoritesState { favoriteIds: readonly string[]; } interface ModelFavorites { getState(): ModelFavoritesState; subscribe(listener: () => void): () => void; /** Merge the canonical ledger into the cache and back-fill what the ledger lacks. Never throws. */ reconcile(): Promise; toggle(id: string): void; } declare function createModelFavorites(config: ModelFavoritesConfig): ModelFavorites; /** * `@ai-matrx/agents/models` — the model catalog's row types (display-shaped). * Moved verbatim from matrx-frontend `features/ai-models/hooks/useModelCatalog.ts` (agent core A1). * Pure types and constants. */ type ModelCatalogVariant = "user" | "admin"; /** Alias of the canonical capabilities vocabulary — never a parallel union. */ type Modality = ContentType; declare const INPUT_MODALITIES: Modality[]; declare const OUTPUT_MODALITIES: Modality[]; /** Alias of the canonical interaction vocabulary — never a parallel union. */ type Interaction = InteractionMode; /** * One serving tier for a model — a row of `ai.model_offering`, identified to * users ONLY by the endpoint's public Matrx brand (`served_via`: "Matrx Fast", * "Matrx Lightning", "Matrx Standard", ...). The real vendor never leaves * `ai.model_admin`. `endpointId` is the opaque uuid a future request-wire * endpoint hint would echo back to the server (resolve_call_profile). */ interface CatalogTier { offeringId: string; /** Public Matrx brand of the serving endpoint — never a vendor name. */ servedVia: string; /** Opaque endpoint uuid (safe wire hint) — no vendor identity. */ endpointId: string | null; /** Lower = preferred/faster (the server's default pick). */ priority: number; /** Points per 1M input tokens on this offering — the user currency. */ pointsInput: number | null; /** Points per 1M output tokens on this offering — the user currency. */ pointsOutput: number | null; } /** * One real $ pricing band from `ai.offering.pricing` (array of tiers: * `{ max_tokens, input_price, output_price, cached_input_price }`, prices in * $ per million tokens unless `usage_basis` says otherwise). ADMIN eyes only. */ interface AdminPricingBand { maxTokens: number | null; inputPrice: number | null; outputPrice: number | null; cachedInputPrice: number | null; } /** * One full `ai.offering` row joined to its endpoint + api — the exact call * (model × endpoint × api × provider_model_id) with REAL vendor/wire/$ data. * ADMIN eyes only; never rendered outside the admin variant. */ interface AdminOffering { offeringId: string; /** The actual model id sent on a raw call to the vendor. */ providerModelId: string | null; /** Real serving vendor (ai.endpoint.vendor) — never user-facing. */ vendor: string | null; endpointId: string | null; endpointDisplayName: string | null; endpointInternalName: string | null; apiId: string | null; apiName: string | null; translatorKey: string | null; transport: string | null; /** Real $ pricing bands (parsed from ai.offering.pricing). */ pricing: AdminPricingBand[]; usageBasis: string | null; tokenBilled: boolean | null; /** Lower = preferred (the server's default pick). */ priority: number; isAvailable: boolean; /** Points per 1M input tokens (user currency) — admin shows $ AND points. */ pointsInput: number | null; /** Points per 1M output tokens (user currency). */ pointsOutput: number | null; pointsCachedInput: number | null; } /** Reshaped, render-ready model row. */ interface CatalogModel { id: string; /** Pretty, business-facing name only — never a technical identifier. */ name: string; /** Who MADE the model (stored `maker` — never a serving vendor). */ maker: string | null; /** Curated cost rating, 1-6 smallint (6 renders as the "5+" band). */ costRating: number | null; /** Curated speed rating, 1-6 smallint (6 renders as "5+"). */ speedRating: number | null; input: Modality[]; output: Modality[]; features: GroupedFeatures; interaction: Interaction; multilingual: boolean; contextWindow: number | null; maxTokens: number | null; isPrimary: boolean; isPremium: boolean; /** * Deprecated = hidden from a normal user's DEFAULT list (the picker's * "Include deprecated" toggle reveals it) but fully runnable — ruled * 2026-09-09. Admin variant always shows it, badged. */ isDeprecated: boolean; /** The dead state (ai_075): the provider no longer serves it. Shown for identity, never selectable. */ retiredAt: string | null; /** Explicit replacement named in the deprecation warning / retirement refusal. */ successorId: string | null; /** OUTPUT cost basis used for the price tier (points for user view). */ outputCost: number | null; /** Points per 1M input tokens — THE user currency, prominently displayed. */ pointsInput: number | null; /** Points per 1M output tokens — THE user currency, prominently displayed. */ pointsOutput: number | null; usageBasis: string | null; tokenBilled: boolean | null; description: string | null; releaseDate: string | null; /** * Serving CLASSES (priority-ordered, preferred first), one tier per class — * equivalent offerings within a class are collapsed to its preferred one. * Multi-class models (e.g. GPT OSS 120B on Matrx Lightning / Matrx Fast / * Matrx Standard) are separate choices; the picker lists one row per class * and the chosen class travels as the offering pin (`offering_id`). */ tiers: CatalogTier[]; /** admin-only extras — present only for variant === "admin" */ admin?: { pricing: unknown; /** Serving vendor (ai.endpoint.vendor) — ADMIN eyes only, never user-facing. */ vendor: string | null; endpointId: string | null; endpointDisplayName: string | null; endpointInternalName: string | null; apiId: string | null; apiName: string | null; /** Wire-contract translator (ai.api.translator_key — the old wire_format vocabulary). */ translatorKey: string | null; transport: string | null; offeringId: string | null; providerModelId: string | null; /** * EVERY offering of this model (priority-ordered, preferred first) with * real vendor/api/provider_model_id/$ pricing — total transparency. */ offerings: AdminOffering[]; }; } /** * `@ai-matrx/agents/models` — the model catalog's RECORDS part (agent core B3). * * What a client needs about one model beyond the picker row: the options list every dropdown * shows, one model's full record (resolved controls / constraints), one pinned class's config, * the class index that names a class beside a model, and the durable label of a model that left * the catalog. It replaces chat's retired `modelRegistry` Redux slice — same reads, same state * shape, same never-downgrade rules — and is created by `createModelCatalog` (`catalog.records`), * so a page holds ONE model catalog. Framework-free; reads only. * * options → `ai.model_offering` (routable ids) + `ai.model_public` * full record → `ai.model_config` (signed in) / `ai.model_public` (guest), one row * class config → `ai.resolve_model_config(p_model_id, p_offering_id)` * classes → `ai.model_offering` * identity → `ai.model_definition` (id, name, common_name, is_deprecated) */ /** Structurally the generated `Json` — a host's typed rows assign both ways. */ type ModelJson = string | number | boolean | null | { [key: string]: ModelJson | undefined; } | ModelJson[]; /** What a record currently holds: `options` (list columns) or `full` (resolved controls too). Never downgrades. */ type ModelRecordFetchType = "options" | "full"; /** Which ids the options list covers. */ type ModelFetchScope = "active" | "deprecated" | "all" | null; /** One `ai.model_definition` row as the catalog holds it, plus the resolved maker / controls. */ interface ModelRecord { _fetchType: ModelRecordFetchType; id: string; name: string; common_name: string | null; /** Resolved maker name (`ai.provider.name`), attached by the read. */ maker: string | null; capabilities: ModelJson | null; /** Resolved from `ai.model_config` on `full` records only. */ controls: ModelJson | null; constraints: ModelJson | null; context_window: number | null; cost_rating: number | null; speed_rating: number | null; max_tokens: number | null; is_deprecated: boolean | null; is_premium: boolean | null; is_primary: boolean | null; is_system: boolean; retired_at: string | null; successor_id: string | null; deleted_at: string | null; description: string | null; release_date: string | null; provider_id: string | null; guest_fallback_id: string | null; mid_fallback_id: string | null; retry_fallback_id: string | null; retry_max_attempts: number; organization_id: string; created_by: string | null; created_at: string; updated_at: string; updated_by: string | null; version: number; metadata: ModelJson; visibility: string; shown_to: string | null; published_to_web: boolean; published_to_web_at: string | null; published_to_web_by: string | null; } type ModelIdentityRow = Pick; /** * What the picker list's read of the catalog returned, raw: every `ai.model_public` row (with at least the * options columns) and every `ai.model_offering` row (`ModelOfferingRow` columns). `records.refresh(listed)` * answers the options list, the identities and the class index from it instead of reading them again. */ interface ModelCatalogListRows { publicRows: Array>; offeringRows: ModelOfferingRow[]; } type ModelClassConfig = { modelId: string; controls: ModelJson | null; constraints: ModelJson | null; }; /** Class configs are keyed by model AND class — a pin is only meaningful for its model. */ declare const classConfigKey: (modelId: string, offeringId: string) => string; interface ModelRecordsState { entities: Record; activeIds: string[]; deprecatedIds: string[]; fetchScope: ModelFetchScope; isLoading: boolean; error: string | null; lastFetched: number | null; detailStatusById: Record; detailErrorById: Record; identityById: Record; identityStatusById: Record; classConfigByOffering: Record; classConfigStatusByOffering: Record; modelClassIndex: ModelClassIndex | null; modelClassStatus?: "loading" | "succeeded" | "failed"; } declare const EMPTY_MODEL_RECORDS: ModelRecordsState; interface PgResult { data: unknown; error: { message: string; code?: string; } | null; } interface RecordsQuery extends PromiseLike { select(columns: string): RecordsQuery; in(column: string, values: readonly unknown[]): RecordsQuery; eq(column: string, value: unknown): RecordsQuery; order(column: string, options?: { ascending?: boolean; nullsFirst?: boolean; }): RecordsQuery; maybeSingle(): PromiseLike; /** Only the catalog stamp (`readModelCatalogStamp`) uses it. */ limit?(count: number): RecordsQuery; } interface ModelRecordsClient { schema(name: string): { from(table: string): RecordsQuery; rpc(fn: string, args?: Record): PromiseLike; }; } interface ModelRecordsConfig { client: ModelRecordsClient; /** OPTIONAL — DEFAULT: signed in. A guest reads the world-readable `ai.model_public` instead of `ai.model_config`. */ isSignedIn?: () => boolean | Promise; errorSink?: (event: { code: string; message: string; context?: object; }) => void; } interface ModelRecords { getState(): ModelRecordsState; subscribe(listener: () => void): () => void; /** The routable options list (live + deprecated). Skips once known. Never throws. */ loadOptions(): Promise; /** One model's full record. Skips when full, loading or failed (see `retryModel`). Never throws. */ loadModel(modelId: string): Promise; /** Clear a failed full read so the next `loadModel` runs. */ retryModel(modelId: string): void; /** One pinned class's resolved controls. A failure is retried on the next ask. Never throws. */ loadClassConfig(modelId: string, offeringId: string): Promise; /** Every routable model's classes (one read). Never throws. */ loadClasses(): Promise; /** * ONE model's classes — what a closed pill needs to name the class it runs on, * without reading every offering. Merged into the same index; a no-op once the * full index (or this model's slice) is known. Never throws. */ loadModelClasses(modelId: string): Promise; /** The durable label of a model outside the options list. Never throws. */ loadIdentity(modelId: string): Promise; /** * The durable labels of SEVERAL models in ONE read (`ai.model_definition … in(id)`) — what an open * menu names (its presets' models), never the options list. Skips every id already named or asked; * a name-shaped key falls back to `loadIdentity`. Never throws. */ loadIdentities(modelIds: readonly (string | null | undefined)[]): Promise; /** * The catalog changed upstream (a `catalog_changed` notice: an admin renamed, offered or retired a model): * re-read everything this instance already read — the options list, every full record, every identity, * the class index and slices, every pinned class's controls — and swap each in when it answers. The old * rows stay on screen until then (no blank pill, no flicker); a failed re-read keeps them and reports * through the error sink. Reads nothing that was never read. Never throws. * * Few requests, however many labels are mounted: full records are ONE `in (...)` read, identities are * ONE `in (...)` read of only the ids no list row covers, model class slices are ONE read. `listed` is * the picker list's own fresh re-read of `ai.model_public` + `ai.model_offering` (the catalog passes it, * `createModelCatalog().refresh()`): when given, the options list, the identities it covers and the class * index are answered from it, so one edit costs the page that one list read. */ refresh(listed?: Promise): Promise; /** * Answer `loadModel` + `loadModelClasses` for one model from rows a batched * route read already returned (`chat.route_seed`): its `ai.model_config` row * (the `loadModel` columns) and its `ai.model_offering` rows. Both later calls * become no-ops. A row that fails the same checks `loadModel` runs is * reported and skipped, so the model's own read still runs. Never throws. */ seedModel(payload: { modelId: string; config: Record | null; offerings: ModelOfferingRow[]; }): void; /** Server-shell hydration: options- or full-level rows. */ hydrate(payload: { models: Array & { id: string; }>; fetchType: ModelRecordFetchType; fetchScope: ModelFetchScope; lastFetched: number; }): void; } declare function createModelRecords(config: ModelRecordsConfig): ModelRecords; declare function selectActiveModels(s: ModelRecordsState): ModelRecord[]; declare function selectDeprecatedModels(s: ModelRecordsState): ModelRecord[]; declare const selectAllModels: (s: ModelRecordsState) => ModelRecord[]; interface ModelOption { value: string; label: string; maker: string | null; costRating: number | null; speedRating: number | null; } /** Active models as dropdown options. */ declare const selectModelOptions: (s: ModelRecordsState) => ModelOption[]; interface ModelOptionWithLifecycle { value: string; label: string; maker: string | null; isDeprecated: boolean; isRetired: boolean; successorId: string | null; } /** All models (active + deprecated) as options; `isRetired` rows are identity-only, never selectable. */ declare const selectAllModelOptions: (s: ModelRecordsState) => ModelOptionWithLifecycle[]; declare const selectModelById: (s: ModelRecordsState, modelId: string | null | undefined) => ModelRecord | undefined; /** Display label for a model id (options, full or identity). */ declare function selectModelLabelById(s: ModelRecordsState, modelId: string | null | undefined): string | undefined; declare const selectModelNameById: (s: ModelRecordsState, modelId: string | null | undefined) => string | undefined; /** The CLASS to name beside a model, or undefined when it has one class. */ declare const selectModelClassName: (s: ModelRecordsState, modelId: string | null | undefined, offeringId: string | null | undefined) => string | undefined; /** THE label for the model an agent or run uses: "Qwen3.8 27B · Matrx Lightning" when it has several classes. */ declare function selectModelLabelWithClass(s: ModelRecordsState, modelId: string | null | undefined, offeringId: string | null | undefined): string | undefined; declare const selectModelControls: (s: ModelRecordsState, modelId: string) => ModelJson | undefined; declare const selectModelFetchType: (s: ModelRecordsState, modelId: string) => ModelRecordFetchType | undefined; declare const selectModelFullyLoaded: (s: ModelRecordsState, modelId: string | null | undefined) => boolean; declare const selectModelDetailError: (s: ModelRecordsState, modelId: string | null | undefined) => string | null; declare const selectModelClassConfig: (s: ModelRecordsState, modelId: string | null | undefined, offeringId: string | null | undefined) => ModelClassConfig | undefined; declare const selectActiveModelsReady: (s: ModelRecordsState) => boolean; declare const selectDeprecatedModelsReady: (s: ModelRecordsState) => boolean; declare const selectAllModelsReady: (s: ModelRecordsState) => boolean; declare const selectModelIdentityById: (s: ModelRecordsState, modelId: string) => ModelIdentityRow | undefined; declare const selectModelIdentityLookupStatus: (s: ModelRecordsState, modelId: string) => "idle" | "loading" | "succeeded" | "failed"; /** * The durable identities (id, name, common_name, is_deprecated) of these models, read from * `ai.model_definition` — the same read `records.loadIdentity` makes. For a process with no records * store that stamps a name ONCE (a sync snapshot); a screen names models through the records hooks. * Rows the database does not return are absent. Throws the database's error. */ declare function readModelIdentities(client: ModelRecordsClient, modelIds: readonly string[]): Promise; /** The structural read `readPublicModel` needs (a supabase-js client fits it as is). */ interface PublicModelClient { schema(name: "ai"): { from(table: "model_public"): { select(columns: string): { eq(column: string, value: string): { maybeSingle(): PromiseLike<{ data: unknown; error: { message: string; } | null; }>; }; }; }; }; } /** * One `ai.model_public` row (id + canonical name) by id or name, read fresh — for a value sent on the wire at * the moment it is used (a voice session's transcription model), never for a label on screen. */ declare function readPublicModel(client: PublicModelClient, key: string): Promise<{ row: { id?: unknown; name?: unknown; } | null; error: string | null; }>; /** * THE catalog stamp: the newest `ai.model_definition.updated_at` (one row, one column) — the cheap read a * tab makes to learn whether the catalog changed while it was not listening (catalog/changes.ts * `followModelCatalogVisibility`). Null when the catalog is empty. Throws the database's error. */ declare function readModelCatalogStamp(client: ModelRecordsClient): Promise; /** * `@ai-matrx/agents/models` — `createModelCatalog`: the headless model catalog store. * * Built the way `createAgentCatalog` is built (framework-free; `getState` / `subscribe`; React * bindings live in `../react`). It is the ONE holder of the picker's catalog rows. Two variants, * each reading its canonical view directly (client → Supabase): * * - "user" → `ai.model_public` (anon + authenticated; masked, points pricing), filtered to * ROUTABLE models via `ai.model_offering`, which also supplies each model's serving * TIERS (`served_via` — the endpoint's public Matrx brand, never the real vendor). * - "admin" → `ai.admin_model_catalog()` + `ai.admin_model_offerings()` (SECURITY * DEFINER, gated by `is_super_admin()`): raw $ pricing, real vendors, every * offering. NEVER read `ai.model_admin` / `ai.offering` directly from a client. * * Loud, no silent fallback: a read failure surfaces the real PostgREST error in `error` and to * the `errorSink`. Reads only — this store writes nothing. */ interface ModelCatalogPgError { message: string; code?: string; } interface ModelCatalogPgResult { data: unknown; error: ModelCatalogPgError | null; } /** The thenable PostgREST builder, structurally (the real builder's own methods). */ interface ModelCatalogQuery extends PromiseLike { select(columns: string): ModelCatalogQuery; order(column: string, options?: { ascending?: boolean; nullsFirst?: boolean; }): ModelCatalogQuery; eq(column: string, value: unknown): ModelCatalogQuery; maybeSingle(): PromiseLike; } /** REQUIRED. A structural subset of `SupabaseClient`. */ interface ModelCatalogClient { rpc(fn: string, args?: Record): PromiseLike; schema(name: string): { from(table: string): ModelCatalogQuery; /** Present on every supabase-js schema scope; optional only so read-only fixtures stay valid. */ rpc?(fn: string, args?: Record): PromiseLike; }; } type ModelCatalogErrorSink = (event: { code: string; message: string; context?: object; }) => void; interface ModelCatalogConfig { /** Re-read on every model catalog change notice (catalog/changes.ts). Default true. */ followChanges?: boolean; client: ModelCatalogClient; /** OPTIONAL — DEFAULT: a tagged `console.error` (the in-picker error banner always renders too). */ errorSink?: ModelCatalogErrorSink; /** A cached variant older than this reloads in the background on the next `load`. Default 60s. */ staleAfterMs?: number; /** OPTIONAL — DEFAULT: signed in. A guest's full model reads use the world-readable `ai.model_public` (`records`). */ isSignedIn?: () => boolean | Promise; } interface ModelCatalogVariantState { /** idle = never requested; loading = first read in flight (no rows yet); ready/error = settled. */ status: "idle" | "loading" | "ready" | "error"; models: CatalogModel[]; error: string | null; loadedAt: number | null; } interface ModelCatalogState { user: ModelCatalogVariantState; admin: ModelCatalogVariantState; } interface ModelCatalog { getState(): ModelCatalogState; subscribe(listener: () => void): () => void; /** Read the variant if it was never read, or is stale. Resolves when settled. Never throws. */ load(variant: ModelCatalogVariant): Promise; /** Re-read the variant now (rows stay visible while it runs). Never throws. */ reload(variant: ModelCatalogVariant): Promise; /** * The catalog changed upstream (`catalog_changed`): re-read every picker variant already read and every * record already read (`records.refresh()`). Called by itself on every `announceModelCatalogChanged` * (catalog/changes.ts) unless `followChanges: false`. Never throws. */ refresh(): Promise; /** Stop following `catalog_changed` (a test, or a catalog a host discards). */ dispose(): void; /** * The RECORDS part (agent core B3): options list, full records with resolved controls, class * configs, the class index and identities — what chat's retired `modelRegistry` slice held. */ records: ModelRecords; } /** Keep the preferred tier per class, priority-ordered. */ declare function collapseTiersByClass(tiers: CatalogTier[]): CatalogTier[]; declare function createModelCatalog(config: ModelCatalogConfig): ModelCatalog; /** * `@ai-matrx/agents/models` — a preferred default model is a (model, class) pair. * * A model offered as Matrx Fast and Matrx Lightning is two products, and the class rides beside * the model as `offering_id` (LLMParams.offering_id) or the server runs the model's preferred * class. The class lives in the model knob's companion, written by the settings row at the same * rung as the model. The two knobs resolve independently, so a class can outlive its model; a class * that does not belong to the resolved model is dropped LOUDLY — never sent, because the server * refuses a pin that is not an offering of the model. * * Moved from matrx-frontend `features/ai-models/preferredModelChoice.ts` (agent core A1). The * scoped-config ladder (`resolveSessionKnob`, the class-knob companion) is app-owned, so it comes * in through the `PreferredModelChoicePorts` port. */ type PreferredModelChoice = { modelId: string; /** The chosen class's `ai.offering` id; null = the model's preferred class. */ offeringId: string | null; }; interface PreferredModelChoicePorts { /** The Supabase client (public offering view read). */ client: ModelCatalogClient | (() => ModelCatalogClient); /** Resolve a settings-ladder knob by its full key for this session. */ resolveKnob(fullKey: string): Promise; /** The class-pin companion knob of a model knob (its full key), or null when it has none. */ classKnobFor(modelKnob: string): { fullKey: string; } | null; /** The offering id a class-knob value pins, or null. */ classPinOf(value: unknown): string | null; } /** * Resolve a default-model knob and its class companion for this session. Null when no model is * set. A read failure of the class answers the model alone (announced) — a run never blocks on a * preference. */ declare function resolvePreferredModelChoice(ports: PreferredModelChoicePorts, modelKnob: string, tag: string): Promise; /** The knob holding a person's preferred chat model ("the model that answers when you have not picked one"). */ declare const CHAT_DEFAULT_MODEL_KNOB = "agents.model_prefs.chat_default_model"; /** The class knob's address for a model knob, or null when it has none. */ declare function modelClassKnobFor(modelFullKey: string): { feature: string; key: string; fullKey: string; } | null; /** A stored class value as a pin: a non-empty string, else undefined. */ declare function modelClassPinOf(value: unknown): string | undefined; /** * `models/succession.ts` — THE one place model succession is resolved. * * A deprecated or retired model may name a `successor_id` (ai.model_public). Every picker and every * host asks this function which model a saved id now means, so a superseded model is never offered * and a saved setting naming one heals to its successor. Cycle-safe, capped at 8 hops. A broken * chain (a cycle, a missing successor, a chain that never reaches a live model) returns the * ORIGINAL id and logs once per id, so the picker keeps its deprecated/retired badge instead. */ /** The fields succession reads from a catalog row (`CatalogModel` satisfies it). */ interface SuccessionNode { id: string; isDeprecated: boolean; retiredAt: string | null; successorId: string | null; } type SuccessionIndex = ReadonlyMap; declare const MAX_SUCCESSION_HOPS = 8; declare function buildSuccessionIndex(models: readonly SuccessionNode[]): SuccessionIndex; /** Live = neither deprecated nor retired. */ declare function isSupersedable(node: SuccessionNode): boolean; /** * The model `modelId` now means: itself when it is live, has no successor, or is unknown to the * index; otherwise the first live model along its `successorId` chain. */ declare function resolveSuccessor(modelId: string, index: SuccessionIndex): string; /** Test seam: forget which broken chains were already logged. */ declare function resetSuccessionLogForTests(): void; /** The read `resolveLiveModelId` makes: a supabase-js client fits it as is. */ interface SuccessionClient { schema(name: "ai"): { from(table: "model_public"): { select(columns: string): { in(column: string, values: readonly string[]): PromiseLike<{ data: unknown; error: { message: string; } | null; }>; }; }; }; } /** * The model a NEW record should be written with: `modelId` followed along its successor chain, read * hop by hop from `ai.model_public`. A creation default pinned in code (a blank agent's model) * outlives the model it names; writing it as is made every new agent start on a superseded model * that the picker then healed on first view, so a brand-new agent showed an unsaved model change * nobody made (Lane D walk, 2026-10-21). A read failure keeps `modelId` and logs: creation never * fails over a name. */ declare function resolveLiveModelId(client: SuccessionClient, modelId: string): Promise; /** * `@ai-matrx/agents/models` — downloadable (local) models: the catalog shape every client reads, the * device's hardware classes, the ONE memory-fit function and the ONE variant selection * (unified-models design §1.4a, §1.6, §1.7). Pure: no I/O, no defaults that are opinions — the share * of memory a model may use is a knob the caller passes in. * * The catalog shape is `matrx.model-catalog.snapshot/v1`, exported from the database by aidream * `scripts/export_model_catalog_snapshot.py`; a live fetch is mapped to the same shape. */ type HardwareClass = "apple_metal" | "x86_cpu" | "arm_cpu" | "nvidia_cuda" | "vulkan_gpu"; type ArtifactUse = "core" | "vision" | "audio_in" | "voices"; type CompatStatus = "verified" | "expected" | "broken"; interface LocalArtifact { path: string; role: string; url: string; size_bytes: number; sha256: string; shard_index: number | null; shard_count: number | null; required_for: ArtifactUse[]; } interface LocalVariant { id: string; key: string; /** `/`: the string key the device protocol carries. */ catalog_key: string; format: string; quant: string; architecture: string; chat_template_kind: string; license: string | null; gated: string; total_bytes: number | null; memory_bytes_min: number | null; memory_bytes_recommended: number | null; min_app_version: string | null; /** Empty = every os/arch; else `/` values. */ os_arch: string[]; selection_rank: number; is_default: boolean; tested_on_current_runtime: boolean; upstream: { kind: string; repo: string | null; revision: string | null; } | null; artifacts: LocalArtifact[]; /** Set only on a variant that came from a pasted link or a search (`fromDiscovery`); catalog rows never carry it. */ discovery?: VariantDiscovery; /** ai.model_variant.review_state (set by `readModelCatalog`; "offered" = listed in apps). */ review_state?: string; /** The public.catalog_entries rows this variant carries (metadata.catalog_entry_ids; set by `readModelCatalog`). */ catalog_entry_ids?: string[]; } /** What discovery said about a variant: the server's own words, carried so the one judge can show them. */ interface VariantDiscovery { /** The server's verdict; null = the server did not say (an older server). */ runnable: boolean | null; why_not: string | null; /** Who published the files (the original maker or a conversion). */ publisher: string | null; /** The repository these files come from (the original or a conversion). */ from_repo: string | null; /** Problems the server found that block this variant (an ambiguity), in words. */ problems: string[]; /** Which runtime runs it (the server's words); absent from older servers. */ runtime?: DiscoveryRuntime | null; } interface LocalPresentation { summary: string | null; symbol: string | null; color: string | null; page: string | null; shelf: string | null; shelf_title: string | null; shelf_subtitle: string | null; params_label: string | null; tags: string[]; links: Array<{ label: string; url: string; }>; thumbnail_file_id: string | null; /** The admin's curated position (ai.model_presentation.display_order); `orderCatalogModels` sorts by it. */ display_order?: number | null; hero_file_id?: string | null; gallery_file_ids?: string[]; /** Admin display facts (ai.model_presentation.facts): ratings, speed, cutoff, tier, notes. Always set by `readModelCatalog`. */ facts?: Record; } interface LocalModel { id: string; /** The model's name; with no variant it is also the key of its default variant (old protocol ids). */ name: string; common_name: string | null; description: string | null; maker: string | null; capabilities: { input?: string[]; output?: string[]; interaction?: string; multilingual?: boolean; [k: string]: unknown; }; presentation: LocalPresentation | null; variants: LocalVariant[]; /** Matrx 2's 1.48 id for a model seeded from its old list (metadata.desktop_id). */ desktop_id?: string | null; maker_logo_file_id?: string | null; /** The admin Catalogs kind (`catalogKindOf`): llm_model, whisper_model, tts_model_file, image_gen_model, video_gen_model, ner_model, wake_word_model, lora, embedding_model, or unknown (no rule places it; `catalogKindVerdict` says why). */ kind?: string; /** At least one variant is offered (Matrx 2 lists it). */ offered?: boolean; /** The later of the model's and its presentation's updated_at: what an admin edit sends as expected_updated_at. */ updated_at?: string | null; /** * Set only when `readModelCatalog(client, { devices })` asked: the computers that hold every required file of * one of this model's listed variants (models/device-models.ts `availableOnDevices`). Absent otherwise. */ available_on?: Array<{ app_instance_id: string; variant_id: string; device_model_file_ids: string[]; }>; } interface LocalCompatRow { hardware_class: HardwareClass; format: string; quant: string; architecture: string; chat_template_kind: string; status: CompatStatus; } interface LocalRuntimeBuild { id: string; version: string; os: string; arch: string; accelerator: string; build_kind: string; url: string | null; size_bytes: number | null; sha256: string | null; is_active: boolean; min_app_version: string | null; /** The architectures this pinned build loads (the runtime build row's data); null/absent = not published, so not checked. */ known_architectures?: string[] | null; compat: LocalCompatRow[]; } interface LocalRuntime { key: string; name: string; upstream_repo: string | null; builds: LocalRuntimeBuild[]; } interface LocalModelCatalog { $schema: "matrx.model-catalog.snapshot/v1"; catalog_updated_at: string; models: LocalModel[]; runtimes: LocalRuntime[]; /** * sha256 of the whole shaped catalog (canonical JSON, this field left out), set by `readModelCatalog`: * changes on ANY change (unoffer, retire, delete, maker rename), where catalog_updated_at only moves * when a stamped row's updated_at does. */ catalog_fingerprint?: string; /** Reads that failed but did not stop the catalog (each one line, said so a screen can show it). */ warnings?: string[]; } /** Whether a value is a v1 catalog with the fields every reader relies on (a cache or a fetch must pass this before it replaces anything). */ declare function isLocalModelCatalog(value: unknown): value is LocalModelCatalog; interface DeviceGraphics { name: string; vendor?: string | null; memory_bytes?: number | null; unified?: boolean; /** A CUDA driver version, when the device reported one. */ cuda_driver?: string | null; vulkan?: boolean | null; } interface DeviceHardware { /** `darwin`, `linux`, `windows` (`win32` accepted). */ os: string | null; /** `arm64`, `x86_64` (`x64`/`amd64` accepted). */ arch: string | null; memory_bytes: number | null; graphics?: DeviceGraphics[] | null; } interface HardwareClasses { /** Best first. */ classes: HardwareClass[]; /** Set when the facts were missing: shown beside the choice, never hidden. */ label: "hardware not detected" | null; } declare function normalizeArch(arch: string | null | undefined): "arm64" | "x86_64" | null; /** The catalog's os names (`darwin`, `linux`, `windows`); Node's `win32` maps to `windows`. */ declare function normalizeOs(os: string | null | undefined): string | null; /** The device's hardware classes, best first (design §1.6). Missing facts ⇒ the processor class, labeled. */ declare function hardwareClassesOf(hw: DeviceHardware | null | undefined): HardwareClasses; /** Whether `memoryBytes` fits `totalMemoryBytes × fitFraction`. Null when either side is unknown ("not measured", never a pass). */ declare function fitsMemory(memoryBytes: number | null, totalMemoryBytes: number | null, fitFraction: number): boolean | null; type ModelFit = "yes" | "slow" | "no" | "unknown"; /** * Whether a model runs on this computer: it must fit memory; with a graphics adapter that shares the * computer's memory (Apple silicon) or has enough of its own it runs well, otherwise on the processor. * `smallBytes`: at or under this a model answers quickly on any recent processor. */ declare function fitsThisComputer(memoryBytes: number | null, hw: { memory: number | null; graphics: ReadonlyArray<{ memory_bytes: number | null; unified: boolean; name: string; }>; }, fitFraction: number, smallBytes?: number): ModelFit; interface Want { variant_policy: "best_fit" | "exact"; /** Variant key for `exact`. */ variant_key?: string | null; /** Optional uses wanted beyond `core` (a projector is fetched only when `vision` is here). */ uses: ArtifactUse[]; } interface SelectDevice { os: string | null; arch: string | null; classes: HardwareClass[]; memory_bytes: number | null; /** The share of memory a model may use (knob desktop.models/memory_fit_fraction). */ fit_fraction: number; /** This app's version; variants needing a later app are left out (counted by `catalogForApp`). */ app_version: string | null; /** Formats whose runtime ships inside the app (no runtime_build row, e.g. faster-whisper's pip lock). */ app_runtime_formats: string[]; } type DecidedBy = "only" | "exact" | "verified" | "rank" | "size" | "key"; interface Selection { ok: true; variant: LocalVariant; /** The runtime build it runs on; null for an app-shipped runtime. */ build: LocalRuntimeBuild | null; hardware_class: HardwareClass | null; compat: CompatStatus | "app_runtime"; fits: boolean | null; decided_by: DecidedBy; /** One line for the screen: what was chosen and why. */ reason: string; /** Exactly the files to fetch: `core`, plus those whose uses overlap the want's. */ fetch: LocalArtifact[]; } interface NoSelection { ok: false; code: "no_variant" | "os_arch" | "app_version" | "runtime_missing" | "not_compatible" | "does_not_fit" | "exact_missing"; reason: string; } /** The files of a variant a device fetches for these uses: every `core` file plus any whose uses overlap. Optional files (empty required_for) never. */ declare function requiredArtifacts(variant: LocalVariant, uses: readonly ArtifactUse[]): LocalArtifact[]; /** * Memory as the computer's own settings show it: binary gigabytes (2^30 bytes), labeled GB the way * macOS, Windows and Linux label installed memory. One unit for every memory figure a screen shows — * a reason line in decimal GB beside a stats strip in binary GB read as two different memories. */ declare function formatMemoryBytes(n: number): string; /** One entry per variant: whether it can be used here, every reason in plain words, and the one recommended. */ interface RankedVariant { variant: LocalVariant; usable: boolean; /** Plain lines for the screen: the problem first when it is unusable, then what it needs. */ reasons: string[]; /** Exactly one usable variant is recommended (none when nothing is usable). */ recommended: boolean; /** Fits this computer's memory: null when unknown or on the web. */ fits: boolean | null; /** The runtime build it runs on here (null on the web or for an app-shipped runtime). */ build: LocalRuntimeBuild | null; /** How it runs here: tested, expected (not yet tested), Matrx's own engine; null when it does not run or on the web. */ compat: CompatStatus | "app_runtime" | null; /** The name of the runtime it runs on ("llama.cpp", "Matrx’s own engine"); null when it does not run or is unknown. */ engine: string | null; } /** The parts of a sharded set that are missing ("part 2 of 3"), from the files' own shard numbers. */ declare function missingShards(variant: LocalVariant): Array<{ index: number; count: number; }>; /** The active llama-server build for this computer that runs GGUF, or null. */ declare function ggufEngine(runtimes: readonly LocalRuntime[], device: SelectDevice): LocalRuntimeBuild | null; /** * The runtimes a version NO catalog reviewed (the person's own model, a version found on Hugging Face) is * judged against: our GGUF engine on this computer takes its signature as "expected" (not yet tested), * so it is judged on what our runtime runs, never on the reviewed list. Without an engine, unchanged. */ declare function runtimesOnEngine(runtimes: readonly LocalRuntime[], variant: LocalVariant, device: SelectDevice): LocalRuntime[]; interface RankOptions { /** Judge every version on our engine for its format (`runtimesOnEngine`): a person's own or found model, never on a reviewed list. */ onEngine?: boolean; } /** * THE judge every screen shows (unified-models §1.4a): every variant of `model` with whether it can be * used here, its reasons in plain words (format our runtimes run, a complete file set, os/arch, and * memory — with numbers), and exactly one recommended usable variant. With `device` null (the web) the * memory line says what it needs instead of yes/no. Ordered: the recommended first, then the other * usable ones in the selection order, then the rest. `selectVariant` is this judge's winner. */ declare function rankVariants(model: LocalModel, want: Want, device: SelectDevice | null, runtimes: readonly LocalRuntime[], options?: RankOptions): RankedVariant[]; /** * ONE verdict for a version on a screen (Find models, the Discovery page): every line a version row * draws comes from this, so a row can never say "Will not run" beside "Runs on llama.cpp". `runs` false: * `reason` is the first plain line (memory with both numbers, or the format) and there is NO runtime line. * `runs` true: `runtime` names what it runs on ("Runs on llama.cpp") when known. `lines` are the rest. * Catalog mode (admin) never gates: `runs` is null and the runtime line is the server's per-runtime word. */ interface VariantVerdict { runs: boolean | null; /** "Will not run" when it does not run here; null otherwise. */ badge: string | null; runtime: string | null; reason: string | null; lines: string[]; } declare function variantVerdict(ranked: RankedVariant, mode: "device" | "catalog"): VariantVerdict; /** * Choose the variant of `model` this device downloads and runs, and exactly the files to fetch — the * recommended entry of `rankVariants`. The choice is returned with `decided_by` (the order step that * separated the winner) and a reason line the screen shows; nothing is chosen silently. */ declare function selectVariant(model: LocalModel, want: Want, device: SelectDevice, runtimes: readonly LocalRuntime[]): Selection | NoSelection; /** One file of a discovered variant. Newer servers send `path`/`size`; older ones `path_in_repo`/`size_bytes`. */ interface DiscoveryArtifact { role: string; path?: string; path_in_repo?: string; url: string; size?: number; size_bytes?: number; sha256?: string | null; shard_index?: number | null; shard_count?: number | null; required_for?: string[]; } interface DiscoveryVariant { key: string; label?: string | null; format: string; quant: string; architecture?: string; chat_template_kind?: string; total_bytes?: number | null; memory_bytes_min?: number | null; memory_bytes_recommended?: number | null; artifacts: DiscoveryArtifact[]; ambiguous?: boolean; from_repo?: string | null; publisher?: string | null; runnable?: boolean | null; why_not?: string | null; /** Which runtime runs this version (server knob ai.models/runtime_rules + ai.runtime rows). */ runtime?: DiscoveryRuntime | null; } /** The runtime a discovered version needs, and whether Matrx 2 has it today. */ interface DiscoveryRuntime { key: string; name: string; available: boolean; /** Plain words when it is not available; null otherwise. */ note?: string | null; } interface DiscoveryMedia { kind: string; source_url: string; alt?: string; cdn_url?: string | null; } interface DiscoveryAmbiguity { code: string; variant?: string | null; words?: string; } /** Where the files came from and whether they run as they are (contract: source.*). */ interface DiscoverySource { repo: string; revision: string; shape: "gguf" | "split_gguf" | "safetensors" | "mixed" | "other"; runnable_as_is: boolean; why_not: string | null; } /** * The resolve response (`POST /ai-catalog/discover/resolve`, or the admin candidate's `suggested`). * Every field beyond `repo`/`revision`/`variants` is optional: an older server omits them and nothing * here invents them. */ interface DiscoveryResult { repo: string; revision: string; card_url?: string; name?: string | null; maker?: string | null; maker_avatar_url?: string | null; summary?: string | null; license_id?: string | null; gated?: string; pipeline_tag?: string | null; capabilities?: { input?: string[]; output?: string[]; interaction?: string; [k: string]: unknown; }; media?: DiscoveryMedia[]; variants: DiscoveryVariant[]; ambiguities?: DiscoveryAmbiguity[]; source?: DiscoverySource | null; conversions_searched?: string[]; recommended_variant_key?: string | null; } /** * A discovery result as a catalog `LocalModel`, so `rankVariants`/`selectVariant` judge a pasted link * exactly as they judge the catalog. The server's recommendation becomes selection rank 0 (every other * variant rank 1), so a device that cannot fit it re-ranks by the same order the catalog uses. */ declare function fromDiscovery(result: DiscoveryResult): LocalModel; /** The models this app version can show, and how many variants a later app would add ("Update Matrx to see N more models"). */ declare function catalogForApp(catalog: LocalModelCatalog, appVersion: string | null): { models: LocalModel[]; hiddenModels: number; }; /** * `@ai-matrx/agents/models` GGUF header reader (moved from matrx-desktop app/shared/models/gguf.ts). A GGUF file's header, read in place (no whole-file read): the key/value section and every tensor's * info, and from them where the tensor data must end, so a file cut short is told apart from a whole one. * Same format rules as aidream's remote reader (aidream/services/ai_catalog/local_models/gguf_header.py: * magic `GGUF`, u32 version, u64 tensor_count, u64 kv_count, then per KV a string key, u32 type, value), * plus the tensor infos it skips (llama.cpp ggml/src/gguf.cpp). Pure: the caller supplies `read`. */ type GgufRead = (offset: number, length: number) => Promise; interface GgufTensor { readonly name: string; readonly dims: readonly number[]; readonly type: number; readonly offset: number; } interface GgufHeader { readonly version: number; /** Scalar values and strings; arrays are kept only as their length (`array_lengths`). */ readonly kv: Readonly>; readonly array_lengths: Readonly>; readonly tensors: readonly GgufTensor[]; /** Where the tensor data starts (the header, padded to `general.alignment`). */ readonly data_offset: number; /** Where the last tensor's bytes end; null when a tensor type is one this reader has no size for. */ readonly data_end: number | null; } declare class GgufError extends Error { } /** llama.cpp `enum llama_ftype` (include/llama.h) → the quant name files are published under (gguf_header.py FILE_TYPE_QUANT). */ declare const FILE_TYPE_QUANT: Readonly>; /** Read the header of a GGUF file of `size` bytes. Throws GgufError (a plain clause) when it is not a readable one. */ declare function readGgufHeader(read: GgufRead, size: number): Promise; /** The facts a model is described by, from its header (architecture, quant, context, size class). */ declare function ggufFacts(h: GgufHeader): { architecture: string | null; name: string | null; quant: string | null; context_length: number | null; parameter_count: number | null; split_count: number; split_index: number; type: string | null; has_tokenizer: boolean; }; /** * `@ai-matrx/agents/models` — the ONE judge of whether a set of model files runs on our runtime * (llama-server, GGUF), whoever holds the files: Matrx 2's Add as My Model (a file in the person's * folder), Matrx 2's Find models download (the files it just fetched), any later client. Pure: the * caller supplies each file's name, size and a byte reader; nothing here touches a file system. * * Rules, each failing with one plain sentence (mirrors aidream services/ai_catalog/local_models * huggingface.py build_variants / shard_problems / pick_projector / is_helper_gguf, which judge the * same sets from a Hugging Face listing before download): * 1. format: a .gguf file (other formats named with what to do instead); * 2. not a helper: never an image projector (name `mmproj` or header general.type mmproj / clip), * a speculative-decoding draft/MTP head, or an importance matrix; * 3. complete file set: a split model (`-0000i-of-0000n.gguf`, the only naming llama-server * loads a split set by) is ONE model; it must be named by its first part, every part 1..n must be * present exactly once, every part's header split.no / split.count must agree with its name; * 4. header facts — THE one rule set shared with the server (judgeHeaderFacts; cases in * __fixtures__/header-rules.json, run by both test suites): names general.architecture; that * architecture is one the pinned llama-server build lists (build.known_architectures — data; no list = * not checked); carries a tokenizer (tokenizer.ggml.model). A missing chat template is a FLAG * (facts.flags), never a refusal; * 5. every file holds every tensor byte its header promises (a cut-short download is refused); * 6. projector: an `mmproj` file beside it is attached by the server's rule (one -> it; else the * variant's quant; else the first of F16, BF16, Q8_0, F32) — never a refusal, a capability; * 7. fit: the whole set (weights + projector) fits this computer's memory (fit_fraction knob); * 8. engine: this computer has an active llama-server build for its os/arch that runs GGUF. */ interface ModelFileHandle { /** The file's name (its last path segment; split parts are matched by name). */ readonly name: string; readonly size: number; /** Reads `length` bytes at `offset` (fewer at the end of the file). */ readonly read: GgufRead; } interface ModelSetFacts { readonly architecture: string; /** general.name, else the file name without its part suffix and extension. */ readonly name: string; readonly quant: string; readonly context_length: number | null; readonly parameter_count: number | null; readonly has_chat_template: boolean; /** Every weights file in load order (one, or parts 1..n); the first is what llama-server is pointed at. */ readonly parts: readonly { readonly name: string; readonly size: number; readonly shard_index: number | null; readonly shard_count: number | null; }[]; /** The image/audio projector attached by rule 6, or null. */ readonly projector: { readonly name: string; readonly size: number; readonly use: "vision" | "audio_in"; } | null; /** weights + projector bytes: the fit is judged on this. */ readonly total_bytes: number; /** Usable, said plainly (header-rules.json flags), e.g. "template_missing". */ readonly flags: readonly HeaderFlag[]; } type ModelSetValidation = { ok: true; facts: ModelSetFacts; } | { ok: false; reason: string; codes?: readonly HeaderRefusal[]; }; type HeaderRefusal = "architecture_missing" | "arch_unknown_to_runtime" | "tokenizer_missing"; type HeaderFlag = "template_missing"; interface HeaderJudgement { readonly usable: boolean; readonly refused: HeaderRefusal[]; readonly flags: HeaderFlag[]; } /** THE header rule set (mirrors aidream huggingface.judge_header_facts; both run __fixtures__/header-rules.json). */ declare function judgeHeaderFacts(facts: { readonly architecture: string; readonly has_tokenizer: boolean; readonly has_chat_template: boolean; }, knownArchitectures: readonly string[] | null): HeaderJudgement; interface ModelSetEnv { readonly device: SelectDevice; readonly runtimes: readonly LocalRuntime[]; } interface ShardName { readonly stem: string; /** 1-based, as in the file name. */ readonly index: number; readonly count: number; } declare function shardName(name: string): ShardName | null; declare const shardFileName: (stem: string, index: number, count: number) => string; declare const isHelperGguf: (name: string) => boolean; declare const isProjectorName: (name: string) => boolean; declare const quantFromName: (name: string) => string | null; /** huggingface.py shard_problems over names: why a shard set is not exactly 1..n of one n (empty = complete). */ declare function splitSetProblems(names: readonly string[]): string[]; /** huggingface.py pick_projector over names: the projector a variant of `quant` takes, or null. */ declare function pickProjector(quant: string | null, projectors: readonly string[]): string | null; /** * Judge `primary` (the file the person chose) with the files beside it (`siblings`: the rest of its * folder or download; split parts and projectors are taken from here, anything else is ignored). */ declare function validateModelSet(primary: ModelFileHandle, siblings: readonly ModelFileHandle[], env: ModelSetEnv): Promise; /** * `@ai-matrx/agents/models` — THE order of the curated model list, for every surface that shows it * (AI Matrx admin › Catalogs, Matrx 2 › Models, any later client). Arman 2026-10-16: Matrx 2 shows * exactly his curated list, in his order. The order is data: `ai.model_presentation.display_order` * (the admin's explicit position, written by promote / placement). Never by name, never by memory. * * Rule: models with a display_order first, ascending; then models without one, in the order they came * (the caller's stable source order); ties keep the order they came in. Shelves (or any grouping) take * the position of their first member, so a shelf sits where the admin put its first model. */ interface OrderedLike { readonly presentation?: (object & { readonly display_order?: number | null; }) | null; } declare const displayOrderOf: (m: OrderedLike) => number | null; /** A new array in the curated order (stable). */ declare function orderCatalogModels(models: readonly T[]): T[]; /** * Groups (shelves) in the curated order: each group sits at its first member's position, its members * in the curated order. `keyOf` names a member's group. */ declare function orderCatalogGroups(models: readonly T[], keyOf: (m: T) => string): Array<{ key: string; members: T[]; }>; /** * `@ai-matrx/agents/models` — THE curated downloadable-model list, read once for every surface that * shows it: AI Matrx admin › Applications › Catalogs (every model, offered or not) and Matrx 2 › * Models (only the offered ones). One query (`fetchCuratedModels`), one membership rule (a model is * offered when at least one of its variants has review_state `offered` — the rule Matrx 2's live * catalog uses), one source order (model name by code point, as the snapshot exporter orders), and * one order (`orderCatalogModels` over ai.model_presentation.display_order). `curatedList` is the * only way a surface turns rows into its list, so two surfaces cannot drift apart. */ type Row$2 = Record; /** A structural subset of `SupabaseClient` (only `schema().from().select()` is used). */ interface CuratedCatalogClient { schema(name: string): { from(table: string): { select(columns: string): PromiseLike<{ data: Row$2[] | null; error: { message: string; } | null; }>; }; }; } interface CuratedVariant { id: string; key: string; review_state: string; format: string; quant: string; total_bytes: number | null; is_default: boolean; /** The public.catalog_entries rows this variant carries (set by the list migration); empty for a model added later. */ catalog_entry_ids: string[]; } interface CuratedPresentation { display_order: number | null; page: string | null; shelf: string | null; shelf_title: string | null; summary: string | null; } interface CuratedModel { id: string; name: string; common_name: string | null; description: string | null; maker: string | null; capabilities: { input?: string[]; output?: string[]; interaction?: string; [k: string]: unknown; }; presentation: CuratedPresentation | null; variants: CuratedVariant[]; /** At least one variant is offered: Matrx 2 lists it. */ offered: boolean; /** Admin display facts (ai.model_presentation.facts): ratings, speed, knowledge cutoff, tier, notes. */ facts: Record; /** The later of the model's and its presentation's updated_at: what an edit sends as expected_updated_at. */ updated_at: string | null; } /** * The review states that take a variant OFF every list (ai.model_review_state: discovered, files_checked, * tested, offered, rejected, withdrawn). A rejected or withdrawn variant is never shown; a model left with * none is not listed. */ declare const EXCLUDED_REVIEW_STATES: ReadonlySet; /** Whether a variant row stays on the curated list (live, and not rejected or withdrawn). */ declare const isListedVariant: (v: Row$2) => boolean; /** The four raw ai.* reads the curated list is shaped from (live rows only; RLS applies). */ interface CuratedRows { models: Row$2[]; variants: Row$2[]; presentations: Row$2[]; providers: Row$2[]; } /** Shape raw ai.* rows into the curated models (pure; the query below feeds it). Rejected and withdrawn variants are left out. */ declare function shapeCuratedModels(input: { models: Row$2[]; variants: Row$2[]; presentations: Row$2[]; providers: Row$2[]; }): CuratedModel[]; /** The four reads behind the curated list (live rows of ai.model_definition, model_variant, model_presentation, provider). */ declare function readCuratedRows(client: CuratedCatalogClient): Promise; /** THE query: every live, unretired model with a variant that is not rejected or withdrawn, from ai.* (RLS applies). */ declare function fetchCuratedModels(client: CuratedCatalogClient): Promise; /** * THE list a surface shows, in the curated order. `offeredOnly` is Matrx 2's list; the admin page * passes false and shows the not-offered ones as inactive. The admin list with only its offered rows * is, by construction, exactly Matrx 2's list in the same order. */ declare function curatedList(models: readonly T[], { offeredOnly }: { offeredOnly: boolean; }): T[]; /** * `@ai-matrx/agents/models` — WHICH COMPUTER HOLDS WHICH CATALOG MODEL. A local model is ONE catalog row * (ai.model_definition / model_variant / model_artifact); there are no per-device catalog rows. Placement * lives in ai.device_model_file (one row per model file a computer found, `artifact_id` → ai.model_artifact). * `readModelCatalog(client, { devices })` and `resolveDeviceModel` (catalog-reader.ts) read it through here. * * The rules (mirrored by the server's device provider; FEATURE.md § Device-served models): * - a file counts only when it is not deleted and its `state` is in DEVICE_FILE_USABLE_STATES; * - a variant is available on a computer only when EVERY required artifact of it has a usable file there. * Required = the artifacts whose `required_for` holds `core` (the same set `requiredArtifacts(v, [])` * fetches); a variant with no `core` artifact requires all of its artifacts; a variant with no artifact * is never available. Optional files (an mmproj, `required_for: ["vision"]`) never block it, and are * listed in `device_model_file_ids` when present. */ type Row$1 = Record; /** The `provider` value of a device-served run: `config_overrides.served_via = { provider: "device", … }`. */ declare const DEVICE_SERVED_VIA: "device"; /** Where a device-served run carries its DeviceServedSettings on the agent-run request: `config_overrides.served_via`. */ declare const DEVICE_SERVED_SETTINGS_KEY: "served_via"; /** * The server's Python twin (the device provider) MUST mirror the usable states, the required-artifact rule * and the resolveDeviceModel refusals above; this test file is the fixture both sides prove against. */ declare const DEVICE_RULE_TWIN_FIXTURE: "apps/shared/matrx-agents/models/__tests__/device-models.test.ts"; /** * The ai.device_model_file states whose catalog file can be loaded (the column holds present, missing, * corrupt, unmatched, hashing, unverified_match). Only `present` is a verified copy of the catalog artifact; * `unverified_match` is a same-size file whose hash is not proven, `hashing` is not finished. */ declare const DEVICE_FILE_USABLE_STATES: readonly string[]; /** * The states in which a person's OWN file (artifact_id null: no catalog artifact) can be loaded: it is on * disk with its content known. Matrx 2 records a scanned file that matches no catalog artifact as `unmatched`. */ declare const DEVICE_OWN_FILE_USABLE_STATES: readonly string[]; /** One computer that holds every required file of one variant. */ interface DeviceAvailability { app_instance_id: string; variant_id: string; /** The usable ai.device_model_file ids of this variant on that computer (required and optional), in artifact order. */ device_model_file_ids: string[]; } /** The run settings of a device-served model. There is no ai.provider row and no pricing row: the ledger counts 0 points. */ /** Carried at `config_overrides.served_via` (DEVICE_SERVED_SETTINGS_KEY) on the agent-run request. */ interface DeviceServedSettings { provider: typeof DEVICE_SERVED_VIA; /** The computer (public.app_instances.id). */ app_instance_id: string; /** ai.model_definition.id, or an ai.device_model_file.id (artifact_id null) for a person's own model. */ local_model_id: string; /** ai.model_variant.id; absent = the device's best available variant (default first, then selection_rank). */ variant_id?: string; } /** One ai.device_model_file row, as the reader returns it. */ interface DeviceModelFile { id: string; app_instance_id: string; root_id: string; /** Null when the person does not share folder paths (the path stays in the computer's local store). */ rel_path: string | null; size_bytes: number | null; sha256: string | null; artifact_id: string | null; state: string; organization_id: string | null; /** * The key the computer runs this file under (`metadata.model_key`, published by Matrx 2): for a person's own * file it is the run key the device's model service names it by. Null when not reported yet. */ model_key: string | null; /** The name the computer shows for this file (`metadata.display_name`, the GGUF's own name); null when not reported. */ display_name: string | null; } /** * THE NAME OF A PERSON'S OWN MODEL (one source, every surface): the name its computer published * (`metadata.display_name`), else the file's name, else the run key. The chat's model menu and the agent * chip (through the model records' identity read) both call this, so a pick reads the same before and * after a reload. Null only when the row carries none of the three. */ declare function ownModelFileName(f: Pick): string | null; declare function toDeviceModelFile(r: Row$1): DeviceModelFile; /** * THE ai.device_model_file read: live rows in `states`, on the given computers (all the caller can see when * omitted; RLS keeps it to the person's own). A failed read throws, naming the table. */ declare function readDeviceModelFiles(client: unknown, opts?: { appInstanceIds?: readonly string[]; states?: readonly string[]; }): Promise; /** A variant's artifacts as the availability rule reads them (raw ai.model_artifact ids). */ interface AvailabilityVariant { id: string; artifacts: ReadonlyArray<{ id: string; required_for: readonly string[]; }>; } /** The artifacts a computer must hold for the variant to be available: `core`, or all when none is `core`. */ declare function requiredArtifactIds(v: AvailabilityVariant): string[]; /** * Pure: for each variant, the computers that hold every required artifact of it (DEVICE_FILE_USABLE_STATES * files only; others are ignored). Computers in code-point order. */ declare function availableOnDevices(variants: readonly AvailabilityVariant[], files: readonly DeviceModelFile[]): Map; /** A device-model request the device cannot serve. `code` says why; the message names the model and computer. */ declare class DeviceModelUnavailableError extends Error { readonly code: "not_on_device" | "unknown_model" | "unknown_variant" | "file_not_usable" | "not_own_file"; constructor(code: DeviceModelUnavailableError["code"], message: string); } /** * `@ai-matrx/agents/models` — THE model-catalog reader. Every surface that shows the downloadable-model * catalog (AI Matrx admin › Applications › Catalogs, Matrx 2 › Models, any later client) reads it here, * so no two surfaces can drift apart: * * - members: the curated query (`readCuratedRows` + `shapeCuratedModels` + `curatedList`) — a live, * unretired model with a variant that is not rejected or withdrawn; `offeredOnly` (the default, for * apps) keeps only models and variants whose review_state is `offered`; the admin page passes false. * - order: the admin's order (`orderCatalogModels` over model name by code point), applied HERE, so a * consumer never re-sorts. * - shape: `LocalModelCatalog` (`matrx.model-catalog.snapshot/v1`, the snapshot exporter's shape), plus * every field the admin Catalogs editor writes, the admin kind (`catalogKindOf`) and a content * fingerprint (`catalog_fingerprint`) that changes on ANY change, not only a moved max(updated_at). * * The columns the Catalogs editor writes are pinned by `scripts/check-catalog-reader-columns.mjs`: drop a * mapping below and the guard goes red. */ type Row = Record; type Result = { data: Row[] | null; error: { message: string; } | null; }; /** A filterable PostgREST query (the subset of supabase-js's builder this reader uses). */ interface LocalCatalogQuery extends PromiseLike { is(column: string, value: null): LocalCatalogQuery; eq(column: string, value: string): LocalCatalogQuery; in(column: string, values: readonly string[]): LocalCatalogQuery; } /** A structural subset of `SupabaseClient`: `schema(name).from(table).select(columns)` plus `is` / `eq` / `in`. */ interface LocalCatalogClient { schema(name: string): { from(table: string): { select(columns: string): LocalCatalogQuery; }; }; } interface ReadModelCatalogOptions { /** Apps (default true): only offered models and their offered variants. The admin page passes false. */ offeredOnly?: boolean; /** * Also read ai.device_model_file and give each model `available_on` (default off: output unchanged). * `true` = every computer the caller can see; `{ appInstanceIds }` = only those computers. */ devices?: boolean | { appInstanceIds?: readonly string[]; }; } /** The kind of a model the catalog cannot place by its capabilities: shown loudly, never offered to a chat. */ declare const UNKNOWN_MODEL_KIND: "unknown"; /** The catalog kinds whose rows live in the unified catalog (ai.*): the admin Catalogs page's model kinds. */ declare const MODEL_CATALOG_KINDS: ReadonlySet; /** The interactions a text-in, text-out model answers a person in: only these make an llm_model. */ declare const LLM_INTERACTIONS: ReadonlySet; /** What `catalogKindOf` reads from a model (a CuratedModel or a LocalModel from `readModelCatalog` both fit). */ interface CatalogKindInput { readonly variants: ReadonlyArray<{ readonly catalog_entry_ids?: readonly string[]; }>; readonly capabilities: { readonly input?: readonly string[]; readonly output?: readonly string[]; readonly interaction?: string; }; readonly presentation?: { readonly shelf?: string | null; } | null; } /** A model's admin kind and, when it is `unknown`, why (one line, said on the admin page and in reader warnings). */ interface CatalogKindVerdict { kind: string; reason: string | null; } /** * THE admin Catalogs kind of a unified model, with the reason when it cannot be placed: the kind of the * old public.catalog_entries rows its variants carry (the list migration stamps them), when that is a * model kind; else placed by what it does (capabilities, never a name list). There is NO default: a model * is an llm_model only when it takes text, gives text and answers in a chat-like interaction * (LLM_INTERACTIONS); anything the rules cannot place is `unknown`, with the reason. */ declare function catalogKindVerdict(m: CatalogKindInput, kindOfEntry: ReadonlyMap): CatalogKindVerdict; /** THE admin Catalogs kind of a unified model (`catalogKindVerdict(...).kind`); `unknown` when it cannot be placed. */ declare function catalogKindOf(m: CatalogKindInput, kindOfEntry: ReadonlyMap): string; /** sha256 (hex) of the catalog's canonical JSON, its own `catalog_fingerprint` left out. Isomorphic (pure JS). */ declare function catalogFingerprint(catalog: LocalModelCatalog): string; /** * THE catalog read: `LocalModelCatalog` in the admin's order, every admin-edited field carried, each model's * admin kind, and `catalog_fingerprint`. Reads ai.* (and public.catalog_entries id/kind for the kind) as the * signed-in caller (RLS applies). A failed ai.* read throws; a failed catalog_entries read only places models * by what they do and is said in `warnings`. */ declare function readModelCatalog(client: CuratedCatalogClient, opts?: ReadModelCatalogOptions): Promise; /** What `resolveDeviceModel` returns: a catalog model to load from the device's files, or a person's own file. */ type DeviceModelResolution = { own: false; model: LocalModel; variant: LocalVariant; files: DeviceModelFile[]; } | { own: true; file: DeviceModelFile; }; /** * THE device-served model lookup (the client's local chat and the server's device provider both use it; * FEATURE.md § Device-served models). `local_model_id` is an ai.model_definition.id, or an * ai.device_model_file.id with no artifact (a person's own model, `{ own: true, file }`). Returns the catalog * model, the variant (`variant_id`, else the device's default-then-best-ranked available one) and the * device's file rows to load. Refuses with DeviceModelUnavailableError, by name, when the device does not * hold it. Reads listed variants (offeredOnly false): a model on the computer runs even after it is unoffered. */ declare function resolveDeviceModel(client: CuratedCatalogClient, req: { app_instance_id: string; local_model_id: string; variant_id?: string; }): Promise; /** * `@ai-matrx/agents/models` — THE fit rules: what a model can do for a purpose, read from the * capability data the catalog holds (`capabilities.input / output / features / interaction`), never * from a placement (a presentation shelf, a page) and never from a list of names. * * One capability can live in two shapes in the data: "sees images" is `input` containing `image` * OR the `vision` feature (the audit projection `toAuditRecord` already reads it that way). Every * filter that asks "does it see images" asks `seesImages`, so a model carrying only one of the two * shapes is never dropped by one surface and kept by another. * * Every picker, every scope chip and every knob that names "the cheapest model that can do X" * (`cheapestAdequateModel`) goes through these, so the web, Matrx 2 and the server-side knobs agree. */ /** The capability fields a fit rule reads. A CatalogModel, a ModelCapabilities and a LocalModel's `capabilities` all fit. */ interface FitCapabilities { readonly input?: readonly string[]; readonly output?: readonly string[]; /** Raw feature keys, or the picker's grouped features (its `raw` keeps every key). */ readonly features?: readonly string[] | Pick; readonly interaction?: string; } /** Sees images: `image` input OR the `vision` feature (both shapes the data uses). */ declare function seesImages(c: FitCapabilities): boolean; /** Takes this modality as input. `image` reads both shapes (`seesImages`). */ declare function takesInput(c: FitCapabilities, modality: string): boolean; /** * Fit for a CHAT turn: an ordinary turn model that takes text in and gives text out, whose job is not * making media (a music or image generator that also returns a caption is not a chat model). Speech-to-text, * embedding, extraction, realtime voice, decision holders and provider-managed agents are out. */ declare function isChatModelCapability(c: FitCapabilities): boolean; /** What a job needs from a model. Every listed item is required. */ interface ModelNeed { /** Input modalities it must take (`image` = sees images, both shapes). */ readonly inputs?: readonly string[]; /** Output modalities it must give. */ readonly outputs?: readonly string[]; /** Raw feature keys it must carry (e.g. `function_calling`, `structured_output`). */ readonly features?: readonly string[]; /** Required interaction mode (e.g. `turn`). Absent = any. */ readonly interaction?: string; /** Minimum context window in tokens. */ readonly minContextWindow?: number; } /** Does the model meet every item of the need? */ declare function fitsNeed(c: FitCapabilities & { readonly contextWindow?: number | null; }, need: ModelNeed): boolean; /** The catalog fields `cheapestAdequateModel` reads (a picker `CatalogModel` fits as-is). */ interface PricedCatalogModel extends FitCapabilities { readonly id: string; readonly name: string; readonly contextWindow?: number | null; /** Points per 1M input tokens (the catalog's price). */ readonly pointsInput?: number | null; /** Points per 1M output tokens (the catalog's price). */ readonly pointsOutput?: number | null; /** Curated 1-6 cost rating: the tie-break, and the price when no points are set. */ readonly costRating?: number | null; readonly isDeprecated?: boolean; readonly retiredAt?: string | null; } interface CheapestAdequateOptions { /** Weight of output price against input price (output tokens usually dominate). Default 3 (1 in : 3 out). */ readonly outputWeight?: number; /** Allow deprecated models. Default false (retired models are never chosen). */ readonly includeDeprecated?: boolean; } /** The blended price a model is ranked by, or null when the catalog holds no price for it. */ declare function blendedPrice(m: PricedCatalogModel, outputWeight?: number): number | null; /** * THE cheapest model in a catalog that meets `need`, by the catalog's own prices (points per 1M * tokens, output weighted). A model with no price ranks after every priced one, by cost rating; ties * break on cost rating then name (stable). Retired models never; deprecated only when asked. Null when * nothing fits — the caller says so; there is no built-in fallback. */ declare function cheapestAdequateModel(catalog: readonly T[], need: ModelNeed, options?: CheapestAdequateOptions): T | null; /** * `@ai-matrx/agents/models` — THE model a new agent starts on comes from a knob, never a constant: * `agents.model_prefs.new_agent_default_model` (organization → person → device ladder, through the host's knob * port; platform default Claude Sonnet 5.5, migration 1512). Every "make me an agent" path (blank agent, quick * agent, templates, the web's manual create, system-agent create and builder form) asks `resolveNewAgentModelId`. * * It is NOT `agent_authoring_default_model`: that knob is the agent FACTORY's own model. * * Every read resolves the knob's id through the catalog's successor chain (`resolveLiveModelId`): a default that * was deprecated with a successor answers its live successor with no knob edit. * * No code fallback: an unset or unreadable knob refuses, naming the knob, so nothing is created on a model nobody * chose (the knob's platform default makes "unset" an admin act, not a normal state). */ declare const NEW_AGENT_DEFAULT_MODEL_KNOB = "agents.model_prefs.new_agent_default_model"; interface NewAgentModel { modelId: string; } /** The model a new agent starts on. `resolveKnob(fullKey)` is the host's effective-knob read. Throws, naming the knob, when it answers nothing. */ declare function resolveNewAgentModelId(resolveKnob: (fullKey: string) => Promise, /** * The catalog the id is resolved through (successor chain). Optional only so a host that has not passed it * yet keeps compiling across a re-lock; without it the knob's id is used as is and that is said loudly. */ catalog?: SuccessionClient): Promise; /** * A json knob mapping names to model ids (e.g. `agents.simple_run.capability_models`), each id resolved through the * catalog's successor chain on this read. Non-string entries are dropped; a value that is not an object answers {}. */ declare function resolveModelMapKnob(value: unknown, catalog: SuccessionClient): Promise>; /** * `@ai-matrx/agents/models` — THE model label formula. Every surface that names a model computes the * name here: the person-facing `common_name`, else the canonical `name`. An empty `common_name` counts * as absent. Pure; the row it is handed must come from the subscribed catalog (records / readModelCatalog), * so a `catalog_changed` notice re-renders it. Guard: `scripts/check-model-catalog-reads.mjs`. */ /** The two columns a model label is made from (cloud records, identities, local catalog rows). */ interface ModelLabelSource { common_name?: string | null; name?: string | null; } /** A row that always has a canonical name always has a label. */ declare function modelDisplayLabel(row: { common_name?: string | null; name: string; }): string; declare function modelDisplayLabel(row: ModelLabelSource | null | undefined): string | undefined; /** * `@ai-matrx/agents/models` — the ONE subscription to `catalog_changed`. * * The server publishes `catalog_changed` on the platform bus for every admin edit and direct SQL write to * the model catalog. A host binds its bus client ONCE with `followModelCatalogChanges(subscribe)` (the web's * directive registry, Matrx 2's IPC forward of the main-process bus); every model catalog * (`createModelCatalog`: picker rows + records) re-reads itself on the notice, and any other reader * subscribes here (`onModelCatalogChanged`, or `useModelCatalogRevision` / `useOnModelCatalogChanged` in * `@ai-matrx/agents/models/react`). No surface keeps a model cache that misses the event. * * The hub lives on `globalThis` so two bundled copies of this module share one set of listeners. * * A tab that is not listening (the bus room not joined yet, or asleep) catches up when it comes to the * front: `followModelCatalogVisibility({ client })` reads the catalog stamp when the tab hides and again * when it shows or takes focus (throttled), and announces a change when the stamp moved. */ /** A `catalog_changed` payload: `{ catalog?: "models" | ..., change?, model_id? }`. */ type ModelCatalogChangePayload = Record; /** What the bus knew about one envelope besides its payload: its `requestId` (the server names one burst once). */ type ModelCatalogChangeMeta = { requestId?: string | null; } | null | undefined; /** The bus client's subscribe call: `(action, handler) => unsubscribe` (the web's `registerDirectiveHandler`). */ type ModelCatalogDirectiveSubscribe = (action: "catalog_changed", handler: (payload: ModelCatalogChangePayload, meta?: ModelCatalogChangeMeta) => void) => () => void; /** Is this notice about the MODEL catalog? (`catalog` absent or `"models"`; the tool catalog shares the action.) */ declare function isModelCatalogChange(payload: ModelCatalogChangePayload | null | undefined): boolean; /** Listen for model catalog changes. Returns the unsubscribe. */ declare function onModelCatalogChanged(listener: (payload: ModelCatalogChangePayload) => void): () => void; /** How many model catalog changes this page has seen (a React key / effect dependency). */ declare function modelCatalogRevision(): number; /** Announce a model catalog change to every listener (a notice that is not about models is ignored). */ declare function announceModelCatalogChanged(payload?: ModelCatalogChangePayload): void; interface ModelCatalogCoalesceOptions { /** Envelopes within this many ms of the last delivery are the same burst. Default 1500. */ windowMs?: number; /** How long a delivered `requestId` is remembered (a late replica's copy is dropped). Default 60 s. */ rememberMs?: number; now?: () => number; setTimer?: (fn: () => void, ms: number) => unknown; clearTimer?: (handle: unknown) => void; } /** * Fold a burst of `catalog_changed` envelopes into ONE delivery. The server's replicas (and, before * 2026-10-24, a second relay ~4 s later) can put the same change on the bus several times; every copy used to * cost every open app a re-read. Rules: an envelope whose `requestId` was delivered within `rememberMs` is * dropped; the first envelope of a burst is delivered at once; within `windowMs` of that delivery a copy with * the same payload is dropped, and a DIFFERENT payload (another model, another kind of edit) is delivered * once when the window closes, so a real second change is never lost. Returns the feed; `feed.stop()` drops * a pending delivery. */ declare function coalesceModelCatalogChanges(deliver: (payload: ModelCatalogChangePayload) => void, options?: ModelCatalogCoalesceOptions): ((payload: ModelCatalogChangePayload, meta?: ModelCatalogChangeMeta) => void) & { stop(): void; }; /** * Bind the host's bus client: every `catalog_changed` it delivers reaches every model reader, a burst of * copies as ONE change (`coalesceModelCatalogChanges`). Returns the unbind. */ declare function followModelCatalogChanges(subscribe: ModelCatalogDirectiveSubscribe, options?: ModelCatalogCoalesceOptions): () => void; interface ModelCatalogVisibilityOptions { /** The client the stamp is read with (supabase-js fits as is). */ client: ModelRecordsClient; /** At most one stamp read per this many ms when the tab shows / takes focus. Default 20 s. */ minIntervalMs?: number; /** The page (tests pass a double); defaults to the global `document` / `window`. */ page?: { document: Pick; window: Pick; }; /** Where a failed stamp read is said. Default: console.error, tagged. */ errorSink?: (event: { code: string; message: string; }) => void; now?: () => number; } /** * When the tab shows or takes focus, read the catalog stamp (one row) and, when it moved since the tab * last knew it, announce a model catalog change through the SAME door as the bus (every catalog re-reads * what it read; every reader following `onModelCatalogChanged` re-reads). The baseline is read at bind, * and again when the tab hides or loses focus after an announced change made it unknown. Every return * from away (hidden or blurred) reads the stamp; only repeated shows / focuses with no time away in * between share the last read for `minIntervalMs` (a tab that took focus to type a search, went to * another tab for a rename and came back inside 20 s used to read nothing — live 2026-10-24, the admin * audit page). Reads already in flight are joined. Returns the unbind. No-op where there is no document * (a server render, a worker). */ declare function followModelCatalogVisibility(options: ModelCatalogVisibilityOptions): () => void; export { CHAT_DEFAULT_MODEL_KNOB, CONTENT_TYPES, DEFAULT_CAPABILITIES, DEVICE_FILE_USABLE_STATES, DEVICE_OWN_FILE_USABLE_STATES, DEVICE_RULE_TWIN_FIXTURE, DEVICE_SERVED_SETTINGS_KEY, DEVICE_SERVED_VIA, DeviceModelUnavailableError, EMPTY_MODEL_RECORDS, EXCLUDED_REVIEW_STATES, FEATURE_BUCKETS, FEATURE_BUCKET_ORDER, FEATURE_KEYS, FILE_TYPE_QUANT, GgufError, INPUT_MODALITIES, INTERACTION_MODES, LLM_INTERACTIONS, MAX_SUCCESSION_HOPS, MODEL_CATALOG_KINDS, MODEL_FAVORITE_ENTITY_TYPE, NEW_AGENT_DEFAULT_MODEL_KNOB, OUTPUT_MODALITIES, SERVICE_LABEL, SERVICE_LABEL_PLURAL, TOP_TIER_COST_RATING, TOP_TIER_LOCKED_TITLE, UNKNOWN_MODEL_KIND, acceptsTextInstructions, announceModelCatalogChanged, availableOnDevices, blendedPrice, buildModelClassIndex, buildSuccessionIndex, catalogFingerprint, catalogForApp, catalogKeyColumn, catalogKindOf, catalogKindVerdict, cheapestAdequateModel, classConfigKey, coalesceModelCatalogChanges, collapseTiersByClass, costRatingTier, costRatingWord, createModelCatalog, createModelFavorites, createModelRecords, curatedList, displayOrderOf, favoriteIdsEqual, fetchCuratedModels, findNonCanonicalCapabilityValues, fitsMemory, fitsNeed, fitsThisComputer, followModelCatalogChanges, followModelCatalogVisibility, formatMemoryBytes, fromDiscovery, ggufEngine, ggufFacts, groupFeatures, hardwareClassesOf, hasCompatibleDecisionInteraction, isChatModelCapability, isContentType, isConversationalModelCapability, isDecisionModelCapability, isFeatureKey, isHelperGguf, isInteractionMode, isListedVariant, isLocalModelCatalog, isManagedAgentModelCapability, isModelCatalogChange, isProjectorName, isSupersedable, isTopTierRating, judgeHeaderFacts, mergeAuditRecordIntoCapabilities, missingShards, modelCatalogRevision, modelClassKnobFor, modelClassName, modelClassPinOf, modelDisplayLabel, modelsForSelectionPurpose, normalizeArch, normalizeModel, normalizeModels, normalizeOs, normalizePromptSettings, onModelCatalogChanged, orderCatalogGroups, orderCatalogModels, outputFormatControlKey, ownModelFileName, parseCapabilities, pickProjector, priceTierRating, quantFromName, rankVariants, readCuratedRows, readDeviceModelFiles, readGgufHeader, readModelCatalog, readModelCatalogStamp, readModelIdentities, readPublicModel, reconcileModelFavoriteIds, reportModelDiagnostic, requireCanonicalCapabilities, requiredArtifactIds, requiredArtifacts, resetSuccessionLogForTests, resolveDeviceModel, resolveLiveModelId, resolveModelMapKnob, resolveNewAgentModelId, resolvePreferredModelChoice, resolveSuccessor, runtimesOnEngine, seesImages, selectActiveModels, selectActiveModelsReady, selectAllModelOptions, selectAllModels, selectAllModelsReady, selectDeprecatedModels, selectDeprecatedModelsReady, selectModelById, selectModelClassConfig, selectModelClassName, selectModelControls, selectModelDetailError, selectModelFetchType, selectModelFullyLoaded, selectModelIdentityById, selectModelIdentityLookupStatus, selectModelLabelById, selectModelLabelWithClass, selectModelNameById, selectModelOptions, selectVariant, serviceCountLabel, setModelDiagnosticsSink, shapeCuratedModels, shardFileName, shardName, speedRatingLabel, speedRatingWord, splitSetProblems, takesInput, toAuditRecord, toDeviceModelFile, validateModelSet, variantVerdict, withModelClass }; export type { AdminOffering, AdminPricingBand, ArtifactUse, AuditCapabilitiesRecord, AvailabilityVariant, CapabilitiesParseContext, CatalogKeyColumn, CatalogKindInput, CatalogKindVerdict, CatalogModel, CatalogTier, CheapestAdequateOptions, CompatStatus, ContentType, CuratedCatalogClient, CuratedModel, CuratedPresentation, CuratedRows, CuratedVariant, DecidedBy, DeviceAvailability, DeviceGraphics, DeviceHardware, DeviceModelFile, DeviceModelResolution, DeviceServedSettings, DiscoveryAmbiguity, DiscoveryArtifact, DiscoveryMedia, DiscoveryResult, DiscoveryRuntime, DiscoverySource, DiscoveryVariant, FeatureBucket, FeatureBucketMeta, FeatureKey, FitCapabilities, GgufHeader, GgufRead, GgufTensor, GroupedFeatures, HardwareClass, HardwareClasses, HeaderFlag, HeaderJudgement, HeaderRefusal, Interaction, InteractionMode, LocalArtifact, LocalCatalogClient, LocalCatalogQuery, LocalCompatRow, LocalModel, LocalModelCatalog, LocalPresentation, LocalRuntime, LocalRuntimeBuild, LocalVariant, Modality, ModelCapabilities, ModelCatalog, ModelCatalogChangeMeta, ModelCatalogChangePayload, ModelCatalogClient, ModelCatalogCoalesceOptions, ModelCatalogConfig, ModelCatalogDirectiveSubscribe, ModelCatalogErrorSink, ModelCatalogListRows, ModelCatalogPgError, ModelCatalogPgResult, ModelCatalogQuery, ModelCatalogState, ModelCatalogVariant, ModelCatalogVariantState, ModelCatalogVisibilityOptions, ModelClassConfig, ModelClassEntry, ModelClassIndex, ModelDiagnosticEntry, ModelDiagnosticsSink, ModelFavorites, ModelFavoritesCache, ModelFavoritesClient, ModelFavoritesConfig, ModelFavoritesErrorSink, ModelFavoritesState, ModelFetchScope, ModelFileHandle, ModelFit, ModelIdentityRow, ModelJson, ModelLabelSource, ModelNeed, ModelOfferingRow, ModelOption, ModelOptionWithLifecycle, ModelRecord, ModelRecordFetchType, ModelRecords, ModelRecordsClient, ModelRecordsConfig, ModelRecordsState, ModelSelectionPurpose, ModelSetEnv, ModelSetFacts, ModelSetValidation, NewAgentModel, NoSelection, OrderedLike, PreferredModelChoice, PreferredModelChoicePorts, PriceTier, PricedCatalogModel, PublicModelClient, RankOptions, RankedVariant, ReadModelCatalogOptions, SelectDevice, Selection, ShardName, SuccessionClient, SuccessionIndex, SuccessionNode, VariantDiscovery, VariantVerdict, Want };