Conversation
…dashboard Let an admin launch otari with almost nothing set, then add provider API keys, set pricing, and manage aliases from the web dashboard. Backend: - Encrypt stored provider keys at rest with OTARI_SECRET_KEY (Fernet) via services/secret_box.py; keys are write-only over the API (responses expose only last4). - services/provider_store_service.py persists provider credentials and overlays them onto config.providers through a TTL refresher (standalone only), mirroring the alias refresher; baseline kept on config._provider_baseline. - services/master_key_service.py generates and prints a master key on first run when none is set; hash stored in runtime_settings. - /v1/provider-credentials CRUD plus per-instance and ad-hoc test endpoints, and a providers catalog endpoint. - Alembic migration for the provider_credentials table. - Fix a 500 on /v1/models when an orphaned pricing key referenced a removed provider (normalize_pricing_key now tolerates AnyLLMError). Dashboard: - New Providers page (two-tab known/custom add form, test-before-save, flags undecryptable keys) and a first-run onboarding panel. - Aliases moved to their own page; "Make an alias" from a model detail prefills the target. - Models page reworked around browsing plus inline pricing; removed the add form and the Released and Features columns. - Adopt react-router-dom (HashRouter) instead of hardcoded section paths. - Header with logo and sign-out, main-pane-only scroll, aligned update prompt, subtle otari.ai link, and CSS-masked secret fields. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 6 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (11)
WalkthroughThe PR adds encrypted runtime provider management across the gateway and dashboard, including persistence, master-key bootstrap, provider overlays, CRUD and connection-testing APIs, hash-based routing, provider onboarding, alias management, and updated documentation and bundled assets. ChangesRuntime provider management
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
🤖 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 `@README.md`:
- Around line 158-163: Update the fenced block containing the Otari first-run
output to specify the text language, using a text fence while preserving its
existing contents.
In `@src/gateway/api/routes/providers.py`:
- Around line 235-250: Extend _validate_instance to validate api_base and any
URL-bearing client_args with the repository’s existing outbound URL/SSRF
validator, rejecting invalid endpoints before provider calls. Invoke this
validation for both ad-hoc test flows and persisted write flows referenced by
the affected sections, reusing the shared validator rather than introducing new
SSRF logic.
- Around line 364-380: Make the optimistic concurrency handling in the PATCH
flow around save_credential atomic: perform the update using instance and the
expected updated_at as conditions, or lock the existing row within the
transaction before saving. Detect when the conditional update affects no row and
raise HTTPException with status 412, while preserving normal saves when the
version still matches.
- Around line 204-214: Update UpdateStoredProviderRequest handling and the
corresponding save_credential call to distinguish omitted fields from explicitly
provided null values using request.model_fields_set or an equivalent sentinel.
Preserve omitted fields as unchanged, while passing explicit null so
provider_type, api_base, client_args, and api_key can be cleared or removed as
requested.
In `@src/gateway/services/model_discovery_service.py`:
- Around line 227-229: Update the exception handling in the provider connection
test to stop using raw exception text: log only impl_name and the exception
class, and return a stable public error message instead of _short_error(exc).
Preserve the existing ProviderDiscovery structure and empty models result.
In `@web/src/App.tsx`:
- Around line 40-41: Update the provider query failure handling around the
isSuccess check in App so an isError result no longer returns null indefinitely.
Render the existing error/retry state if available, or redirect to /providers,
while preserving the current loading and successful provider-rendering behavior.
In `@web/src/components/Login.tsx`:
- Around line 71-79: Update the Login component’s “First run? Where to find your
key” help text to remove instructions for retrieving the master key from server
logs, including the “Your master key” and docker logs guidance. Direct operators
to the approved permission-restricted key location or explicit setup flow
instead, without exposing or logging the secret.
In `@web/src/pages/ModelsPage.tsx`:
- Around line 388-391: Update InfoTooltip to generate its DOM id with React
useId() instead of the fixed "pricing-info-tip" value, ensuring every tooltip
instance has a unique id while preserving its aria-describedby association and
existing label behavior.
- Line 772: Update the pricing error display in the ModelsPage editor to render
failures from both setPricing and deletePricing, ensuring reset errors provide
the same feedback as save errors. Reuse the existing errorMessage helper and
preserve the current styling and conditional rendering behavior.
In `@web/src/pages/ProvidersPage.tsx`:
- Line 684: Update the ErrorBanner invocation in ProvidersPage to include errors
from settings, updateSettings, and deleteProvider in addition to the existing
meta.error and stored.error fallbacks. Preserve the current precedence while
ensuring failures from all provider management actions are surfaced.
- Around line 650-659: Update runTest to use testProvider.mutateAsync with
row-local async error handling, ensuring each invocation independently updates
its instance through success or failure. Preserve the existing pending, done,
result, and error state shapes while preventing later tests from replacing
earlier completion handlers.
- Around line 120-127: Update the onSelectionChange handler to handle null keys
by clearing the parent provider value and selectedKey, rather than ignoring
deselection. Preserve the existing provider lookup and text update for non-null
keys so the UI and submitted providerType remain synchronized.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 96967236-43f5-4213-bb05-03be5ac098fe
⛔ Files ignored due to path filters (3)
docs/public/openapi.jsonis excluded by!docs/public/openapi.jsonuv.lockis excluded by!**/*.lock,!**/uv.lockweb/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (46)
AGENTS.mdREADME.mdalembic/versions/b4d6f8a0c2e4_add_provider_credentials.pydocs/configuration.mddocs/public/otari.postman_collection.jsonpyproject.tomlsrc/gateway/api/deps.pysrc/gateway/api/routes/_passthrough.pysrc/gateway/api/routes/_pipeline.pysrc/gateway/api/routes/providers.pysrc/gateway/cli.pysrc/gateway/core/config.pysrc/gateway/main.pysrc/gateway/models/entities.pysrc/gateway/services/master_key_service.pysrc/gateway/services/model_discovery_service.pysrc/gateway/services/pricing_service.pysrc/gateway/services/provider_kwargs.pysrc/gateway/services/provider_metadata_service.pysrc/gateway/services/provider_store_service.pysrc/gateway/services/secret_box.pysrc/gateway/static/dashboard/assets/index-8efccAl2.csssrc/gateway/static/dashboard/assets/index-BiTSmG14.jssrc/gateway/static/dashboard/assets/index-BmfhJJsH.csssrc/gateway/static/dashboard/assets/index-C8ycIJF3.jssrc/gateway/static/dashboard/index.htmltests/integration/test_provider_credentials_api.pytests/integration/test_provider_store.pytests/unit/test_gateway_cli.pytests/unit/test_master_key_service.pytests/unit/test_provider_instances.pytests/unit/test_provider_store_service.pytests/unit/test_secret_box.pyweb/package.jsonweb/src/App.tsxweb/src/api/hooks.tsweb/src/api/types.tsweb/src/components/AppShell.tsxweb/src/components/Login.tsxweb/src/components/UpdatePrompt.tsxweb/src/pages/AliasesPage.test.tsxweb/src/pages/AliasesPage.tsxweb/src/pages/ModelsPage.test.tsxweb/src/pages/ModelsPage.tsxweb/src/pages/ProvidersPage.test.tsxweb/src/pages/ProvidersPage.tsx
- providers: distinguish an omitted field from an explicit null on PATCH (model_fields_set plus a tri-state sentinel in save_credential), so api_base, provider_type, client_args, and the key can each be cleared or left untouched - providers: lock the row FOR UPDATE on PATCH so the optimistic-concurrency check and the write it guards are atomic on PostgreSQL - discovery: log only the exception class on a failed model listing or connection test, since some providers echo a partial key in the message; the capped message still reaches the master-key caller in the response - dashboard: never strand the index route on a blank screen when the providers query fails, fall back to Providers - dashboard: surface settings/update/delete failures on Providers and reset failures on the inline price form - dashboard: give each pricing tooltip a unique id (useId) and clear the provider combobox value when the selection is cleared - docs: add a language to a README fenced block Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Description
Lets an admin launch otari with almost nothing configured, then add provider API keys, set model pricing, and manage aliases entirely from the web dashboard. No config-file edit or restart required.
On first run with no master key, otari generates one and prints it to the console; the dashboard sign-in screen takes that key. From there the new Providers page can add credentials, which are encrypted at rest and merged into the live provider config by a background refresher.
Backend
services/secret_box.pyencrypts stored keys withOTARI_SECRET_KEY(Fernet). Keys are write-only over the API; responses expose onlylast4. Undecryptable rows (for example after the secret key changes) are flagged rather than crashing.services/provider_store_service.pypersists credentials and overlays them ontoconfig.providersthrough a TTL refresher (standalone only), mirroring the existing alias refresher. The per-config baseline lives onconfig._provider_baseline.services/master_key_service.pygenerates and prints a master key when none is set; the hash is stored inruntime_settings./v1/provider-credentialsCRUD, a per-instance test endpoint and an ad-hoc test-before-save endpoint, and a providers catalog endpoint. All routes are master-key gated and standalone-only (not mounted in hybrid mode).provider_credentialstable./v1/modelsreturned a 500 when an orphaned pricing key referenced a provider that had been removed.normalize_pricing_keynow toleratesAnyLLMError, so the stale key is skipped instead of failing the request.Dashboard
react-router-dom(HashRouter) in place of hardcoded section switching.Notes for reviewers
test_provider_store.py,test_provider_credentials_api.py) run in CI under Docker; locally I exercised the same behavior against SQLite.-webkit-text-securityfor masking, so the key renders unmasked on Firefox (masked on Chromium and Safari). Called out in case we want a JS fallback later./v1. Happy to switch to BrowserRouter with clean URLs if preferred.PR Type
Relevant issues
Checklist
tests/unit,tests/integration).make lint,make typecheck,make test).uv run python scripts/generate_openapi.py).AI Usage
AI Model/Tool used: Claude Code (Opus 4.8)
Any additional AI details you'd like to share:
Implemented by Claude Code through back-and-forth with @njbrake. The reasoning and design decisions are his; the code and prose are Claude's.