fix(inference): synth local-runtime entry for list_models when no cloud_providers row (Sentry TAURI-RUST-28Z)#2785
Conversation
…TAURI-RUST-28Z)
`inference_list_models("ollama")` previously failed hard with
"no cloud provider with id or slug 'ollama' found" whenever no
`ollama` entry existed in `config.cloud_providers` — even though the
AI panel intends to register one (see `cloud_providers::is_slug_reserved`
doc-comment). Users on drifted configs (upgrade from a build that only
persisted `config.local_ai.base_url`, flush-vs-probe race, or a removed
custom entry) hit it on every dropdown render. Self-hosted Sentry
TAURI-RUST-28Z: 24 events / 7d, `domain=rpc operation=invoke_method
method=openhuman.inference_list_models`.
Add `synthesize_local_runtime_entry` in
`src/openhuman/inference/provider/ops.rs` that produces a transient
`CloudProviderCreds` for the well-known local-runtime slugs `ollama`
and `lmstudio` only. `list_configured_models_from_config` chains
`find().or_else(synth)` so an explicit `cloud_providers` entry still
wins (e.g. a remote ollama box) — synth is reached only when the find
returns `None`. The synth routes to `<ollama_base_url>/v1` (OpenAI-
compat surface — same `{"data": [...]}` shape the existing parser
handles) and to `lm_studio_base_url(config)` (already ends in `/v1`),
both with `AuthStyle::None` so the probe runs without an Authorization
header on loopback.
The contract is intentionally narrow: only `ollama` and `lmstudio` map
to synth; everything else (`openai`, `anthropic`, opaque ids, typos)
continues to produce the unchanged "no cloud provider" error so a
mistyped slug doesn't silently route to localhost.
7 new tests pin: synth-shape per slug, base_url override, the explicit-
entry-wins precedence contract, the regression contract (missing entry
+ known local slug → synth), and the rejection contract (missing entry
+ unknown slug → still errors). cargo test --lib
openhuman::inference::provider::ops::tests → 31/31 pass.
Sentry-Issue: TAURI-RUST-28Z
📝 WalkthroughWalkthroughThe PR implements local-runtime provider synthesis: when ChangesLocal-runtime provider synthesis fallback
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
graycyrus
left a comment
There was a problem hiding this comment.
@CodeGhost21 hey! the code looks good to me, but CI still has a few checks pending (Rust Core Coverage, Rust Core Tests + Quality, Windows E2E, and Build & smoke-test core image). once those come through green, i'll come back and approve this. let me know if you hit any issues.
Walkthrough: two files, 217 lines net — almost entirely new test code. ops.rs adds synthesize_local_runtime_entry (a private function), hooks it into the existing find().or_else(synth) chain, and backs it with 7 focused unit tests. cloud_providers.rs gets a single doc-comment paragraph pointing at the new fallback. No public API surface changes.
| File | Change | Risk |
|---|---|---|
inference/provider/ops.rs |
new private fn + .or_else(synth) in lookup + 7 tests |
Low |
config/schema/cloud_providers.rs |
doc comment update only | None |
The fix is well-scoped. Explicit cloud_providers entries still win; synth is only reached when find() returns None. Rejection for anything other than ollama/lmstudio is pinned by test. The is_loopback_unavailable classifier downstream handles the daemon-not-running case cleanly.
A few minor things:
Endpoint double-path edge case (ops.rs, synthesize_local_runtime_entry): the ollama arm does format!("{}/v1", base.trim_end_matches('/')) where base comes from ollama_base_url_from_config. If a user sets config.local_ai.base_url to a value already ending in /v1 (e.g. http://localhost:11434/v1), trim_end_matches('/') won't strip it, and you end up probing …/v1/v1/models. The existing test only covers the bare base-URL case. Worth either documenting that ollama_base_url_from_config guarantees no /v1 suffix, or adding a trim_end_matches("/v1") guard before appending.
Precedence test tests the pattern, not the code path (ops.rs, cloud_providers_entry_takes_precedence_over_local_runtime_synthesis): the test re-implements the find().or_else(synth) chain inline rather than calling through list_configured_models_from_config. It pins the algorithm correctly today, but a refactor that moves the synthesis logic inside the function body wouldn't break this test — it'd still pass. Not blocking, just noting it.
--no-verify in PR notes: the Rust fmt + clippy CI passing makes this harmless for this diff, but it's worth not letting that become a habit in the team — the pre-push hook exists for a reason.
Summary
synthesize_local_runtime_entryinsrc/openhuman/inference/provider/ops.rsthat produces a transientCloudProviderCredsfor the well-known local-runtime slugsollamaandlmstudio.list_configured_models_from_configchainsfind().or_else(synth)so explicitcloud_providersentries still win (e.g. a remote ollama box athttps://ollama.example.com/v1) — the synth is reached only when the find returnsNone.<ollama_base_url>/v1(Ollama's OpenAI-compatible surface — same{"data": [...]}shape the existing parser already handles) and tolm_studio_base_url(config)(already ends in/v1), both withAuthStyle::Noneso the probe runs without anAuthorizationheader on loopback.TAURI-RUST-28Z: 24 events / 7d ontauri-rust,domain=rpc operation=invoke_method method=openhuman.inference_list_models, first-seen 2026-05-20, last-seen 2026-05-27 inopenhuman@0.56.0.Problem
inference_list_modelslooks the requested provider up by id-or-slug inconfig.cloud_providersand bails with the bare string\"no cloud provider with id or slug '<slug>' found\"(src/openhuman/inference/provider/ops.rs:54) when nothing matches. The dropdown surfaces the error in the AI settings panel and Sentry captures it as an unhandled tracing error.The AI settings panel is supposed to register an
ollamacloud_providersrow when the user configures Ollama — see the design note onis_slug_reservedinsrc/openhuman/config/schema/cloud_providers.rs:100. But the call still fires with no matching row when:config.local_ai.base_url.AIPanel.tsx:1868, 2369, 2658, 3148).Five frontend call sites all funnel into the same RPC, so a code-path fix here covers all of them at once.
Solution
The contract is intentionally narrow: only
ollamaandlmstudioroute to synth. Every other slug — built-in cloud providers (openai,anthropic,openrouter), opaque ids (p_random_xyz), typos (tpyo), empty / whitespace — continues to produce the unchanged `"no cloud provider"` error. A typo can't silently route to localhost.Cloud-provider entries still take precedence end-to-end: a user-pointed remote ollama (e.g.
https://ollama.example.com/v1withAuthStyle::Bearer) wins because thefind()runs before theor_else(synth). This is pinned bycloud_providers_entry_takes_precedence_over_local_runtime_synthesis.The comment on `is_slug_reserved` is updated to point at the synth as the documented fallback path.
Submission Checklist
Impact
inference_list_modelsis called with slugollama/lmstudioand no matchingcloud_providersrow exists, the RPC now probes the local runtime instead of erroring. If the local daemon is running, the dropdown populates with installed models; if it isn't, the existing transport-error classifier (`is_loopback_unavailable`) demotes the `ConnectionRefused` so we don't trade one Sentry issue for another. Existing cloud-providers entries take precedence — no behavior change for any user who already has a working setup.Related
Notes for reviewers
Summary by CodeRabbit
Bug Fixes
New Features
Tests