/** * Provider/model catalog feeding the settings card dropdowns. * * Data sources mirror the built-in Models page and the model-selection * surface: * - `llm.providers` — configurable provider directory merged with live routes * (`{ providers: [{ provider, displayName, settingsNs, settingsPath, * active, declared? }] }`) * - `llm.models` — advisory per-provider model catalog * (`{ groups: [{ id, name, models: [{ id, name?, … }] }], failures }`) * - `session.modelCatalog` — same `{ groups }` shape, served by the * Plugins-tab-native face, so it covers pages where the legacy bulk * `llm.models` RPC never existed (preferred over the synthesis below) * - `settings.describe` — optional enrichment: cross-references each row's * settings address to derive `configured`, so the card can group * user-configured providers ahead of the bare directory (same semantics as * the built-in Models page). * * Refresh triggers mirror ui-model-selection's convergence sources: * `llm/adapters-updated` (registry topology commits) and * `settings/document-updated` (configuration-owned catalogs). * * Everything here is advisory. The card always keeps a free-text escape * hatch, because proxy rules legitimately target things no catalog lists: * wildcard patterns (`vendor-*`, `*`) and providers whose adapter is not * installed (yet). */ /** One selectable provider row (shape of `llm.providers.providers`). */ export type CatalogProvider = { provider: string; displayName: string; active: boolean; /** Settings namespace whose section configures this provider (absent = no settings address). */ settingsNs?: string; /** Path from that section's root to this provider's profile object. */ settingsPath?: string[]; /** Adapter knows this route only because configuration declared it. */ declared?: boolean; /** * Mirrors ui-settings-models' derivation: the provider's settings namespace * exists AND its profile object is present in the merged value. Undefined * when unknown (no settings address, or settings.describe unavailable). */ configured?: boolean; }; /** One selectable model row (subset of an `llm.models` group entry). */ export type CatalogModel = { id: string; name?: string; }; /** * Host-computed directory mirror (`model-proxy.catalog` in our own settings * namespace). Same data as the Typert remotes, delivered through the card's * own settings scope — the one channel available on every page the card * renders on. Remote rows always win; mirror rows only fill gaps. */ export type MirrorModel = { id: string; name?: string; }; export type MirrorProvider = { provider: string; displayName?: string; active?: boolean; }; export type DirectoryMirror = { providers: MirrorProvider[]; models: Array<{ provider: string; models: MirrorModel[]; }>; }; /** * Normalize mirror `models` entries into the card's provider→models map. * Malformed entries are skipped, duplicates collapse — total, never throws. */ export declare function mirrorModelsToMap(mirror: DirectoryMirror | undefined): Record; export type CatalogSnapshot = { status: 'idle' | 'loading' | 'ready' | 'unavailable'; providers: CatalogProvider[]; /** Provider id → catalog models; absent/empty means no known models. */ modelsByProvider: Record; }; /** Structural wire face so tests can pass fakes without a transport. */ export type CatalogApiFace = { llm: { providers(payload: object): Promise; models(payload: object): Promise; }; /** * Optional settings mirror used to derive each provider's `configured` * flag. Absent or failing keeps configured undefined — grouping degrades * to the flat directory list. */ settings?: { describe(payload: object): Promise; }; }; /** Structural face of `ctx.remote` — `$on` returns its own disposer. */ export type CatalogRemoteFace = { $on(event: string, listener: (...args: never[]) => void): () => void; llm?: { listProviders?(): Promise; listConfigurableProviders?(): Promise; discoverModels?(ns: string, req: unknown): Promise; }; settings?: { describe?(): Promise; }; /** * Session model catalog — the same `{ groups: [{ id, models }] }` shape as * the retired bulk `llm.models`, served by the face the built-in * Plugins-tab cards already consume (`remote.session.modelCatalog()`), so * it stays mounted wherever this card is rendered. */ session?: { modelCatalog?(): Promise; }; }; type Listener = () => void; /** * Unwrap one RPC envelope (`{ result: { ok, value | error } }`), throwing on * a business rejection with its wire message. */ export declare function unwrapRpc(response: unknown): unknown; /** * Unwrap one Typert RemoteResult envelope (`{ ok: true, value }` / * `{ ok: false, error }`), throwing on a business rejection with its wire * message. * * Non-envelope payloads (a bare array, an already-unwrapped `{ providers }` * object) pass through untouched, so hosts that return payloads directly * keep working. */ export declare function unwrapRemoteResult(response: unknown): unknown; /** * Extract selectable provider rows from an unwrapped `llm.providers` value. * Malformed rows are skipped, never fatal — the dropdown degrades to whatever * is parseable. * * A Typert RemoteResult envelope is tolerated here as well (unwrapped first), * so the function stays total no matter which layer forgot to unwrap. * * DSH 0.1.2 renamed `provider` → `id` on live routes (`remote.llm` * `listProviders()` returns `{ id, name }`) and serves the configurable * directory separately via `listConfigurableProviders()` (`{ provider, * displayName, settingsNs, settingsPath, declared? }`). Accept both shapes * and default `active` to true when the field is absent (new remote omits it * on live rows; joined directory rows carry their own derivation). */ export declare function pickProviders(value: unknown): CatalogProvider[]; /** Deep-get along a path; undefined as soon as any hop is missing. */ export declare function getPath(value: unknown, path: readonly string[]): unknown; /** * Attach `configured` to provider rows, mirroring ui-settings-models' * derivation: the settings namespace exists in the mirror AND either the * provider configures via the whole section (`settingsPath` empty) or its * profile object is present in the merged value. Rows without a settings * address keep `configured` undefined (unknown, not false). */ export declare function attachConfigured(providers: CatalogProvider[], namespaces: ReadonlyArray<{ ns?: unknown; value?: unknown; }>): CatalogProvider[]; /** * Merge live routes with the configurable directory, mirroring the built-in * Models page (`joinProviderDirectory` in `dsh-client-ui-settings-models`): * declared directory rows first (in declaration order, `active` derived from * live membership), then live-only routes with no declaration appended. * * - `registered`: live routes from `remote.llm.listProviders()` (`{id,name}`). * - `directory`: configurable providers from * `remote.llm.listConfigurableProviders()` (`{provider,displayName, * settingsNs,settingsPath,declared?}`). * * Either side may be absent/empty (older hosts serve only one face); the join * degrades to whatever side is present instead of blanking the dropdown. * Returns rows in the `{providers:[...]}` row shape `pickProviders` * understands, so callers normalize through a single funnel. */ export declare function joinProviderDirectory(registered: unknown, directory: unknown): { providers: unknown[]; }; /** * Group an unwrapped `llm.models` value into provider id → model rows. * Failures entries are ignored (the dropdown simply offers nothing for a * provider whose catalog failed host-side). */ export declare function pickModelsByProvider(value: unknown): Record; /** * uSES-compatible store over the two catalog RPCs. Loads once on bind(), * reloads on forwarded topology/settings events, and keeps the last good * rows across a failed background refresh so an outage never blanks open * dropdowns. */ export declare class ProviderCatalogStore { private readonly api; private readonly remote?; private readonly listeners; private snapshot; private generation; private unsubs; private bound; constructor(api: CatalogApiFace, remote?: CatalogRemoteFace | undefined); /** Idempotent: start loading and subscribe to refresh events. */ bind(): void; /** Stop listening; in-flight loads become no-ops via the generation guard. */ dispose(): void; subscribe(listener: Listener): () => void; getSnapshot(): CatalogSnapshot; /** * Fetch both catalogs. Each call settles independently (`allSettled` * semantics via mapped thunks): a provider-catalog failure fails the whole * load, while a model-catalog failure degrades to an empty model map — * models are an enrichment, providers drive the primary dropdown. * * DSH 0.1.2 moved `llm.providers` from `connection.api` (envelope RPC) to * `remote.llm` Typert remotes. Every Remote method resolves to a * RemoteResult envelope (`{ok:true,value}` / `{ok:false,error}`) — never a * bare payload — and the provider surface is split in two, mirroring the * built-in Models page: `listProviders()` (live routes) joined with * `listConfigurableProviders()` (configurable directory). Try the Typert * face first, fall back to the legacy envelope RPC for older hosts. */ load(): Promise; /** * Keep last good rows across a failed refresh; the card falls back to * free-text inputs only when the snapshot holds no providers at all. * * A provider-less failure is the user-visible breakage (every dropdown * degrades to a text box), so it warns loudly instead of failing silent * like the models/settings enrichments below it. */ private markUnavailable; private set; } export {}; //# sourceMappingURL=catalog.d.ts.map