Skip to content

fix(ollama): URL builder + local-AI noise suppression bundle#1553

Merged
senamakel merged 6 commits into
tinyhumansai:mainfrom
oxoxDev:fix/ollama-bundle
May 13, 2026
Merged

fix(ollama): URL builder + local-AI noise suppression bundle#1553
senamakel merged 6 commits into
tinyhumansai:mainfrom
oxoxDev:fix/ollama-bundle

Conversation

@oxoxDev
Copy link
Copy Markdown
Contributor

@oxoxDev oxoxDev commented May 12, 2026

Summary

  • Hardened native Ollama embedding configuration so base URLs are normalized and pre-suffixed /api, /v1, and /chat/completions endpoints fail early with clear errors.
  • Reject local-* virtual model IDs for native Ollama embeddings, while preserving local-v1 for OpenAI-compatible chat routing.
  • Added no-auth support for local Ollama chat providers to avoid bogus Ollama API-key errors.
  • Demoted expected local-AI-disabled and missing-API-key configuration errors away from Sentry error reporting.
  • Gated local-AI download/test UI actions when the runtime is disabled, with Vitest coverage.

Problem

  • Sentry reported brittle Ollama URL construction and local-AI disabled/configuration states being treated like application bugs.
  • Native Ollama embeddings require real Ollama model IDs and root server URLs, but the previous path allowed virtual chat aliases and endpoint-shaped base URLs to leak into /api/embed.
  • The UI could still trigger local-AI RPCs when config.local_ai.runtime_enabled was off.

Solution

  • Added OllamaEmbedding::try_new with URL/model validation, kept legacy new for internal callers, and validated the configured Ollama base URL in the embedding factory.
  • Added AuthStyle::None for local OpenAI-compatible Ollama routes and updated local provider construction to skip auth when no API key is configured.
  • Added report_error_or_expected to classify local ai is disabled as info-level and API-key-missing config errors as warning-level instead of sending them through the Sentry error path.
  • Disabled local download/bootstrap/test actions across local model settings and AI settings while runtime is disabled.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — covered by CI coverage gates; focused Rust/Vitest regression tests were run locally.
  • Coverage matrix updated — N/A: behaviour-only hardening with no feature row changes.
  • All affected feature IDs from the matrix are listed in the PR description under ## Related — N/A: Sentry/meta-tracker links listed below.
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: no smoke checklist surface added.
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Desktop runtime: local Ollama misconfiguration now fails earlier with clearer messages.
  • Observability: expected disabled/config errors are logged at info/warn and should no longer create Sentry error events.
  • UI: local-AI model download/test actions are disabled when the runtime is disabled; status views remain readable.
  • Compatibility: OpenAI-compatible local-v1 chat routing remains intact.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/ollama-bundle
  • Commit SHA: 4dde3994acdff0b8cadea2a7cf95aa44c5449a51

Validation Run

  • pnpm --filter openhuman-app format:check (via pre-push)
  • pnpm typecheck
  • Focused tests: cargo test --lib openhuman::embeddings; cargo test --lib openhuman::local_ai; cargo test --lib openhuman::providers::compatible; cargo test --lib core::observability; pnpm --filter openhuman-app test:unit src/components/settings/panels/__tests__/LocalModelPanel.test.tsx src/components/settings/panels/__tests__/AIPanel.test.tsx src/components/settings/panels/local-model/ModelDownloadSection.test.tsx
  • Rust fmt/check: cargo fmt --check; cargo check --manifest-path Cargo.toml
  • Tauri fmt/check: cargo fmt --manifest-path app/src-tauri/Cargo.toml --all --check; cargo check --manifest-path app/src-tauri/Cargo.toml (via pre-push)

Validation Blocked

  • command: cargo clippy --workspace -- -D warnings
  • error: Existing workspace warnings outside this change fail the global -D warnings gate; filtered clippy check found no hits in touched Rust files after fixes.
  • impact: This PR's touched-file clippy issues were resolved; unrelated warning backlog remains.

Behavior Changes

  • Intended behavior change: Invalid Ollama embedding base/model config fails early; expected disabled/config errors are not reported as Sentry errors; disabled runtime blocks local-AI action buttons.
  • User-visible effect: Fewer noisy local-AI failures and clearer setup/configuration behavior.

