Skip to content

fix: keep custom cloud provider as default#2142

Merged
senamakel merged 2 commits into
tinyhumansai:mainfrom
vaddisrinivas:fix/custom-provider-budget-exhausted
May 20, 2026
Merged

fix: keep custom cloud provider as default#2142
senamakel merged 2 commits into
tinyhumansai:mainfrom
vaddisrinivas:fix/custom-provider-budget-exhausted

Conversation

@vaddisrinivas
Copy link
Copy Markdown
Contributor

@vaddisrinivas vaddisrinivas commented May 18, 2026

Summary

  • Fixes "cloud" / unset workload routing so it resolves through primary_cloud instead of hard-falling back to OpenHuman.
  • Preserves legacy custom inference_url routing for users whose config already contains a custom endpoint but still has primary_cloud pointing at OpenHuman.
  • Updates the AI-provider unification migration so legacy custom inference endpoints become the primary cloud target.
  • Updates load-time legacy routing rewrite so old "cloud" workload fields point at the legacy custom provider instead of freezing onto OpenHuman.

Problem

  • Users who configured a custom model provider could still see hosted OpenHuman budget/resource-exhausted errors.
  • Root cause: the provider factory and load-time routing rewrite treated unset/"cloud" workloads as OpenHuman even when the config had a custom cloud provider seeded from inference_url.
  • Already-migrated configs were especially affected because migration 1→2 preserved the custom provider entry but defaulted primary_cloud to the OpenHuman entry.

Solution

  • Resolve unset/"cloud" workload routes through primary_cloud.
  • If primary_cloud still points at OpenHuman but a legacy external inference_url matches a non-OpenHuman cloud provider, route through that custom provider for backward compatibility.
  • Keep explicit "openhuman" routes explicit so users can still force OpenHuman.
  • Treat OpenHuman and TinyHumans backend hosts as hosted/OpenHuman endpoints during migration and load-time heuristics.

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
  • N/A: Diff coverage ≥ 80% — not run locally; focused Rust unit coverage added and CI should enforce merged diff-cover.
  • Coverage matrix updated — N/A: behaviour-only provider-routing fix, no feature rows added/removed/renamed.
  • All affected feature IDs from the matrix are listed in the PR description under ## Related
  • 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 release manual smoke surface changed.
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Runtime impact: desktop/core provider routing.
  • Compatibility: legacy configs with external inference_url now keep routing on the custom provider instead of consuming OpenHuman hosted budget.
  • No new network dependencies, secrets, or schema-version bump.

Related


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

Keep this section for AI-authored PRs. For human-only PRs, mark each field N/A.

Linear Issue

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

Commit & Branch

  • Branch: fix/custom-provider-budget-exhausted
  • Commit SHA: fbc2178

Validation Run

  • N/A: pnpm --filter openhuman-app format:check — not run; Rust formatter command below covers touched files.
  • N/A: pnpm typecheck — not run; no TypeScript changed.
  • Focused tests:
    • cargo test --manifest-path Cargo.toml openhuman::inference::provider::factory::factory_test --lib
    • cargo test --manifest-path Cargo.toml openhuman::migrations::unify_ai_provider_settings::tests --lib
    • cargo test --manifest-path Cargo.toml migrate_cloud_provider_slugs --lib
  • Rust fmt/check (if changed):
    • cargo fmt --manifest-path Cargo.toml --all --check
    • cargo check --manifest-path Cargo.toml -p openhuman
    • git diff --check
  • N/A: Tauri fmt/check (if changed) — no Tauri app files changed.

Validation Blocked

  • command: node scripts/codex-pr-preflight.mjs --strict-path --lightweight
  • error: local worktree path is /Users/srinivasvaddi/Projects/openhuman-custom-provider-budget-fix instead of /workspace/openhuman; branch prefix fix/custom-provider-budget-exhausted does not match the Codex branch convention.
  • impact: required-file and remote checks passed; failures are local harness/path + branch naming policy only.

Behavior Changes

  • Intended behavior change: unset/"cloud" workloads resolve to the configured primary/custom provider instead of always OpenHuman.
  • User-visible effect: users with a custom provider configured should no longer hit OpenHuman hosted budget exhaustion for workloads that were meant to use the custom provider.

Parity Contract

  • Legacy behavior preserved: configs without custom providers still fall back to OpenHuman; explicit "openhuman" still forces OpenHuman.
  • Guard/fallback/dispatch parity checks: new tests cover direct "cloud", role-based unset routing, legacy inference_url, load-time rewrite, and explicit OpenHuman override.

Duplicate / Superseded PR Handling

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

@vaddisrinivas vaddisrinivas requested a review from a team May 18, 2026 19:01
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

📝 Walkthrough

Walkthrough

This PR updates cloud provider slug migration and runtime resolution to preserve custom provider choices across configuration migrations. When a legacy inference_url points to a custom provider and primary_cloud is set to OpenHuman, the system now correctly routes to the custom provider instead of exhausting the OpenHuman budget. Migration helpers normalize endpoints and detect OpenHuman backends, while runtime resolution respects that primary_cloud selection with legacy inference_url precedence.

Changes

Cloud Provider Resolution and Migration

Layer / File(s) Summary
Endpoint normalization and provider detection helpers for migration
src/openhuman/config/schema/load.rs, src/openhuman/migrations/unify_ai_provider_settings.rs
Helper functions normalize provider endpoints for case-insensitive comparison, detect OpenHuman-like backend endpoints by host pattern and domain suffix (including TinyHumans), and classify provider entries as OpenHuman based on slug, auth style, or endpoint shape. These utilities enable matching legacy inference_url to cloud provider entries and determining when to preserve vs replace routing.
Primary cloud selection during unify_ai_provider_settings migration
src/openhuman/migrations/unify_ai_provider_settings.rs
The migration prefers matching a legacy custom cloud provider entry derived from the configured inference_url (normalized and case-insensitive endpoint comparison) when primary_cloud is unset, rather than always defaulting to OpenHuman. TinyHumans domains are recognized as OpenHuman-like alongside existing patterns. Fallback to OpenHuman entry selection remains when no custom match is found.
Cloud routing sentinel migration in load.rs
src/openhuman/config/schema/load.rs
The load.rs migration computes legacy_custom_slug by matching inference_url against non-OpenHuman provider endpoints, then rewrites the "cloud" sentinel to either "openhuman" or "{slug}:" format. Selection uses primary_slug mapping, legacy_custom_slug, and OpenHuman-vs-non-OpenHuman rules to ensure custom providers are preserved when primary_cloud still targets OpenHuman post-migration.
Provider resolution at runtime in factory.rs
src/openhuman/providers/factory.rs
Updated documentation and runtime logic cause "cloud"/empty provider strings to resolve via primary_cloud instead of always returning OpenHuman. New resolve_primary_cloud_provider_string logic checks primary_cloud type: if OpenHuman, prefers legacy inference_url when present and non-OpenHuman (mapping by normalized endpoint); if non-OpenHuman, returns slug:model format with configured or entry-default model. Helper functions provide endpoint normalization and OpenHuman-backend detection matching migration logic.
Test coverage for migration and provider resolution
src/openhuman/config/schema/load_tests.rs, src/openhuman/migrations/unify_ai_provider_settings_tests.rs, src/openhuman/providers/factory_test.rs
Tests verify that migrate_cloud_provider_slugs rewrites "cloud" routing when a custom provider exists and primary_cloud targets OpenHuman, and that provider resolution respects custom provider default models and precedence. Additional tests validate that unify_ai_provider_settings preserves custom provider selection and that factory resolution correctly applies legacy inference_url precedence.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • tinyhumansai/openhuman#1888: The main PR's Rust-side slug-keyed cloud/provider string migration and src/openhuman/providers/factory.rs "cloud"/empty resolution directly build on the same <slug>:<model> provider-ref parsing/routing work introduced in #1888 (including handling OpenHuman vs non-OpenHuman and auth-style/legacy inference-url precedence), so the changes are code-level related.

Suggested labels

working

Suggested reviewers

  • senamakel

Poem

