A local endpoint that publishes no limits — LM Studio's /models says nothing —
left every model on the 8192/4096 defaults with no way to say "this one has a
131k window". Each discovered model now gets three optional pins in the
endpoint editor: context window, max output tokens, thinking (auto/on/off).
A pin always wins: user pin → what the endpoint published → compiled default.
Blank means no opinion, so discovery keeps ruling wherever you don't pin. Pins
live in customProvider.overrides keyed by model id, stored apart from the
discovery snapshot: a reconnect refreshes the models and prunes the pins of
models that vanished while keeping the rest.
Pins are written by PATCH /api/custom-provider, which never probes — the whole
point is configuring a server that isn't answering /models correctly. Connect
still probes and still fails loudly when the endpoint is down. A pin under 2048
tokens is rejected on the write side and clamped up on the read path, matching
the compaction-floor discipline.
Connecting was one-way: POST could only replace the base URL, and the only
escape was hand-editing settings.json. DELETE now mirrors POST — drop the
stored section, delete the reserved key, unregister the provider from
piModels() — and the connect editor grows a Disconnect beside Connect,
following the stored-key rows' Delete pattern (quiet text, one click, no
confirm, only once there is an endpoint to remove).
Custom models were pinned reasoning:false, so every one of them offered a
single thinking level ("off") while built-in reasoning models offered the
standard ladder. The endpoint's /models metadata now decides:
- detectReasoning() trusts explicit metadata only — a boolean
reasoning / supports_reasoning / thinking, or a reasoning token in
supported_parameters (vLLM, OpenRouter), capabilities, tags or
architecture.output_modalities. No name guessing: a false positive would
send reasoning_effort to a server that rejects it.
- CustomModel carries the detected flag (persisted, sanitizer keeps it),
toPiModel passes it through, and pi-ai does the rest — default
off/minimal/low/medium/high set, reasoning_effort on the wire.
Verified in the browser against a local OpenAI-compatible mock: the
reasoning-capable model exposes off/minimal/low/medium/high, the plain one
stays at off, and a prompt with medium actually arrives as
reasoning_effort=medium.
The prior change made the vision slot show every model (image-capable first),
which relaxed the filter for built-in providers too — built-in text-only models
became selectable for Vision. Centralize the vision rule in ModelMenuList:
built-ins must report image input; custom-endpoint models stay selectable (their
capability may be unreported). Image-capable still float first.
Review pass: shorten verbose doc comments across the custom-endpoint code,
merge duplicate imports, share one synthetic provider entry between the keys
section and setup dialog, collapse the duplicated connect-editor block, revert
an unrelated settings-route drive-by.