Parity Contract

  • Legacy behavior preserved: local-v1 remains valid for OpenAI-compatible chat routes; status/progress views still load while runtime is disabled.
  • Guard/fallback/dispatch parity checks: Local no-auth provider path is scoped to local Ollama providers; non-local providers still require credentials.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: N/A
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • New Features

    • Settings now disable local-AI actions and navigation when the local runtime is turned off (buttons show disabled styling).
  • Bug Fixes

    • Faster initial status/loading on settings pages with continued periodic polling and robust timer cleanup.
    • Error reporting no longer treats certain expected conditions as hard failures.
    • Providers can operate without an API key when configured that way; embedding config validation improved.
  • Tests

    • Added coverage for disabled-runtime behavior across settings and embedding validation.

Review Change Stack

@oxoxDev oxoxDev requested a review from a team May 12, 2026 12:46
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4b5f2499-b260-47b3-9180-8ad6de6ab2a3

📥 Commits

Reviewing files that changed from the base of the PR and between 2c53e60 and 4dde399.

📒 Files selected for processing (11)
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/components/settings/panels/LocalModelDebugPanel.tsx
  • app/src/components/settings/panels/__tests__/LocalModelPanel.test.tsx
  • app/src/components/settings/panels/local-model/ModelDownloadSection.test.tsx
  • app/src/components/settings/panels/local-model/ModelStatusSection.test.tsx
  • src/openhuman/agent/triage/routing.rs
  • src/openhuman/embeddings/ollama.rs
  • src/openhuman/embeddings/ollama_tests.rs
  • src/openhuman/providers/compatible.rs
  • src/openhuman/providers/compatible_tests.rs
  • src/openhuman/routing/factory.rs
🚧 Files skipped from review as they are similar to previous changes (8)
  • app/src/components/settings/panels/local-model/ModelDownloadSection.test.tsx
  • src/openhuman/providers/compatible_tests.rs
  • src/openhuman/agent/triage/routing.rs
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/components/settings/panels/tests/LocalModelPanel.test.tsx
  • src/openhuman/embeddings/ollama_tests.rs
  • src/openhuman/providers/compatible.rs
  • src/openhuman/embeddings/ollama.rs

📝 Walkthrough

Walkthrough

This PR gates frontend local-AI actions when the runtime is disabled, classifies expected errors to avoid Sentry noise, enables optional/no-auth provider configurations, and validates Ollama embedding configuration at construction time.

Changes

Local AI Runtime Gating and Error Classification

Layer / File(s) Summary
Expected error classification and reporting
src/core/observability.rs
Adds ExpectedErrorKind and expected_error_kind() classifier; introduces report_error_or_expected() which logs recognized expected conditions at info/warn instead of reporting Sentry events. Tests added for classification and safe invocation.
Error reporting integration
src/core/jsonrpc.rs, src/openhuman/agent/harness/tool_loop.rs, src/openhuman/providers/reliable.rs
Call sites updated to call report_error_or_expected() where appropriate so expected conditions are logged rather than sent to Sentry, preserving existing error propagation.
Optional API key authentication
src/openhuman/providers/compatible.rs, src/openhuman/providers/compatible_tests.rs
OpenAiCompatibleProvider supports AuthStyle::None; credential_for_request() returns Option<&str> and apply_auth_header() conditionally applies headers. Streaming and chat paths accept optional credentials. Tests added for AuthStyle::None and whitespace-only key handling.
Local provider authentication style
src/openhuman/agent/triage/routing.rs, src/openhuman/routing/factory.rs, src/openhuman/tree_summarizer/ops.rs
Local provider construction now derives AuthStyle from a trimmed API key presence (Bearer when present, None when absent) and passes the normalized key/auth style into OpenAiCompatibleProvider.
Ollama embeddings validation
src/openhuman/embeddings/ollama.rs, src/openhuman/embeddings/factory.rs, src/openhuman/embeddings/ollama_tests.rs
Adds OllamaEmbedding::try_new(...) with normalize_base_url() and normalize_model() to validate/normalize base URL and model; embed_url() becomes fallible and errors propagate. Factory uses runtime-resolved base URL. Tests cover URL/model edge cases and rejections.
UI runtime gating
app/src/components/settings/panels/AIPanel.tsx, app/src/components/settings/panels/LocalModelPanel.tsx, app/src/components/settings/panels/LocalModelDebugPanel.tsx, app/src/components/settings/panels/local-model/ModelStatusSection.tsx, app/src/components/settings/panels/local-model/ModelDownloadSection.tsx
Panels derive runtimeEnabled from status/usage flags and disable/guard download, test, and bootstrap actions when runtime is disabled. Polling effects defer initial load via setTimeout(0) and then poll via intervals with proper cleanup.
UI gating tests
app/src/components/settings/panels/__tests__/AIPanel.test.tsx, app/src/components/settings/panels/__tests__/LocalModelPanel.test.tsx, app/src/components/settings/panels/local-model/ModelDownloadSection.test.tsx, app/src/components/settings/panels/local-model/ModelStatusSection.test.tsx
Adds/updates tests asserting buttons are disabled and command invocations are not triggered when runtime is disabled; default test props updated as needed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • senamakel

