Skip to content

fix(inference): synth local-runtime entry for list_models when no cloud_providers row (Sentry TAURI-RUST-28Z)#2785

Open
CodeGhost21 wants to merge 1 commit into
tinyhumansai:mainfrom
CodeGhost21:fix/sentry-28z-local-runtime-list-models
Open

fix(inference): synth local-runtime entry for list_models when no cloud_providers row (Sentry TAURI-RUST-28Z)#2785
CodeGhost21 wants to merge 1 commit into
tinyhumansai:mainfrom
CodeGhost21:fix/sentry-28z-local-runtime-list-models

Conversation

@CodeGhost21
Copy link
Copy Markdown
Contributor

@CodeGhost21 CodeGhost21 commented May 27, 2026

Summary

  • 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.
  • list_configured_models_from_config chains find().or_else(synth) so explicit cloud_providers entries still win (e.g. a remote ollama box at https://ollama.example.com/v1) — the synth is reached only when the find returns None.
  • The synth routes to <ollama_base_url>/v1 (Ollama's OpenAI-compatible surface — same {"data": [...]} shape the existing parser already 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.
  • Targets self-hosted Sentry TAURI-RUST-28Z: 24 events / 7d on tauri-rust, domain=rpc operation=invoke_method method=openhuman.inference_list_models, first-seen 2026-05-20, last-seen 2026-05-27 in openhuman@0.56.0.

Problem

inference_list_models looks the requested provider up by id-or-slug in config.cloud_providers and 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 ollama cloud_providers row when the user configures Ollama — see the design note on is_slug_reserved in src/openhuman/config/schema/cloud_providers.rs:100. But the call still fires with no matching row when:

  1. The user upgraded from a build that persisted only config.local_ai.base_url.
  2. `flushCloudProviders` was async-deferred and the probe in `AIPanel.tsx` fires before the entry is durable.
  3. The user removed a custom ollama row but stale UI state still triggers the model-list refetch (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

let entry = config
    .cloud_providers
    .iter()
    .find(|e| e.id == provider_id || e.slug == provider_id)
    .cloned()
    .or_else(|| synthesize_local_runtime_entry(&provider_id, config))
    .ok_or_else(|| format!(\"no cloud provider with id or slug '{}' found\", provider_id))?;
fn synthesize_local_runtime_entry(
    slug: &str,
    config: &crate::openhuman::config::Config,
) -> Option<CloudProviderCreds> {
    let endpoint = match slug {
        \"ollama\" => {
            let base = crate::openhuman::inference::local::ollama_base_url_from_config(config);
            format!(\"{}/v1\", base.trim_end_matches('/'))
        }
        \"lmstudio\" => {
            crate::openhuman::inference::local::lm_studio::lm_studio_base_url(config)
        }
        _ => return None,
    };
    Some(CloudProviderCreds { /* AuthStyle::None, synthetic id, … */ })
}

The contract is intentionally narrow: only ollama and lmstudio route 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/v1 with AuthStyle::Bearer) wins because the find() runs before the or_else(synth). This is pinned by cloud_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

If a section does not apply to this change, mark the item as `N/A` with a one-line reason. Do not delete items.

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy — 7 new tests in `src/openhuman/inference/provider/ops.rs`:
    • `synthesize_local_runtime_entry_ollama_returns_v1_endpoint_with_no_auth` (happy path, ollama)
    • `synthesize_local_runtime_entry_lmstudio_returns_v1_endpoint_with_no_auth` (happy path, lmstudio)
    • `synthesize_local_runtime_entry_returns_none_for_unknown_slug` (rejection: 5 negative cases)
    • `synthesize_local_runtime_entry_ollama_respects_config_base_url` (config.local_ai.base_url priority)
    • `cloud_providers_entry_takes_precedence_over_local_runtime_synthesis` (precedence contract)
    • `missing_cloud_providers_entry_falls_back_to_local_runtime_synth` (regression contract — exact 28Z scenario)
    • `missing_cloud_providers_entry_for_unknown_slug_still_errors` (rejection contract for the chained lookup)
  • Diff coverage ≥ 80% — every new line in `synthesize_local_runtime_entry` and the chained `or_else` is exercised by the new tests.
  • N/A: Coverage matrix updated — internal inference-provider routing change; not a tracked feature row in `docs/TEST-COVERAGE-MATRIX.md`.
  • N/A: All affected feature IDs from the matrix are listed in the PR description under `## Related` — no matrix feature IDs affected.
  • No new external network dependencies introduced — synth reuses existing local-runtime base-URL helpers; the probe HTTP path through `build_runtime_proxy_client_with_timeouts` is unchanged.
  • N/A: Manual smoke checklist updated — internal RPC fallback; no release-cut user-visible change beyond "the dropdown no longer errors when ollama isn't in cloud_providers".
  • N/A: Linked issue closed via `Closes #NNN` — Sentry-only fix; no GitHub issue. The `Sentry-Issue` trailer below carries the back-reference.

Impact

  • Runtime: Desktop (Tauri shell). When inference_list_models is called with slug ollama / lmstudio and no matching cloud_providers row 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.
  • Performance: Net positive — no new code paths fire for non-affected users (find() returns Some, synth is never called); affected users now see one local HTTP request instead of an error round-trip + retry.
  • Security: None. The synth produces `AuthStyle::None` and the endpoint is derived from `config.local_ai.base_url` / `ollama_base_url_from_config` — paths the user already controls.
  • Migration / compatibility: None. Additive code path; the synth is invoked only as a fallback after the existing find fails.
  • Observability: Sentry TAURI-RUST-28Z (~24 events / 14d) drops to zero for users on drifted configs. The Sentry-visible `tracing::error!` at `inference/ops.rs:248` (`list_models:error`) only fires now if (a) the user has an explicit non-local entry that returns 4xx/5xx, or (b) the local daemon isn't running — both legitimate, actionable signals.

Related

  • Sentry-Issue: TAURI-RUST-28Z
  • Reads-from: `config.local_ai.base_url` (Ollama), `lm_studio_base_url` (LM Studio). Both already drive the chat factory path (`provider/factory.rs:667, 694`); this PR brings model-listing in line with the same routing.

Notes for reviewers

  • Pre-push hook (`pnpm format`) was skipped via `--no-verify` because the fresh worktree has no `node_modules`; prettier is unable to run. The change is Rust-only (.rs touched, no .ts/.tsx/.md) and `cargo fmt --manifest-path Cargo.toml --check` is clean.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed "no cloud provider found" error when using local runtime providers (Ollama, LMStudio) without explicit configuration entries.
  • New Features

    • Added automatic fallback to synthesize local runtime provider entries for Ollama and LMStudio, making configuration easier.
  • Tests

    • Extended unit tests to validate fallback behavior and precedence rules for local runtime providers.

Review Change Stack

…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
@CodeGhost21 CodeGhost21 requested a review from a team May 27, 2026 20:15
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

📝 Walkthrough

Walkthrough

The PR implements local-runtime provider synthesis: when ollama or lmstudio slugs lack a persisted cloud_providers entry, the resolution logic now synthesizes a transient provider with OpenAI-compatible endpoints and unauthenticated access, preventing model listing failures. Tests and documentation are updated accordingly.

Changes

Local-runtime provider synthesis fallback

Layer / File(s) Summary
Synthesis helper and provider resolution
src/openhuman/inference/provider/ops.rs
synthesize_local_runtime_entry generates CloudProviderCreds for ollama and lmstudio with /v1 endpoints and AuthStyle::None; list_configured_models_from_config chains explicit cloud_providers matching with this synthesis fallback.
Tests for synthesis and resolution
src/openhuman/inference/provider/ops.rs
Unit tests validate synthetic entry generation, endpoint/auth expectations, local-URL honoring for Ollama, None for unknown slugs, and precedence rules where explicit entries override synthesis.
Reserved-slug fallback documentation
src/openhuman/config/schema/cloud_providers.rs
Documentation clarifies that ollama and lmstudio reserved slugs fall back to transient local-runtime synthesis when no cloud_providers row exists.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • tinyhumansai/openhuman#1888: Backend synthesis of CloudProviderCreds for ollama/lmstudio (AuthStyle None, OpenAI-compatible /v1 endpoints) when no persisted cloud_providers entry exists, complementing upstream slug-keyed provider and model-catalog refactoring.

Suggested labels

bug, rust-core

Suggested reviewers

  • graycyrus
  • senamakel

Poem

🐰 Local runtimes need no rows,
Synthesis fallback grows and grows,
ollama, lmstudio find their way,
Transient providers save the day,
Models catalog without a frown! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding fallback synthesis for local-runtime provider entries when no cloud_providers row exists, which directly aligns with the primary purpose of this PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. bug labels May 27, 2026
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants