13. Model Catalog & Selection
1. Product rule
Users must be able to use market-available models broadly, not only a curated demo subset.
Therefore:
- Catalog is refreshable
- Custom model IDs are always allowed
- OpenAI-compatible gateways are first-class
- Search is global across enabled providers
2. Selection UX
Model configuration is discovery-first: the AI service is the authority on which models it serves, so the service's own endpoint is asked first and the models.dev catalog is used only to enrich what came back. The bundled catalog is never presented as a browsable list of every published model — a deployment does not necessarily host everything its vendor publishes, and a key is not necessarily entitled to it.
Settings: one provider form
ProviderSetupDialog is a single form, not a wizard:
- Name, Base URL and API Key are all on screen at once. Custom endpoint keys are optional, with no authentication-mode switch. An empty or whitespace-only key on a row without a stored key saves
authKind: "none"withoutsecretValue; a supplied key uses API-key authentication. Editing with an empty key preserves the stored key. Clear saved API key is an icon button beside the input on the same row, with a localized tooltip and accessible name. It stages removal, keeps the input enabled, and stops discovery from reusing that key; Save applies the removal and no-auth mode unless a replacement key is typed. Closing without saving changes no stored credential. Named cloud services keep their existing authentication defaults. - The models section lists what the service returned.
useProviderModelsdebounces edits by 600 ms, guards against out-of-order replies with a monotonic request sequence, and needs no API key so local and no-auth gateways still resolve. A saved provider paints its cached list immediately and then replaces it with the live answer. - Filtering that list is client-side: it is a short live list, not a catalog, so no host search is involved.
- The list header has a checkbox that selects or clears every currently visible row in one step, so a long service list does not have to be ticked one by one. While a search filter is active, "all" means the matching rows only; models already chosen outside the filter stay chosen. Newly added rows adopt
bindingFromModelInfo(or the custom-model defaults); existing bindings keep their advanced overrides. The checkbox is checked when every visible row is chosen, unchecked when none are, and indeterminate when the visible set is mixed. - The same header has a Fetch list action that probes the service immediately, skipping the 600 ms edit debounce and the cache-first paint. Existing rows stay visible while it loads. Automatic discovery on credential edits is unchanged. The control is disabled when no discoverable endpoint is ready, while a probe is in flight, or while the form is saving. Idle-with-a-valid-URL (the edit debounce) stays enabled so Fetch list can skip that window.
- Context window, output limit and initial thinking levels come from
bindingFromModelInfoover the enriched record; per-model overrides live behind a per-row Advanced disclosure.publishedThinkingLevelsdescribes the catalog baseline and seeds known-model bindings. The dialog always offers the seven canonical levels, including for unknown or non-reasoning records, and the runtime uses the binding's explicit set. A model that publishes no level list and no level map but does claim reasoning still seedslow/medium/high. - Limit values render through one shared compact formatter (
formatCompactTokenCount): up to two decimals at theMscale and one at theKscale, trailing zeros dropped, and aKmantissa that would round up to 1000 promoted to theMscale. Published windows on the 1M line therefore stay distinguishable — 1000000 reads1M, 1048576 and 1050000 read1.05M, 1100000 reads1.1M— instead of collapsing into one rounded1M/1.1M, and a limit the service never published reads as an em dash. The settings rows, the Composer picker, the context inspector and the transcript all call this one implementation, while usage counters keep a real0instead of the dash. - When an explicit binding enables
xhighormaxwithout a catalog wire mapping, the runtime sends that canonical value through to the adapter rather than letting the adapter clamp it tohigh. Existing non-null catalog mappings remain authoritative for providers that translate the level. - Named service defaults may derive their wire API from the provider's published
npmadapter (apiStyleForAdapter). A custom endpoint's saved API style is the selected gateway format and takes precedence over a model's published adapter; named and OAuth providers may keep a model-level wire API pin when their catalog requires it. - The built-in OpenCode Go preset also honors a models.dev Claude entry's explicit
anthropic_messagesAPI and distinct AnthropicbaseUrltogether; the runtime then reaches the catalog route's/v1/messagesendpoint. Custom endpoints and foreign relay matches keep their saved provider API and base URL. - A custom model ID is always accepted, so a gateway without a
/modelsroute stays usable.
Settings: selected model order
The AI service and OAuth vendor-account editors share the selected-model pane. Each selected row has a dedicated reorder handle: drag it before or after another visible row, or focus it and press the Up or Down arrow key to move it past the neighboring visible row. Reordering is disabled while the form is busy or fewer than two selected rows are visible. Dragging text still selects it for copying; checkbox, Advanced, and Remove actions keep their existing behavior and do not start a reorder.
The complete models binding array owns the order. Filtering only hides rows: a move inserts the existing binding before or after the visible target in that complete array, preserving hidden bindings and their relative order. Model IDs, aliases, and advanced overrides travel with their bindings. A canceled drag or a drop outside a selected row does not change the draft.
Saving persists the new order through the existing provider update flow, and reopening either editor displays it again. Canceling the editor discards its unsaved order. The provider's compatibility defaultModelId still mirrors the first binding on save, so moving a model to the head changes that provider default. When the edited service or account is the app's default provider, saving also synchronizes the app-level default model to that first binding, as the existing save flow does. The app default is unchanged when editing another provider, and an explicitly bound session keeps its stored model choice. Adding a provider is not a way to change either app default: the default model, and the default image model when the new service brings image models, move to it only while nothing resolves for the app — an empty selection, or one whose provider or model is gone. A default the user can still run stays where it is until they repoint it. No storage schema or IPC contract changes are required.
Discovery precedence
providers.listModels resolves in this order, and the order is load-bearing:
- The stored secret is resolved, so an edit needs no retyped key.
discoverProviderModelsasks the service (/modelsor the per-style equivalent). A non-empty answer wins, is enriched per model throughmodelsDevCatalog.findModel, replaces what the model cache held for that provider, and is reported assource: "remote".- Only if the endpoint published nothing usable —no route, an auth error, or an empty list— does
modelsForProvidersupply the vendor's published models, reported assource: "catalog"together with any discovery error so the UI can say the service did not answer. This result is not cached, so a catalog guess never becomes indistinguishable from a real probe. - Last resort: the provider's configured model,
source: "fallback".
An OAuth vendor account skips step 2 — it has no key to probe with, and pi-ai already knows which models the subscription allows.
The cache belongs to the configuration it was recorded for: saving a provider without a binding it used to have deletes that model's cached row, so a deleted model's recorded limits, capabilities and display name are not handed back to the next add of the same id, and the id leaves the picker's cache-first paint. Only the ids a save dropped are touched — the rest of the discovered list is the service's answer, and a model the service still publishes is recorded again by the next probe, described by the service rather than by a stale answer.
A complete, nonempty discovery answer updates endpoint-list membership atomically. Previously published configured models missing from that answer remain saved and editable, but carry discoveryState: "unlisted" through live results, offline fallback, and restart hydration. Composer excludes these rows from ordinary and recent model choices; it never changes a session's current model or the saved default automatically. A missing id is not proof of failed inference. Manually configured ids never discovered are unknown, not unlisted, and remain selectable. Republishing a model restores its normal choices.
Unconfigured missing discovered rows are pruned, and user-owned rows remain untouched. The settings service-model pane excludes unlisted and configured-only rows; the chosen pane retains bindings for editing. Changing the endpoint or wire format clears old discovery state. Failed, empty, explicitly paginated, truncated, or malformed-row answers do not invalidate missing rows. Partial answers may add/update the rows they actually contain. Unknown server-side truncation cannot be inferred from a successful response alone.
The picker never dumps the raw host error into the model list. A failed probe that leaves no rows shows a one-line "the list is missing" label in the empty pane, while the classified reason (auth, missing list, rate limit, timeout, network, invalid response, or HTTP status) is reported once through the app toast stack. A refused probe that still has cached rows keeps those rows and reports the same reason as a toast.
Subagent editor
The Subagents create/edit sheet reuses the configured, runnable models the Composer already offers (enabled providers with a credential or authKind: none). The control is a searchable, provider-grouped menu anchored to its trigger rather than a native <select>: a definition may pin any configured model, so the list can run to dozens of rows, and only an anchored surface scrolls inside itself and accepts a filter. Inherit-session is the empty value, options are vendorKey-or-name/modelId grouped by provider display name, and a pin that is no longer configured stays as an extra row so an edit cannot silently drop it. Every option comes from the configured provider catalog, so a saved pin is always resolvable; the sheet deliberately offers no free-text model id, and when no provider has a runnable model it shows an empty state with an action that opens Models instead of a hand-typed field. Only the slash in a pin is structural: the provider half is matched by a normalized alias, and a custom endpoint's display name may contain spaces, so the picker and the draft check share one splitter and can never disagree about what is saveable. The thinking selector offers inherit-session (empty), do-not-send, and the seven canonical levels; inherit keeps the session level, while do-not-send leaves the provider adapter's own default untouched. When a generic or duplicate vendor key would be ambiguous, the option uses a unique provider display name; if the names also collide, it uses the stored provider id so no configured provider disappears from the picker.
The sheet also offers an ordered Fallback models list using that same configured-model picker. Users can add, move up/down, or remove alternatives. Already-selected models are excluded from the add menu. Saved-row display identity is resolved from all configured providers, independently of which models are currently selectable. A configured provider keeps its display name and the saved model ID after it is disabled, with a localized disabled status; disabling it must not turn its label back into a raw provider UUID. Other unavailable bindings are marked unavailable. Exact stored provider IDs take precedence over aliases, and ambiguous aliases are not guessed. Only an unresolved provider or model binding retains the raw pin as its display label. The add menu continues to offer only the existing runnable model choices. When selected fallback rows have the same visible label, resolved rows include the provider ID; unresolved or same-provider duplicates include their position and stored pin instead. Display labels and move/remove accessible names use the same resolved identity and status. The stored pin is unchanged. Saved pins that become unavailable stay visible and removable; reopening or editing another field must not drop them. Clearing the list saves fallbackModels: []. Inherit-session remains a primary-only choice. The hint explains that alternatives run after model retries fail, completed tool results are kept, and Stop cancels the whole task. See runtime §5f and ADR subagent-model-fallback.
Advanced
- “Use custom model ID”
- “Refresh catalog”
- per-provider wire API override
- per-model context window, output limit and thinking-level overrides
3. Recent models
Persist recent selected model refs:
type RecentModelRef = {
providerId: string
modelId: string
usedAt: string
}Show top N in picker.
4. Session model binding
Each session stores:
providerIdmodelIdthinkingLevel(off|minimal|low|medium|high|xhigh|max|omit)
Changing model or thinking level mid-session affects subsequent turns only. The stored thinking preference survives restart. An explicit switch to a different provider/model in Composer resets thinkingLevel to the target binding's defaultThinkingLevel, clamped to its enabled levels; if unset, it uses the normal new-session fallback. Selecting the already-active provider/model preserves a manually selected level. The effective request level remains clamped against the selected model binding's enabled levels at execution time, except omit, which is preserved on a reasoning model and sends no thinking override. An empty binding or a binding containing only off resolves to off.
For a newly created session or explicit model switch, the renderer resolves the selected (or app-default) model's ModelBinding. A catalog-matched reasoning model starts at that binding's defaultThinkingLevel (omit is preserved; other values are clamped onto the enabled levels); when the default is unset it falls back to the highest enabled level. An unmatched model starts at off unless its binding stores an explicit default, while its Composer ladder stays available for manual opt-in. Changing a default in Settings does not rewrite existing sessions; an existing session keeps its stored choice until a different model is explicitly selected.
Unpinned sessions still advertise that inherited default model's reasoning capability on session list/get/create/fork/configure. Enrichment does not pin providerId/modelId; desktop session create does, by writing the then-current app default (or Composer draft override) into the durable ids. Later Settings default-model changes do not rewrite an already created session. Opening a legacy row whose ids are still empty snapshots the last used turn, else the current default, so it stops following Settings. The Composer never treats a supportsReasoning: false or empty thinking-level snapshot as authoritative when the selected catalog/binding model exposes levels, so a mid-turn thinking or model pick cannot collapse the menu to Off-only.
5. Capability warnings
If user selects model tagged without tools while in Agent mode:
- show non-blocking warning
- do not hard-block (vendor tags may be incomplete)
6. Refresh behavior
The bundled models.dev snapshot is the startup baseline. Startup reads no remote catalog and uses no ambient credentials. providers.refreshModelCatalog refreshes models.dev explicitly; settings metadata lookups themselves are local.
For keyed endpoints, live discovery supplies IDs and models.dev enriches their metadata. A positive safe integer context/input or output limit reported on a live model-list row takes precedence over the catalog for that field; each field falls back independently to catalog metadata when the endpoint omits it. An omitted or non-positive value is not guessed. For OAuth, the live account endpoint supplies entitled IDs, which are then enriched from models.dev. Successful account lists are authoritative; a failed list preserves the last usable IDs. A live-only ID with no service or catalog capacity stays generic; pi-ai sibling metadata never supplies its chat limits, reasoning or price.
For an unanchored OpenAI-compatible relay, a known model family may use the weight owner's published record when that owner names the model. This record outranks shared reseller matches without changing the configured wire ID. Lookup accepts dotted/dashed version notation, one whitelisted deployment label, and the owner's published canonical generation ID; semantic variants and unpublished generations remain distinct. The owner's own sibling keys (regional, token-plan and coding-plan) are consulted as added evidence when the public key states nothing, so a generation the vendor ships only on one of its keys still resolves; a sibling key never vetoes an answer the public key already gave. When the relay names a generation the owner publishes under another ID, models.dev's own canonical declaration translates the name, but only when at least two distinct publishers corroborate the same owner-prefixed target and the ordinary lookup resolved nothing — the capacity still comes from the owner's record for the translated ID, never from the declarers. If owner aliases disagree on capabilities or limits, no owner record is borrowed. When the owner publishes no matching record, the existing shared catalog matching rules still apply.
source: "cache" reads Host caches and configured bindings without network. source: "refresh" probes the selected endpoint/account and decorates returned IDs. Saved bindings remain visible when discovery is partial or unavailable. Account removal invalidates catalog access; failed Host deletion preserves the still-existing account. Refresh does not rewrite configured models or history.
7. Offline behavior
If refresh fails / offline:
- use cached catalog
- never clear an already-rendered cached list or flash an empty picker
- allow custom model id
- still allow providers with known model ids
- when a saved provider cache is empty or partial, append every configured model binding before applying models.dev metadata decoration, so multi-model settings remain editable and per-model capability state stays aligned
- if no matching bundled models.dev metadata is available offline, keep the configured IDs visible with generic metadata
8. Catalog item schema
type ModelCatalogItem = {
providerId: string
vendorKey: string
modelId: string
displayName: string
source: "bundled" | "discovered" | "user" | "recent"
capabilities: Array<
| "tools"
| "vision"
| "reasoning"
| "streaming"
| "json"
| "long_context"
>
contextWindow?: number
maxOutputTokens?: number
deprecated?: boolean
notes?: string
supportedThinkingLevels?: Array<
"off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
>
/** Which known catalog supplied metadata for this row. */
catalogSource?: "models.dev"
}9. Selection resolution order
When UI/search requests models for picker:
- recent models for enabled providers
- user-defined models
- models.dev records for the matching provider/API URL
- provider discovery/cache for custom or account-specific models
- always include "custom model id" entry action
Deduplicate by (providerId, modelId) with priority: user > models.dev catalog > provider-discovered > recent-only. The catalogSource field records a models.dev match; a provider cache stores only normalized selection fields and is re-decorated from the local raw catalog on the next read.
9.1 Effective model limits
The runtime, the context inspector and the settings surface resolve one effective model window and output cap. Bindings record their provenance independently: contextWindowSource belongs to contextWindow, and maxTokensSource belongs to maxTokens.
catalog— that limit is a models.dev snapshot, so a later correction to its published field replaces it. A refreshed context such asgpt-6.1-sol(1,050,000tokens) stops appearing as a 128k model, and a row added while nothing published its id stops reporting the generic 8.2k output once the record resolves. Only a resolved models.dev record counts as published: when lookup falls back to the generic shape (for example a gateway serving an id several publishers list), its 128k / 8.2k values are not a correction and the stored catalog snapshot stays in force.user— the value was entered in that limit's Advanced control or preset ladder and is never replaced by the catalog, including explicit 128,000 and 8,192 values that equal the generic seeds. Setting a context window cannot change the output-cap source.
Bindings written before either marker preserve their stored window and output cap, including 128,000 and 8,192, because old records cannot distinguish generic seeds from user choices. Only explicit catalog provenance follows later corrections. Unknown models use conservative 128k / 8.2k runtime limits and are never promoted from an ID pattern alone. Settings shows an em dash for an unpublished limit unless the user explicitly configured that value; the Advanced inputs retain the conservative runtime limits until the user edits them. Both markers are optional, so old configs remain readable and downgrade clients ignore them.
The configured user value remains persisted and visible in Advanced settings, but provider safety does not trust an enlarged override beyond a known published window. Outbound output caps, automatic compaction, and overflow classification use the smaller of the configured and published windows; a smaller user value continues to narrow the runtime budget.
9.2 Conversation Composer scope
The conversation Composer is a configured-model picker, not a raw discovery catalog. For each enabled, runnable provider it renders only the model IDs in that provider's persisted models bindings (or the legacy defaultModelId fallback). Cached or freshly discovered records may enrich those rows with display names and metadata, but a discovered model that is not configured is not shown in the conversation list. When discovery is missing, the configured IDs remain visible by themselves.
The Settings provider dialog continues to use discovery to add and configure models; saving a model binding is what makes it eligible for the Composer.
When the combined Composer menu opens, the renderer starts provider-model hydration before the Model submenu is entered. The first visible rows therefore come from the cached catalog or configured bindings; live discovery remains a background update. A configured non-empty alias is resolved from the binding for every equivalent model ID and remains the sole visible model name while the catalog is refreshed.
Vision badges in the Composer use the effective image-input capability for the exact provider/model binding. An explicit supportsImages: true or false wins over the published record; an absent or null value follows it. This lets a configured custom or proxied model show the capability the endpoint was explicitly configured to use without shaping the published ModelInfo. An OAuth provider heading uses its non-secret account label when present, so duplicate accounts from one vendor remain distinguishable; Composer model rows show the configured wire model ID, while a configured alias remains available as the compact selected-chip label.
10. Recent chat model policy
New desktop chats inherit the most recently used available chat model. An explicit draft selection takes precedence; existing sessions retain their pinned provider/model pair. Selecting or submitting an existing chat does not change another chat's binding. Opening a chat alone does not reorder history.
The renderer stores up to 20 provider/model pairs in the local UI preferences, newest first, deduplicated by provider ID and case-insensitive full wire model ID. Only accepted message submission updates history, using that submission's provider/model pair. Selecting a model, changing reasoning, or applying deferred configuration alone does not count as usage. Rejected submissions do not replace the preference. Sending in an older chat does update the recent usage order. This preference survives renderer reload and app restart and is not part of cloud configuration sync.
Inheritance skips deleted, disabled, unauthenticated and image-generation bindings. With no usable history it uses a valid legacy app selection, otherwise the first available configured chat binding. Legacy settings and provider default fields remain readable for scheduled jobs and older consumers; no database or IPC migration is required. Settings has no fixed chat-default picker or service "Make default" action. Image-generation selection remains independent.
The combined Composer model menu shows the three most recent available bindings first. Each row identifies the provider as well as the model or alias. Search is always visible and searches all configured models. Other models expand inline, grouped by provider, without duplicating recent entries. With no history, all models are visible immediately. The current model is checked; reasoning controls remain below the list. Discovery enriches exact configured IDs without adding unconfigured choices. Model and reasoning changes retain the existing session configuration and permission boundaries.
11. Capability gating
| mode/feature | required capability |
|---|---|
| Agent mode tools | tools (warn if missing; hard-block only if runtime cannot function) |
| image input | vision |
| reasoning UI affordances | reasoning |
| structured repair helpers | json optional |
Warnings are non-blocking unless execution is impossible.
Composer context-window labels resolve the exact binding with effectiveContextWindow: explicit user limits override discovery, catalog-owned limits follow discovery, and legacy bindings preserve their saved limits. A binding with a valid saved limit still displays it when discovery is unavailable. This display resolution does not modify the discovered catalog or runtime safety budgets.
11.1 Reasoning capability resolution
- Resolve published Pi thinking metadata for the exact physical model.
- Project explicit binding levels at the account boundary. Known unsupported levels and native null mappings remain unavailable; saved settings are not rewritten. Clamp the dispatched request using Pi's public helper.
- Unknown free-form IDs retain the generic Desktop ladder for manual opt-in, with
offas the unset default. Explicit binding defaults remain scoped to new drafts/sessions rather than overwriting existing session preferences. omitremains a distinct request choice: no thinking field is sent. Agent bookkeeping may storeoffwhile the request omits reasoning.- The Composer renders effective enabled levels in canonical order. Dispatch normalization cannot silently enable a native unsupported level.
11.2 Vision capability resolution
- Resolve the published image-input baseline from the matching model record.
- Apply the exact configured binding's
supportsImagesvalue to that baseline. An absent ornullvalue follows the published capability;trueenables image input for a configured endpoint even when its published record is text-only, andfalsedisables a published image capability. - The Composer model-row vision badge and the main attachment transport gate use this same effective result. An unknown or custom model without an explicit binding override remains on the conservative path-fallback route; discovery or cache metadata alone cannot promote it to image transport.
- The main process prepares pasted images as content-addressed refs. A vision-capable model receives images within the 10 MB app-side inline bound as transient image blocks; other cases receive a safe
@path.
11.3 Settings model-add metadata
When the setup form adds a model the service returned, its initial context window, output limit, capability badges and thinking defaults come from bindingFromModelInfo over the enriched record, so the common path needs no manual token entry. The enrichment lookup is:
- Resolve account/vendor identity and normalized endpoint against Pi providers.
- Read the published typed chat model separately from effective user overrides.
- When Pi has no chat record, display-only operation metadata may describe a non-chat settings entry. It cannot promote that entry into a chat model or grant operation entitlement.
- Use generic metadata on a miss; no metadata lookup sends account credentials.
- Apply the conservative relay rules in §11.3.1 without rewriting wire IDs.
These rules attach published metadata only: they never rewrite the configured wire model ID or infer reasoning from a suffix. An unmatched free-form ID keeps the host's generic capability snapshot, while Composer exposes the canonical thinking ladder for explicit manual opt-in.
11.3.1 Relay metadata matching
The Pi adapter preserves the configured wire ID. Unknown endpoints may attach metadata from an exact, case-insensitive final / segment. A unique official publisher compatible with the ID family wins; conflicting capability/thinking candidates remain unknown. Exact served IDs are checked first. Only a narrow whitelist of deployment-added environment/context labels (for example -test and -1m) may be removed as a fallback to a published base ID. Thinking, preview, and dated-release suffixes are not stripped, and spelling is never fuzzy-corrected.
Known provider lookup and aliases stay authoritative. An ambiguous known same-endpoint miss does not borrow from arbitrary publishers. There is no majority-vote capability or median-limit algorithm. Unmatched IDs use conservative generic metadata and explicit binding overrides.
12. Refresh strategy
- manual refresh button in settings/model picker
- optional refresh on provider create/test success
- no aggressive background polling in MVP
- refresh failures keep previous cache and surface non-fatal error
Electron decorates cached and freshly returned model rows from the local models.dev snapshot. Runtime model resolution uses the same resolved metadata for limits and capabilities while pi-ai remains the selected transport adapter. Provider discovery remains the ID source for custom/account-specific models absent from the snapshot.
13. Search behavior
There is no catalog search channel. The models a user chooses from are the ones their service returned, and that list is short enough to filter in the renderer: the provider form matches the typed text against model id and display name with a plain case-insensitive substring test.
The Composer picker likewise searches the configured models only, matching model id, display name, published family and the account-aware provider display name (composerModelMatchesQuery).
Model ids are compared case-insensitively wherever a chosen model is matched against a returned one, so a hand-typed GPT-5 and a published gpt-5 are the same model to the check mark, the toggle and the duplicate guard.
14. Acceptance criteria
- [ ] the model list a user chooses from is the one their service returned; the bundled catalog is never offered as a browsable list of every published model
- [ ] the catalog is consulted only when the endpoint publishes nothing usable, and that result is reported as
catalog, is not cached, and carries the discovery error - [ ] adding an AI service is one form, with no stages to step through, and the wire API is derived from the published adapter instead of being asked for
- [ ] adding a discovered model requires no manual context-window or output-token entry; overrides stay behind a per-model Advanced disclosure
- [ ] the API-key path and the OAuth vendor-account path use the same live model list and the same binding shape
- [ ] selected models can be reordered by drag handle or Up/Down arrow keys in both editors; saving and reopening preserves the order, aliases, and overrides, and a filtered move preserves hidden bindings and their order
- [ ] canceling a drag or the editor preserves the previous applicable order; busy forms disable reordering, and text-copy and row actions still work
- [ ] an unsaved provider can be probed from the form before it is persisted, and a saved one reuses its stored secret without a retyped key
- [ ] custom model id path works without catalog hit
- [ ] recent models surface in the picker
- [ ] refresh merges into cache and picker (never destructively replaces)
- [ ] restart hydrates the prior catalog before live refresh, and offline refresh keeps the cached picker populated
- [ ] capability badges visible
- [ ] session model change applies to next turn only
- [ ] a newly created session stores the then-current default provider/model, and later default-model changes do not rewrite that session
- [ ] a catalog-matched reasoning model defaults a new session to that binding's stored thinking level (clamped onto the enabled set; strongest-enabled only when unset); an unmatched model starts at
offwithout an explicit binding default while retaining the manual thinking ladder in Composer - [ ] the settings picker always exposes the canonical thinking ladder; published levels seed known models and explicit binding levels clamp the same way in Composer, Electron main, and the pi sidecar
- [ ] models.dev metadata wins for a matching provider/model; an ID absent from it uses the generic shape while pi-ai supplies only transport/OAuth and typed non-chat operation support
- [ ] provider settings and cached discovery cannot replace known catalog capabilities; explicit binding edits remain persisted configuration
- [ ] a models.dev limit correction reaches an already saved
catalogbinding without deleting and re-adding the model, while a number the user entered in Advanced (user) survives every correction, a hand-entered128,000included - [ ] a binding saved before the provenance marker resolves deterministically: the generic 128k seed follows the catalog and every other value stays as stored
- [ ] the provenance marker survives a provider save/read round trip and an unmarked record keeps working
- [ ] unknown free-form models remain runnable without invented capabilities
- [ ] a models.dev record and an unknown generic record resolve through the same selected transport without sending provider credentials to the remote catalog
- [ ] compact limit text never reads above the published value, keeps the neighbouring 1M-line windows apart (
1M/1.05M/1.1M), and never renders aKmantissa at or above 1000
Image model binding
The default conversation model has a separate Image generation model row below it. Model Advanced can select that unique binding; provider form Save commits it, Cancel discards it, and replacing it leaves the conversation default unchanged. See image generation and editing for the tool and batch contract.