Poem

🐰 I checked the runtime, quiet and neat,
Buttons now sleep when the service can't speak.
Errors that were noisy are calm as a stream,
Ollama URLs tidy, no panic to scream.
Hop, patch, and ship — snug logs in the dream.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(ollama): URL builder + local-AI noise suppression bundle' clearly summarizes the main changes, focusing on Ollama URL hardening and error classification improvements.
Linked Issues check ✅ Passed All code changes align with issue #1472 objectives: Ollama URL validation and normalization, local-AI configuration error classification (report_error_or_expected), UI gating for disabled runtime, and test coverage.
Out of Scope Changes check ✅ Passed All changes are in-scope to issue #1472: Ollama embedding hardening, auth-style refactoring for no-auth support, expected-error classification, UI runtime gating, and corresponding test additions.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (3)
app/src/components/settings/panels/local-model/ModelDownloadSection.test.tsx (1)

52-61: ⚡ Quick win

Expand runtime-disabled assertions beyond the summary action.

This PR gated multiple controls; adding at least one more assertion (e.g., asset download or prompt test button) would reduce regression risk for the broader gate change.

As per coding guidelines, “Achieve ≥ 80% coverage on changed lines; PRs must pass diff-cover check via Vitest and cargo-llvm-cov.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/local-model/ModelDownloadSection.test.tsx`
around lines 52 - 61, The test in ModelDownloadSection currently only asserts
the summary test button is disabled and its handler (props.onRunSummaryTest)
isn't called; extend it to cover another gated control (e.g., the asset download
or prompt test) by retrieving that button (for example by role/name like
'Download Model Assets' or 'Run Prompt Test'), asserting it is disabled, firing
a click, and verifying the corresponding prop handler (e.g.,
props.onDownloadAssets or props.onRunPromptTest) was not called; update the test
that uses makeProps and ModelDownloadSection to include this additional
assertion so the runtime-disabled gate is validated for multiple actions.
app/src/components/settings/panels/__tests__/LocalModelPanel.test.tsx (1)

163-172: ⚡ Quick win

Add a runtime-disabled assertion for the “Advanced settings” action too.

This file now covers download gating, but the same runtime gate was added to navigation; adding that assertion will fully cover the new UI guard behavior.

As per coding guidelines, “Achieve ≥ 80% coverage on changed lines; PRs must pass diff-cover check via Vitest and cargo-llvm-cov.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/__tests__/LocalModelPanel.test.tsx` around
lines 163 - 172, Extend the existing test in LocalModelPanel.test.tsx to also
assert that the "Advanced settings" action is gated when the runtime is
disabled: locate the Advanced settings control (e.g., using
screen.findByRole('button', { name: 'Advanced settings' }) or the same query
style used for Download Models), add expect(advancedButton).toBeDisabled(),
fireEvent.click(advancedButton), and assert that whatever mock/handler would
navigate/open advanced settings is not called (follow the same pattern used for
openhumanLocalAiDownload and openhumanLocalAiDownloadAllAssets); keep the
assertion inside the same it(...) block for 'does not invoke model downloads
while runtime is disabled'.
app/src/components/settings/panels/local-model/ModelStatusSection.test.tsx (1)

27-27: ⚡ Quick win

Please add a runtime-disabled button-state test in this suite.

Setting runtimeEnabled: true fixes setup compatibility, but it doesn’t verify the new gating behavior (Bootstrap / Resume and Force Re-bootstrap disabled when runtime is off).

As per coding guidelines, “Achieve ≥ 80% coverage on changed lines; PRs must pass diff-cover check via Vitest and cargo-llvm-cov.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/local-model/ModelStatusSection.test.tsx`
at line 27, Add a new test in ModelStatusSection.test.tsx that mounts the
ModelStatusSection component with runtimeEnabled: false (instead of true) and
asserts that the "Bootstrap / Resume" and "Force Re-bootstrap" controls are
rendered in a disabled state; locate the setup helper used in existing tests
(e.g., the render or setup function that supplies props like runtimeEnabled) and
reuse it with runtimeEnabled: false, then query the buttons by their accessible
text or test-id and expect them to have the disabled attribute or
aria-disabled=true to validate the gating behavior when runtime is off.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/settings/panels/AIPanel.tsx`:
- Around line 132-136: The retry-download onClick handler may throw from
openhumanLocalAiDownload(true) and needs a try/catch like other handlers; wrap
the await openhumanLocalAiDownload(true) call inside try { ... } catch (err) {
setError(err); } and ensure loadLocalAiStatus() still runs (either after
successful download or in a finally block) so the component state is refreshed;
reference the localAiRuntimeEnabled guard, openhumanLocalAiDownload,
loadLocalAiStatus, and setError when making this change.

In `@app/src/components/settings/panels/LocalModelDebugPanel.tsx`:
- Line 108: The current runtimeEnabled check treats status === null as disabled
causing a permanent no-op; change the logic in the LocalModelDebugPanel where
runtimeEnabled is computed (the runtimeEnabled constant that uses status and
status.state) so that only an explicit state of "disabled" blocks actions—e.g.
compute runtimeEnabled by negating status?.state === 'disabled' or by treating
null/undefined status as enabled rather than disabled; update any dependent
checks that rely on runtimeEnabled accordingly.

In `@src/openhuman/agent/triage/routing.rs`:
- Around line 107-116: Normalize the local API key before deriving auth_style:
replace the current is_some() check on local_cfg.api_key with a
trimmed/empty-aware test (e.g., obtain an Option<&str> that trims whitespace and
becomes None for empty strings) and use that normalized option to set auth_style
(AuthStyle::Bearer vs AuthStyle::None) and to pass into
OpenAiCompatibleProvider::new (instead of raw local_cfg.api_key.as_deref()).
Ensure you reference local_cfg.api_key, auth_style, AuthStyle::Bearer, and
OpenAiCompatibleProvider::new when making the change.

