import * as react from 'react'; import { ComponentType, ReactNode } from 'react'; 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]; /** * 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"; type FeatureBucket = "schema" | "tools" | "search" | "x_search" | "thinking" | "extraction" | "other"; 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[]; } /** * `@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; /** 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[]; }; } /** * 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; }; /** * `@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; }; 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"; } 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; } /** * `@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 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; } 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; } type ModelCatalogLinkComponent = ComponentType<{ href: string; className?: string; children?: ReactNode; }>; /** One model a host runs on the person's own computer. */ interface LocalModelEntry { /** The value `onValueChange` receives (the conversation's model override). */ id: string; name: string; /** Brand glyph key (e.g. "Meta"); absent draws the neutral glyph. */ maker?: string | null; contextWindow?: number | null; /** The computer cannot hold it today: the row is dimmed and says why, but stays pickable. */ needsMoreMemory?: boolean; /** * On the computer but it cannot run there, and why, in one line ("Part 2 of 3 is missing"). The * picker lists it under "Cannot run here" with this reason, dimmed and never pickable. */ unusable?: string; /** * The model is loaded and answering now. With a port `stop`, the row draws Stop; with `start` * and not running, Start. Absent = the host does not say (no control is drawn). */ running?: boolean; /** The host is loading it right now: the row's control shows its pending state. */ starting?: boolean; } /** * The host's local-model source (a store: `getModels` + `subscribe`, like the catalog). The picker * draws its models as one "On this computer" group marked local. Starting and stopping a model and * getting more of them are capabilities by presence: a host that supplies `start` / `stop` / `getMore` * gets a compact Start/Stop control per row and one "Get More Models..." row; a host that leaves them * out gets neither (its own screens stay the only way). */ interface LocalModelsPort { /** Must return the SAME array until the list changes (it is a store snapshot). */ getModels(): readonly LocalModelEntry[]; subscribe(listener: () => void): () => void; /** Group heading. Default "On this computer". */ groupLabel?: string; /** * Open the local model in its own private conversation (the host's Confidential Chat), instead * of switching this conversation to it. Present = a local pick calls ONLY this: the cloud value * and the class pin stay untouched, and no draft or context is carried. Absent = the pick reports * the id through `onValueChange` (the override path). */ open?(id: string): void; /** * Start the model (load it so it can answer). Present = a row that is not running draws Start. The * row shows a pending state until the returned promise settles; a rejection says why on the row. */ start?(id: string): void | Promise; /** Stop the running model. Present = a running row draws Stop (same pending state and failure line). */ stop?(id: string): void | Promise; /** Open the host's place to find and install more models. Present = one "Get More Models..." row ends the group. */ getMore?(): void; /** * Why the list could not be read just now, one line ("Your computers' models can't be read"), or null. * Set = the group shows that line, with Try Again when `retry` exists, instead of going quiet (the last * list stays under it). A change is announced through `subscribe`. */ readFailure?(): string | null; /** Read the list again: the failure line's Try Again. */ retry?(): void; /** True while the first read is in flight and nothing is listed yet: the group says "Checking the models". */ checking?(): boolean; /** * The line the group shows when the port lists no model at all ("No model on your computers yet"), with * Get More when the port has it. Absent = an empty port draws nothing. */ emptyLine?: string; } /** * How the person may reach a cloud model in THIS place (a chat composer passes it to its own picker, * like `localModels`). Both fields are optional; a host that supplies neither changes nothing. */ interface ModelAccessPort { /** * Set = every cloud model is listed disabled with this one-line reason (never hidden), e.g. a * confidential chat in an organization that requires its own hardware: "Blocked by your organization". */ cloudBlockedReason?: string | null; /** * True when the person's own API key serves this model: the picker and the chat's model chip say * "Your key" and show no model points. */ usesOwnKey?(modelId: string): boolean; } interface ModelCatalogUiPorts { localModels?: LocalModelsPort; access?: ModelAccessPort; isSuperAdmin?: boolean; canUseTopTier?: boolean; hiddenModelIds?: readonly string[] | undefined; LinkComponent?: ModelCatalogLinkComponent; settingsHref?: string | null; renderErrorAction?: (error: string | undefined) => ReactNode; } interface ModelCatalogProviderProps extends ModelCatalogUiPorts { catalog: ModelCatalog; favorites: ModelFavorites; children: ReactNode; } declare function ModelCatalogProvider({ catalog, favorites, children, localModels, access, isSuperAdmin, canUseTopTier, hiddenModelIds, LinkComponent, settingsHref, renderErrorAction }: ModelCatalogProviderProps): react.JSX.Element; declare const useModelCatalogStore: () => ModelCatalog; declare const useModelCatalogUiPorts: () => ModelCatalogUiPorts; /** * The picker's catalog hook. Reads the cached variant straight away and starts the read when it * was never made (or is stale — then the rows stay on screen while it refreshes). */ declare function useModelCatalog(variant: ModelCatalogVariant): { models: CatalogModel[]; isLoading: boolean; error: string | null; reload: () => void; }; /** The picker's stars: the favorites store, reconciled with the canonical ledger when `active` flips. */ declare function useModelFavorites(active: boolean): { favoriteIds: readonly string[]; favoriteSet: Set; toggleFavorite: (id: string) => void; }; /** * The host's local models; the empty list (stable) when no port is supplied. A port passed straight * to the picker (`override`, e.g. by a chat composer) beats the provider's. */ declare function useLocalModels(override?: LocalModelsPort): { models: readonly LocalModelEntry[]; groupLabel: string; open: ((id: string) => void) | undefined; start: ((id: string) => void | Promise) | undefined; stop: ((id: string) => void | Promise) | undefined; getMore: (() => void) | undefined; /** The port's read-failure line, or null (`LocalModelsPort.readFailure`). */ readFailure: string | null; retry: (() => void) | undefined; checking: boolean; emptyLine: string | null; }; /** The cloud-access rules for this picker; a port passed straight to the picker beats the provider's. */ declare function useModelAccess(override?: ModelAccessPort): ModelAccessPort; /** The label a model reached through the person's own key carries, in the picker and on the chat's chip. */ declare const YOUR_KEY_LABEL = "Your key"; /** * A model offered in several CLASSES (Matrx Fast, Matrx Lightning, ...) is * several products. A picker that configures a call MUST carry the chosen * class as the offering pin, or the server runs the preferred class and the * person's choice is silently lost. So every caller declares one of: * - `pinnedOfferingId` + `onOfferingPinChange` — one row per class, the * selected class's offering travels with the model; or * - `modelOnly` — the picker names a MODEL as a relationship target * (fallback, alias, audit), so one row per model. */ type ModelClassSelectionProps = { /** * Currently pinned `ai.offering` uuid (agent settings `offering_id`). * null/undefined = Auto — the server picks the preferred offering. */ pinnedOfferingId: string | null | undefined; /** * Called with the chosen class's offering uuid, or `undefined` to clear * the pin (Auto). Selecting a single-class model clears it. Within one * gesture the class is ALWAYS reported before `onValueChange` (and a * class-only change on the current model reports no value at all). */ onOfferingPinChange: (offeringId: string | undefined) => void; modelOnly?: never; } | { /** The value is a model as a relationship target — never a call. */ modelOnly: true; pinnedOfferingId?: never; onOfferingPinChange?: never; }; type ModelListDropdownProps = ModelListDropdownBaseProps & ModelClassSelectionProps; interface ModelListDropdownBaseProps { /** * The saved model id. A superseded model (deprecated/retired with a live `successor_id` chain) is * shown as its successor, and `onValueChange(successor)` fires once so the host's setting heals. */ value: string | null | undefined; /** * The person's local models for THIS picker only (a chat composer passes its host's port). Beats * the provider's port. Pickers that do not pass it never see local rows unless their provider * supplies one. */ localModels?: LocalModelsPort; /** * Cloud-access rules for THIS picker (a chat composer passes its host's): a blocked reason that * lists every cloud model disabled with why, and which models run on the person's own key. * Beats the provider's port. */ access?: ModelAccessPort; onValueChange: (modelId: string) => void; /** REQUIRED — input modalities the model must accept (seeds the filter). */ inputModalities: Modality[]; /** Optional — output modalities the model must produce (seeds the filter). */ outputModalities?: Modality[]; /** * The execution contract this picker selects for. `"agent"` admits both * conversational models and decision holders, because an agent's message * may be a `decision_questions` part rather than a conversational turn — * see `ModelSelectionPurpose` in the capabilities module. */ selectionPurpose?: ModelSelectionPurpose; /** * Optional catalog constraint for specialized surfaces (for example a * user's active-model preference or a replacement-model allowlist). The * canonical picker still owns rendering, search, filters, favorites and * details; callers only declare which model ids are eligible. */ allowedModelIds?: readonly string[]; /** * Force one catalog variant. Omit for the normal user picker with the * super-admin Admin toggle. Administrative relationship editors use * "admin" so non-routable/deprecated catalog rows remain selectable. */ catalogVariant?: ModelCatalogVariant; /** * Adds a first-class null/default choice (for example "Platform default" * or "Agent default"). `onClear` receives that choice; model selection * continues through `onValueChange` with a real model id. */ emptyOptionLabel?: string; onClear?: () => void; /** Text shown when no model/default is selected. */ placeholder?: string; /** Eligible ids to float ahead of the normal alphabetical order. */ priorityModelIds?: readonly string[]; className?: string; /** Settings rows use the same standard select trigger as other setting controls. */ triggerVariant?: "default" | "settings"; /** * `inline`: no trigger and no popover/drawer — the picker body fills its * parent (a menu's side panel on desktop, a pushed page on a phone). The * host closes its own container from `onValueChange`. */ presentation?: "picker" | "inline"; /** DOM id for the picker trigger. */ id?: string; /** When true, renders the current value read-only — no picker opens. */ disabled?: boolean; disabledTitle?: string; /** * Accessible name of the trigger (a settings row passes its label). A * hyphenated JSX prop is not type-checked, so without this declaration the * name was silently dropped and the trigger read as its current value only. */ "aria-label"?: string; "aria-labelledby"?: string; } declare function ModelListDropdown({ value: savedValue, localModels: localModelsProp, access: accessProp, onValueChange, inputModalities, outputModalities, selectionPurpose, allowedModelIds, catalogVariant, emptyOptionLabel, onClear, placeholder, priorityModelIds, pinnedOfferingId, onOfferingPinChange, modelOnly, className, triggerVariant, presentation, id, disabled, disabledTitle, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }: ModelListDropdownProps): react.JSX.Element; /** * `@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; /** Subscribe to a slice of a model catalog's records. `select` must return a stable value for unchanged state. */ declare function useModelRecordsSelector(records: ModelRecords, select: (state: ModelRecordsState) => T): T; /** * How many `catalog_changed` notices this page has seen. A reader that holds its OWN rows (the admin model * editor) puts it in its effect dependencies / query key, so an edit made anywhere re-reads it. */ declare function useModelCatalogRevision(): number; /** Run `onChange` on every model catalog change notice (the latest callback; never stale). */ declare function useOnModelCatalogChanged(onChange: (payload: ModelCatalogChangePayload) => void): void; /** * `@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 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[]; } /** 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; } /** 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; } type DiscoveryKind = "text" | "vision" | "speech_to_text" | "text_to_speech" | "image" | "video" | "embedding" | "any"; type DiscoverySort = "downloads" | "likes" | "updated" | "trending"; type DiscoveryMode = "catalog" | "device"; interface DiscoverySearchQuery { query: string; kind: DiscoveryKind; sort: DiscoverySort; cursor: string | null; } /** One search hit (contract: POST /ai-catalog/discover/search → results[]). */ interface DiscoverySearchHit { repo: string; name?: string | null; maker?: string | null; maker_avatar_url?: string | null; pipeline_tag?: string | null; kind?: string | null; downloads?: number | null; likes?: number | null; license?: string | null; gated?: boolean | string | null; updated_at?: string | null; thumbnail_url?: string | null; has_gguf?: boolean | null; size_bytes?: number | null; } interface DiscoverySearchPage { results: DiscoverySearchHit[]; next_cursor: string | null; } /** What the host gives the view. Errors are thrown as `Error`s whose message is a sentence for the screen. */ interface DiscoveryPort { /** Resolve a pasted link (or a search hit's repo) to its versions. */ resolve(link: string, signal?: AbortSignal): Promise; /** Search by kind; absent = this host has no search (the tab says so). */ search?(query: DiscoverySearchQuery, signal?: AbortSignal): Promise; /** This computer's facts; null on the web (memory lines then say what a version needs). */ device: SelectDevice | null; /** The runtimes the judge checks formats against (the catalog's). Empty = format not judged here. */ runtimes?: readonly LocalRuntime[]; /** * "catalog" (admin: the Catalogs dialog, the Discovery page): every version can be added — whether it * runs is per runtime and per device, never a gate on the catalog; each row names its runtime and says * when Matrx 2 has none yet. "device" (a person's Find models): Choose is offered only for versions * that run here, and a disabled Choose always has its reason beside it. Default "device". */ mode?: DiscoveryMode; /** What Choose does (web admin: promote; web person: add as my model; Matrx 2: download + add). Resolves to a sentence shown after. */ onChooseVariant(model: LocalModel, variant: LocalVariant, result: DiscoveryResult): Promise | string | void; } interface ModelDiscoveryProps { port: DiscoveryPort; /** The Choose button's words. Default "Choose". */ chooseLabel?: string; /** Which tab opens first. Default "search" when the port can search, else "link". */ initialTab?: "search" | "link"; /** A controlled result (a host that already holds one, e.g. a saved candidate). */ result?: DiscoveryResult | null; /** Told whenever a resolve answers (or the result is cleared). */ onResultChange?: (result: DiscoveryResult | null) => void; /** Extra controls on each version row (beside Choose). */ renderVariantActions?: (variant: LocalVariant, ranked: RankedVariant) => ReactNode; /** Extra panels under the result (the admin's promote / placement / test panels). */ renderActions?: (result: DiscoveryResult, ranked: RankedVariant[]) => ReactNode; /** The want the judge applies. Default best fit, core files only. */ want?: Want; /** A host that reads some links elsewhere claims them here (return true); the view then does not resolve it. */ interceptLink?: (link: string) => boolean; } declare function ModelDiscovery({ port, chooseLabel, initialTab, result: controlled, onResultChange, renderVariantActions, renderActions, want, interceptLink, }: ModelDiscoveryProps): react.JSX.Element; interface DiscoveryResultViewProps { result: DiscoveryResult; device: SelectDevice | null; runtimes: readonly LocalRuntime[]; want?: Want; chooseLabel?: string; /** See `DiscoveryPort.mode`. Default "device". */ mode?: DiscoveryMode; onChoose: (model: LocalModel, variant: LocalVariant) => Promise | string | void; renderVariantActions?: ((variant: LocalVariant, ranked: RankedVariant) => ReactNode) | undefined; renderActions?: ((result: DiscoveryResult, ranked: RankedVariant[]) => ReactNode) | undefined; } /** A resolved model: its header, the source's reason when the originals will not run, and every version judged. */ declare function DiscoveryResultView({ result, device, runtimes, want, chooseLabel, mode, onChoose, renderVariantActions, renderActions }: DiscoveryResultViewProps): react.JSX.Element; export { DiscoveryResultView, ModelCatalogProvider, ModelDiscovery, ModelListDropdown, YOUR_KEY_LABEL, useLocalModels, useModelAccess, useModelCatalog, useModelCatalogRevision, useModelCatalogStore, useModelCatalogUiPorts, useModelFavorites, useModelRecordsSelector, useOnModelCatalogChanged }; export type { DiscoveryKind, DiscoveryMode, DiscoveryPort, DiscoveryResultViewProps, DiscoverySearchHit, DiscoverySearchPage, DiscoverySearchQuery, DiscoverySort, LocalModelEntry, LocalModelsPort, ModelAccessPort, ModelCatalogLinkComponent, ModelCatalogProviderProps, ModelCatalogUiPorts, ModelDiscoveryProps, ModelListDropdownProps };