A rabbit hops through routing paths so clear, 🐰
Where custom clouds now take their rightful place,
No more default exhaustion without care,
Each inference finds its proper base!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: keep custom cloud provider as default' directly addresses the main objective—ensuring custom providers are used instead of defaulting to OpenHuman.
Linked Issues check ✅ Passed All changes directly address #2140 requirements: routing now respects custom providers via primary_cloud, legacy inference_url endpoints are preserved and prioritized, and the migration ensures custom providers become primary targets.
Out of Scope Changes check ✅ Passed All five files contain changes narrowly scoped to cloud provider routing logic, migration, and testing—directly addressing the custom provider fallback issue with no unrelated modifications.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 18, 2026
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: 3

🤖 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 `@src/openhuman/config/schema/load.rs`:
- Around line 613-634: The current logic in legacy_custom_slug maps any
non-OpenHuman inference_url to the first non-OpenHuman provider via the or_else
fallback, which can incorrectly rewrite primary_cloud; modify the closure so it
only returns a slug when a provider's normalized endpoint actually matches the
normalized inference_url (i.e., remove the or_else fallback), keeping the use of
normalize_provider_endpoint, cloud_providers, is_openhuman_provider_entry,
inference_url and looks_like_openhuman_provider_endpoint to locate an exact
match before mapping to entry.slug.

In `@src/openhuman/providers/factory.rs`:
- Around line 278-282: is_openhuman_cloud_entry currently only checks slug and
AuthStyle but must also detect known hosted endpoints to match migration/load
heuristics; update the function (is_openhuman_cloud_entry) to return true if the
entry.slug == PROVIDER_OPENHUMAN or AuthStyle::OpenhumanJwt OR if the
entry.endpoint host matches hosted domains (e.g., equals or contains
"api.openhuman.ai" or ends_with ".tinyhumans.ai"); use the CloudProviderCreds
endpoint field (e.g., entry.endpoint.host or entry.endpoint.as_ref()) to check
host patterns alongside the existing slug/auth checks so hosted backend URLs are
routed through the backend/JWT flow.
🪄 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: 26243dcb-b110-4c0c-965c-edb6b96796ab

📥 Commits

Reviewing files that changed from the base of the PR and between 0b053c5 and f633b9a.

📒 Files selected for processing (6)
  • src/openhuman/config/schema/load.rs
  • src/openhuman/config/schema/load_tests.rs
  • src/openhuman/migrations/unify_ai_provider_settings.rs
  • src/openhuman/migrations/unify_ai_provider_settings_tests.rs
  • src/openhuman/providers/factory.rs
  • src/openhuman/providers/factory_test.rs