In `@src/openhuman/embeddings/ollama.rs`:
- Around line 82-121: The normalize_base_url function currently accepts
credentialed URLs (e.g., http://user:pass@host) which can leak secrets when
stored or logged; update normalize_base_url to detect and reject any Url that
has non-empty username() or password() (use reqwest::Url::username()/password()
or the Url methods available), returning an anyhow::bail! with a clear message
instructing to provide the server root without credentials; keep existing
validations (scheme, query/fragment, path segments) and still trim trailing
slashes before returning.

In `@src/openhuman/providers/compatible.rs`:
- Around line 327-338: The credential_for_request method currently treats
Some("") or whitespace-only credentials as present; update it so when auth is
required (AuthStyle != AuthStyle::None) you treat empty or whitespace-only
strings as missing: check self.credential.as_deref().map(str::trim).filter(|s|
!s.is_empty()) and return Ok(Some(trimmed)) when non-empty, otherwise return the
same anyhow::anyhow!("{} API key not set...") error using self.name; keep the
early return for AuthStyle::None but ensure blank/whitespace credentials do not
produce an Ok(Some("")) value.

In `@src/openhuman/routing/factory.rs`:
- Around line 77-87: The auth-mode selection incorrectly treats Some("") as a
present key and forces AuthStyle::Bearer; update the check around
local_auth_style to consider only non-empty strings (e.g. use
local_ai_config.api_key.as_deref().filter(|s| !s.is_empty()).is_some() or
equivalent) so empty API keys map to AuthStyle::None; adjust the creation of the
OpenAiCompatibleProvider (OpenAiCompatibleProvider::new with provider_label,
&local_base, local_ai_config.api_key.as_deref(), local_auth_style) to use the
corrected local_auth_style logic.

---

Nitpick comments:
In `@app/src/components/settings/panels/__tests__/LocalModelPanel.test.tsx`:
- Around line 163-172: Extend the existing test in LocalModelPanel.test.tsx to
also assert that the "Advanced settings" action is gated when the runtime is
disabled: locate the Advanced settings control (e.g., using
screen.findByRole('button', { name: 'Advanced settings' }) or the same query
style used for Download Models), add expect(advancedButton).toBeDisabled(),
fireEvent.click(advancedButton), and assert that whatever mock/handler would
navigate/open advanced settings is not called (follow the same pattern used for
openhumanLocalAiDownload and openhumanLocalAiDownloadAllAssets); keep the
assertion inside the same it(...) block for 'does not invoke model downloads
while runtime is disabled'.

In
`@app/src/components/settings/panels/local-model/ModelDownloadSection.test.tsx`:
- Around line 52-61: The test in ModelDownloadSection currently only asserts the
summary test button is disabled and its handler (props.onRunSummaryTest) isn't
called; extend it to cover another gated control (e.g., the asset download or
prompt test) by retrieving that button (for example by role/name like 'Download
Model Assets' or 'Run Prompt Test'), asserting it is disabled, firing a click,
and verifying the corresponding prop handler (e.g., props.onDownloadAssets or
props.onRunPromptTest) was not called; update the test that uses makeProps and
ModelDownloadSection to include this additional assertion so the
runtime-disabled gate is validated for multiple actions.

In `@app/src/components/settings/panels/local-model/ModelStatusSection.test.tsx`:
- Line 27: Add a new test in ModelStatusSection.test.tsx that mounts the
ModelStatusSection component with runtimeEnabled: false (instead of true) and
asserts that the "Bootstrap / Resume" and "Force Re-bootstrap" controls are
rendered in a disabled state; locate the setup helper used in existing tests
(e.g., the render or setup function that supplies props like runtimeEnabled) and
reuse it with runtimeEnabled: false, then query the buttons by their accessible
text or test-id and expect them to have the disabled attribute or
aria-disabled=true to validate the gating behavior when runtime is off.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38b56b96-6223-4294-876e-b0067d1d0dab

📥 Commits

Reviewing files that changed from the base of the PR and between 15c7442 and 2c53e60.

📒 Files selected for processing (21)
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/components/settings/panels/LocalModelDebugPanel.tsx
  • app/src/components/settings/panels/LocalModelPanel.tsx
  • app/src/components/settings/panels/__tests__/AIPanel.test.tsx
  • app/src/components/settings/panels/__tests__/LocalModelPanel.test.tsx
  • app/src/components/settings/panels/local-model/ModelDownloadSection.test.tsx
  • app/src/components/settings/panels/local-model/ModelDownloadSection.tsx
  • app/src/components/settings/panels/local-model/ModelStatusSection.test.tsx
  • app/src/components/settings/panels/local-model/ModelStatusSection.tsx
  • src/core/jsonrpc.rs
  • src/core/observability.rs
  • src/openhuman/agent/harness/tool_loop.rs
  • src/openhuman/agent/triage/routing.rs
  • src/openhuman/embeddings/factory.rs
  • src/openhuman/embeddings/ollama.rs
  • src/openhuman/embeddings/ollama_tests.rs
  • src/openhuman/providers/compatible.rs
  • src/openhuman/providers/compatible_tests.rs
  • src/openhuman/providers/reliable.rs
  • src/openhuman/routing/factory.rs
  • src/openhuman/tree_summarizer/ops.rs

Comment thread app/src/components/settings/panels/AIPanel.tsx
Comment thread app/src/components/settings/panels/LocalModelDebugPanel.tsx Outdated
Comment thread src/openhuman/agent/triage/routing.rs Outdated
Comment thread src/openhuman/embeddings/ollama.rs
Comment thread src/openhuman/providers/compatible.rs
Comment thread src/openhuman/routing/factory.rs Outdated
@senamakel senamakel merged commit 54a9396 into tinyhumansai:main May 13, 2026
17 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Track and fix active Sentry issues

2 participants