Harden Rust custom provider routing and mock coverage#1958
Conversation
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR adds rule-based request matching to the mock LLM handler, refactors provider selection to use dynamic role routing based on model hints, updates the session cache fingerprint to track provider bindings instead of fixed reasoning providers, restructures provider factory and router tests into separate files with improved coverage, and extends e2e tests to validate custom reasoning provider routing with authentication. ChangesProvider Routing and Mock LLM Rule System
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/openhuman/providers/factory.rs (1)
261-285:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAvoid credential-store lookup for no-auth provider styles.
Line 261 fetches credentials before auth-style branching, so
AuthStyle::None(andAuthStyle::OpenhumanJwt) can fail provider creation if credential storage is unreadable, even though these paths do not require API keys.💡 Suggested fix
- let key = lookup_key_for_slug(slug, config)?; - match entry.auth_style { AuthStyle::Anthropic => { + let key = lookup_key_for_slug(slug, config)?; let p = make_openai_compatible_provider(&entry.endpoint, &key, CompatAuthStyle::Anthropic)?; Ok((p, effective_model)) } AuthStyle::OpenhumanJwt => { // Route to the OpenHuman backend — ignore the entry's endpoint // and model; use the backend provider with the configured default. log::debug!( "[providers][chat-factory] slug='{}' has auth_style=OpenhumanJwt → routing to openhuman backend", slug ); make_openhuman_backend(config) } AuthStyle::None => { let p = make_openai_compatible_provider(&entry.endpoint, "", CompatAuthStyle::None)?; Ok((p, effective_model)) } AuthStyle::Bearer => { + let key = lookup_key_for_slug(slug, config)?; let p = make_openai_compatible_provider(&entry.endpoint, &key, CompatAuthStyle::Bearer)?; Ok((p, effective_model)) } }🤖 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 261 - 285, The code currently calls lookup_key_for_slug(slug, config) unconditionally which can fail for providers that don't need credentials; change it to only call lookup_key_for_slug inside the branches that require a key (AuthStyle::Anthropic and AuthStyle::Bearer), and keep AuthStyle::None and AuthStyle::OpenhumanJwt paths using make_openai_compatible_provider or make_openhuman_backend without performing a credential lookup; update the match arm implementations around make_openai_compatible_provider, AuthStyle::Anthropic, AuthStyle::Bearer, AuthStyle::None and the AuthStyle::OpenhumanJwt routing to ensure lookup_key_for_slug is invoked lazily only where needed.
🧹 Nitpick comments (2)
src/openhuman/channels/providers/web.rs (1)
1335-1342: ⚡ Quick winShare this provider-role mapping with
AgentBuilder.This table is duplicated here and in
src/openhuman/agent/harness/session/builder.rs. If one side gains a new hint/alias and the other doesn't, cache fingerprinting and actual provider construction will drift, which can cause stale session reuse or unnecessary rebuilds. Please move the mapping into one shared helper and reuse it from both call sites.🤖 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/channels/providers/web.rs` around lines 1335 - 1342, The provider_role_for_model_override function duplicates a mapping used by AgentBuilder; refactor by extracting this mapping into a single shared helper (e.g., pub fn provider_role_for_model_override or a const MAP in a common module) and have both provider_role_for_model_override and the AgentBuilder call site use that shared helper instead of duplicating the match; update references in provider_role_for_model_override and in the AgentBuilder code in builder.rs to call the new shared helper so both use the exact same hint/alias-to-role mapping.tests/json_rpc_e2e.rs (1)
190-285: 💤 Low valueConsider extracting request capture logic to reduce duplication.
Both
chat_completionsandgeneric_chat_completionshave nearly identical request-capture blocks (lines 198-216 and 255-273). A helper function could reduce this duplication:♻️ Suggested refactor
+fn capture_chat_request(uri: &Uri, headers: &HeaderMap, body: &Value) { + let auth_header = headers + .get(AUTHORIZATION) + .and_then(|value| value.to_str().ok()) + .map(str::to_string); + let x_api_key = headers + .get("x-api-key") + .and_then(|value| value.to_str().ok()) + .map(str::to_string); + with_chat_completion_requests(|requests| { + requests.push(json!({ + "path": uri.path(), + "model": body.get("model").and_then(Value::as_str), + "stream": body.get("stream").and_then(Value::as_bool), + "thread_id": body.get("thread_id").and_then(Value::as_str), + "authorization": auth_header, + "x_api_key": x_api_key, + "body": body.clone(), + })) + }); +} async fn chat_completions( uri: Uri, headers: HeaderMap, Json(body): Json<Value>, ) -> Json<Value> { if let Some(model) = body.get("model").and_then(Value::as_str) { with_chat_completion_models(|models| models.push(model.to_string())); } - let auth_header = headers - .get(AUTHORIZATION) - .and_then(|value| value.to_str().ok()) - .map(str::to_string); - // ... (remove duplicate capture block) + capture_chat_request(&uri, &headers, &body);🤖 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 `@tests/json_rpc_e2e.rs` around lines 190 - 285, Both chat_completions and generic_chat_completions duplicate the request-capture block; extract that logic into a helper (e.g., capture_chat_completion_request or push_chat_request) that accepts the Uri, HeaderMap and &Value body, builds the auth_header/x_api_key the same way, constructs the JSON object and calls with_chat_completion_requests to push it; replace the duplicated blocks in chat_completions and generic_chat_completions with a single call to that helper to remove duplication and keep behavior identical.
🤖 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 `@scripts/mock-api/routes/llm.mjs`:
- Around line 518-533: The current branch treats any requestRule with status >=
400 as a streaming success by calling writeSseHead() when parsedBody?.stream ===
true; change the logic in the block that checks requestRule?.error ||
(requestRule?.status && requestRule.status >= 400) so that HTTP error status
codes always invoke sendRuleError(res, requestRule) (and return true) instead of
starting an SSE response — only call writeSseHead() / writeSseEvent() when
requestRule indicates a streaming error body (e.g., requestRule.error &&
parsedBody?.stream === true) or when status is < 400; update the conditional
around writeSseHead/writeSseEvent to explicitly require requestRule.status to be
absent or < 400 before starting the SSE stream.
---
Outside diff comments:
In `@src/openhuman/providers/factory.rs`:
- Around line 261-285: The code currently calls lookup_key_for_slug(slug,
config) unconditionally which can fail for providers that don't need
credentials; change it to only call lookup_key_for_slug inside the branches that
require a key (AuthStyle::Anthropic and AuthStyle::Bearer), and keep
AuthStyle::None and AuthStyle::OpenhumanJwt paths using
make_openai_compatible_provider or make_openhuman_backend without performing a
credential lookup; update the match arm implementations around
make_openai_compatible_provider, AuthStyle::Anthropic, AuthStyle::Bearer,
AuthStyle::None and the AuthStyle::OpenhumanJwt routing to ensure
lookup_key_for_slug is invoked lazily only where needed.
---
Nitpick comments:
In `@src/openhuman/channels/providers/web.rs`:
- Around line 1335-1342: The provider_role_for_model_override function
duplicates a mapping used by AgentBuilder; refactor by extracting this mapping
into a single shared helper (e.g., pub fn provider_role_for_model_override or a
const MAP in a common module) and have both provider_role_for_model_override and
the AgentBuilder call site use that shared helper instead of duplicating the
match; update references in provider_role_for_model_override and in the
AgentBuilder code in builder.rs to call the new shared helper so both use the
exact same hint/alias-to-role mapping.
In `@tests/json_rpc_e2e.rs`:
- Around line 190-285: Both chat_completions and generic_chat_completions
duplicate the request-capture block; extract that logic into a helper (e.g.,
capture_chat_completion_request or push_chat_request) that accepts the Uri,
HeaderMap and &Value body, builds the auth_header/x_api_key the same way,
constructs the JSON object and calls with_chat_completion_requests to push it;
replace the duplicated blocks in chat_completions and generic_chat_completions
with a single call to that helper to remove duplication and keep behavior
identical.
🪄 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: 4bc9c109-3908-4713-8b1e-a7874172cc5d
📒 Files selected for processing (12)
scripts/mock-api/routes/__tests__/llm.test.mjsscripts/mock-api/routes/llm.mjssrc/openhuman/agent/harness/session/builder.rssrc/openhuman/channels/providers/web.rssrc/openhuman/channels/providers/web_tests.rssrc/openhuman/credentials/ops_tests.rssrc/openhuman/doctor/core_tests.rssrc/openhuman/providers/factory.rssrc/openhuman/providers/factory_test.rssrc/openhuman/providers/router.rssrc/openhuman/providers/router_test.rstests/json_rpc_e2e.rs
Summary
auth_style = "none"OpenAI-compatible providershint:*workloads*_test.rsmodules to keep implementation files focused without losing private-item coverageProblem
Solution
auth_style = "none", and add explicit regression tests for missing-key, invalid URL, and routing alias cases.factory.rsandrouter.rsinto child*_test.rsmodules via#[path = ...]to preserve access to private helpers while reducing implementation-file noise.Submission Checklist
diff-cover) meet the gate enforced by.github/workflows/coverage.yml. Local verification was blocked, but CI coverage jobs enforce this gate before merge.## Relatedbecause no matrix row changes were requireddocs/RELEASE-MANUAL-SMOKE.md) — Rust/test-harness only, no release-cut desktop surface changeCloses #NNNin the## Relatedsection — no linked issue for this PRImpact
Related
AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
test/custom-provider-harness20e57220Validation Run
pnpm --filter openhuman-app format:checkpnpm typechecknode --test scripts/mock-api/routes/__tests__/llm.test.mjscargo test --manifest-path Cargo.toml resolve_translates_openhuman_tier_aliases_via_route_table -- --nocapturecargo test --manifest-path Cargo.toml cloud_provider_with_auth_none_does_not_require_api_key -- --nocapturecargo test --manifest-path Cargo.toml provider_role_override_routes_hint_workloads -- --nocapturebash scripts/test-rust-with-mock.sh --test json_rpc_e2ecargo fmt --allpnpm --filter openhuman-app rust:checkValidation Blocked
command:cargo llvm-cov/ merged diff-cover verification not completed locallyerror:mixed rustup/Homebrew toolchain setup made local coverage reporting unreliableimpact:changed-line coverage gate still needs confirmation from CIBehavior Changes
Parity Contract
hint:agenticstays on backend routing while custom reasoning providers apply only where intendedDuplicate / Superseded PR Handling