Comment on lines +613 to +634
let legacy_custom_slug = config
.inference_url
.as_deref()
.map(str::trim)
.filter(|url| !url.is_empty() && !looks_like_openhuman_provider_endpoint(url))
.and_then(|url| {
let normalized = normalize_provider_endpoint(url);
config
.cloud_providers
.iter()
.find(|entry| {
!is_openhuman_provider_entry(entry)
&& normalize_provider_endpoint(&entry.endpoint) == normalized
})
.or_else(|| {
config
.cloud_providers
.iter()
.find(|entry| !is_openhuman_provider_entry(entry))
})
.map(|entry| entry.slug.clone())
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Only preserve legacy custom routing on an actual endpoint match.

The fallback at Lines 627-632 turns any non-OpenHuman inference_url into the first non-OpenHuman slug, even when that URL does not match a configured provider. If primary_cloud is still OpenHuman, Line 663 will then rewrite "cloud" to that unrelated provider and can persist the wrong route.

Suggested fix
     let legacy_custom_slug = config
         .inference_url
         .as_deref()
         .map(str::trim)
         .filter(|url| !url.is_empty() && !looks_like_openhuman_provider_endpoint(url))
         .and_then(|url| {
             let normalized = normalize_provider_endpoint(url);
             config
                 .cloud_providers
                 .iter()
                 .find(|entry| {
                     !is_openhuman_provider_entry(entry)
                         && normalize_provider_endpoint(&entry.endpoint) == normalized
                 })
-                .or_else(|| {
-                    config
-                        .cloud_providers
-                        .iter()
-                        .find(|entry| !is_openhuman_provider_entry(entry))
-                })
                 .map(|entry| entry.slug.clone())
         });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let legacy_custom_slug = config
.inference_url
.as_deref()
.map(str::trim)
.filter(|url| !url.is_empty() && !looks_like_openhuman_provider_endpoint(url))
.and_then(|url| {
let normalized = normalize_provider_endpoint(url);
config
.cloud_providers
.iter()
.find(|entry| {
!is_openhuman_provider_entry(entry)
&& normalize_provider_endpoint(&entry.endpoint) == normalized
})
.or_else(|| {
config
.cloud_providers
.iter()
.find(|entry| !is_openhuman_provider_entry(entry))
})
.map(|entry| entry.slug.clone())
});
let legacy_custom_slug = config
.inference_url
.as_deref()
.map(str::trim)
.filter(|url| !url.is_empty() && !looks_like_openhuman_provider_endpoint(url))
.and_then(|url| {
let normalized = normalize_provider_endpoint(url);
config
.cloud_providers
.iter()
.find(|entry| {
!is_openhuman_provider_entry(entry)
&& normalize_provider_endpoint(&entry.endpoint) == normalized
})
.map(|entry| entry.slug.clone())
});
🤖 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 `@src/openhuman/config/schema/load.rs` around lines 613 - 634, The current
logic in legacy_custom_slug maps any non-OpenHuman inference_url to the first
non-OpenHuman provider via the or_else fallback, which can incorrectly rewrite
primary_cloud; modify the closure so it only returns a slug when a provider's
normalized endpoint actually matches the normalized inference_url (i.e., remove
the or_else fallback), keeping the use of normalize_provider_endpoint,
cloud_providers, is_openhuman_provider_entry, inference_url and
looks_like_openhuman_provider_endpoint to locate an exact match before mapping
to entry.slug.

Comment on lines +225 to +250
fn legacy_custom_inference_provider_string(config: &Config) -> Option<String> {
let inference_url = config
.inference_url
.as_deref()
.map(str::trim)
.filter(|url| !url.is_empty())?;

if looks_like_openhuman_backend(inference_url) {
return None;
}

let normalized_inference = normalize_endpoint_for_compare(inference_url);
config
.cloud_providers
.iter()
.find(|entry| {
!is_openhuman_cloud_entry(entry)
&& normalize_endpoint_for_compare(&entry.endpoint) == normalized_inference
})
.or_else(|| {
config
.cloud_providers
.iter()
.find(|entry| !is_openhuman_cloud_entry(entry))
})
.map(|entry| cloud_entry_provider_string(entry, config))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't route unmatched legacy URLs to an arbitrary provider.

Lines 244-249 fall back to the first non-OpenHuman entry when inference_url doesn't match any configured endpoint. That makes empty/"cloud" roles send traffic to whichever provider happens to be first, instead of honoring primary_cloud or falling back to OpenHuman.

Suggested fix
     config
         .cloud_providers
         .iter()
         .find(|entry| {
             !is_openhuman_cloud_entry(entry)
                 && normalize_endpoint_for_compare(&entry.endpoint) == normalized_inference
         })
-        .or_else(|| {
-            config
-                .cloud_providers
-                .iter()
-                .find(|entry| !is_openhuman_cloud_entry(entry))
-        })
         .map(|entry| cloud_entry_provider_string(entry, config))
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn legacy_custom_inference_provider_string(config: &Config) -> Option<String> {
let inference_url = config
.inference_url
.as_deref()
.map(str::trim)
.filter(|url| !url.is_empty())?;
if looks_like_openhuman_backend(inference_url) {
return None;
}
let normalized_inference = normalize_endpoint_for_compare(inference_url);
config
.cloud_providers
.iter()
.find(|entry| {
!is_openhuman_cloud_entry(entry)
&& normalize_endpoint_for_compare(&entry.endpoint) == normalized_inference
})
.or_else(|| {
config
.cloud_providers
.iter()
.find(|entry| !is_openhuman_cloud_entry(entry))
})
.map(|entry| cloud_entry_provider_string(entry, config))
fn legacy_custom_inference_provider_string(config: &Config) -> Option<String> {
let inference_url = config
.inference_url
.as_deref()
.map(str::trim)
.filter(|url| !url.is_empty())?;
if looks_like_openhuman_backend(inference_url) {
return None;
}
let normalized_inference = normalize_endpoint_for_compare(inference_url);
config
.cloud_providers
.iter()
.find(|entry| {
!is_openhuman_cloud_entry(entry)
&& normalize_endpoint_for_compare(&entry.endpoint) == normalized_inference
})
.map(|entry| cloud_entry_provider_string(entry, config))
}

Comment on lines +278 to +282
fn is_openhuman_cloud_entry(
entry: &crate::openhuman::config::schema::cloud_providers::CloudProviderCreds,
) -> bool {
entry.slug == PROVIDER_OPENHUMAN || matches!(entry.auth_style, AuthStyle::OpenhumanJwt)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep OpenHuman entry detection aligned with the migration/load heuristics.

is_openhuman_cloud_entry no longer considers the endpoint host, so an entry pointing at api.openhuman.ai or *.tinyhumans.ai but carrying stale slug/auth metadata is treated as custom here. That can push hosted-backend URLs through the OpenAI-compatible path instead of the backend/JWT flow this PR is trying to preserve.

Suggested fix
 fn is_openhuman_cloud_entry(
     entry: &crate::openhuman::config::schema::cloud_providers::CloudProviderCreds,
 ) -> bool {
-    entry.slug == PROVIDER_OPENHUMAN || matches!(entry.auth_style, AuthStyle::OpenhumanJwt)
+    entry.slug == PROVIDER_OPENHUMAN
+        || matches!(entry.auth_style, AuthStyle::OpenhumanJwt)
+        || looks_like_openhuman_backend(&entry.endpoint)
 }
🤖 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 `@src/openhuman/providers/factory.rs` around lines 278 - 282,
is_openhuman_cloud_entry currently only checks slug and AuthStyle but must also
detect known hosted endpoints to match migration/load heuristics; update the
function (is_openhuman_cloud_entry) to return true if the entry.slug ==
PROVIDER_OPENHUMAN or AuthStyle::OpenhumanJwt OR if the entry.endpoint host
matches hosted domains (e.g., equals or contains "api.openhuman.ai" or ends_with
".tinyhumans.ai"); use the CloudProviderCreds endpoint field (e.g.,
entry.endpoint.host or entry.endpoint.as_ref()) to check host patterns alongside
the existing slug/auth checks so hosted backend URLs are routed through the
backend/JWT flow.

@vaddisrinivas vaddisrinivas force-pushed the fix/custom-provider-budget-exhausted branch from f633b9a to 4d7eb35 Compare May 18, 2026 19:07
@vaddisrinivas vaddisrinivas force-pushed the fix/custom-provider-budget-exhausted branch from 4d7eb35 to fbc2178 Compare May 18, 2026 19:10
@vaddisrinivas
Copy link
Copy Markdown
Contributor Author

Addressed CodeRabbit feedback in latest push fbc2178: removed unmatched custom-provider fallback in load/runtime resolution, added hosted OpenHuman/TinyHumans endpoint detection in the factory, and added regressions for unmatched legacy URLs plus hosted endpoint entries. Focused Rust tests and cargo check pass locally.

@vaddisrinivas
Copy link
Copy Markdown
Contributor Author

CI note after latest check: the only red check is test / Rust Core Tests (Windows — secrets ACL), and the job log shows cargo test -p openhuman -- security::secrets --nocapture passed (53 passed; 0 failed). The failed step is Post Cache Rust build artifacts, so this looks like a cache/post-job artifact failure rather than a code/test failure. I attempted to rerun failed jobs but GitHub returned Must have admin rights to Repository.

senamakel
senamakel previously approved these changes May 19, 2026
# Conflicts:
#	src/openhuman/config/schema/load_tests.rs
#	src/openhuman/inference/provider/factory.rs
@senamakel senamakel merged commit c07e822 into tinyhumansai:main May 20, 2026
26 checks passed
mtkik pushed a commit to mtkik/openhuman-meet that referenced this pull request May 21, 2026
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
CodeGhost21 pushed a commit to CodeGhost21/openhuman that referenced this pull request May 22, 2026
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: custom cloud provider still falls back to exhausted OpenHuman budget

2 participants