feat: make model api key settings explicit#740
Conversation
📝 WalkthroughWalkthroughAdds ModelApiKeysSettings, a client React component to view, validate, save, and delete model provider API keys globally or per-repository (SWR-backed), integrates it into ModelsSettings, and provides a Vitest + React Testing Library suite covering save, override, display, and delete behaviors. ChangesModel API Keys Settings Feature
Sequence Diagram — Save/Delete flow: sequenceDiagram
participant User
participant UI as ModelApiKeysSettings
participant API as /api/secrets or /api/repos/{owner}/{name}/secrets
participant SWR as SWRCache
participant Toast
User->>UI: edits keys / clicks Save or Remove
UI->>API: PUT changed secrets / DELETE key
API-->>UI: 200 { status: "ok" }
UI->>SWR: mutate/revalidate secrets endpoint
UI->>Toast: show success or error
🎯 4 (Complex) | ⏱️ ~60 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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: 2
🧹 Nitpick comments (1)
packages/web/src/components/settings/model-api-keys-settings.test.tsx (1)
67-67: 📐 Maintainability & Code Quality | ⚡ Quick winExtract
dedupingIntervalinto a named ms constant.Line 67 inlines a duration default (
Infinity) directly in config. Please define it once as a named constant with unit in the name and reference it here.As per coding guidelines, “For durations and timeouts: use … milliseconds for TypeScript, encode the unit in variable names … define each default value exactly once in a named constant.”
Suggested change
+const DEDUPING_INTERVAL_MS = Number.POSITIVE_INFINITY; + function renderWithSWR(fallback: Record<string, unknown> = {}) { const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => { @@ provider: () => new Map(), fallback, - dedupingInterval: Infinity, + dedupingInterval: DEDUPING_INTERVAL_MS, revalidateOnFocus: false,🤖 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 `@packages/web/src/components/settings/model-api-keys-settings.test.tsx` at line 67, The test in model-api-keys-settings references a duration inline via dedupingInterval: Infinity; extract this into a single named millisecond constant (e.g., DEFAULT_DEDUPING_INTERVAL_MS) declared once near the top of the test/module and replace the inline value with that constant wherever dedupingInterval is used (update any objects or calls that set dedupingInterval in the test, such as the config/props passed to the ModelApiKeysSettings component).Source: Coding guidelines
🤖 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 `@packages/web/src/components/settings/model-api-keys-settings.test.tsx`:
- Around line 131-135: The test is asserting badges that render only after
SWR/UI updates, so replace the synchronous queries with async ones: after
calling selectRepo() in the test for ModelApiKeysSettings, wait for the badges
using await screen.findByText("Inherited") and await screen.findByText("Set")
(or wrap getByText assertions in await waitFor(...)) to avoid races with
isLoading/fetchError; update the assertions referencing
screen.getByText("Inherited") and screen.getByText("Set") to use async finders
or waitFor accordingly.
In `@packages/web/src/components/settings/model-api-keys-settings.tsx`:
- Around line 303-312: The Remove button can be clicked while a save is in
progress; update its disabled condition to also block when the component is
saving. In the JSX for the Button (the one using onClick={() =>
handleDelete(item.key)} and props disabled={!hasDirectKey || deletingKey ===
item.key}), include the saving state (e.g., saving === true) so the disabled
prop is true if saving, hasDirectKey is false, or deletingKey matches item.key;
ensure the same saving flag is used by save logic to prevent concurrent
delete/save races.
---
Nitpick comments:
In `@packages/web/src/components/settings/model-api-keys-settings.test.tsx`:
- Line 67: The test in model-api-keys-settings references a duration inline via
dedupingInterval: Infinity; extract this into a single named millisecond
constant (e.g., DEFAULT_DEDUPING_INTERVAL_MS) declared once near the top of the
test/module and replace the inline value with that constant wherever
dedupingInterval is used (update any objects or calls that set dedupingInterval
in the test, such as the config/props passed to the ModelApiKeysSettings
component).
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: ef029f9d-6e79-49aa-8c88-7e65d519e20c
📒 Files selected for processing (3)
packages/web/src/components/settings/model-api-keys-settings.test.tsxpackages/web/src/components/settings/model-api-keys-settings.tsxpackages/web/src/components/settings/models-settings.tsx
|
@cygaar can you include a screenshot/recording of the feature? |
|
@ColeMurray added a screenshot |
|
[Automated Review] Summary
Overall: This is a clean, secure, well-tested component, and the underlying behavior is correct end-to-end (verified against the backend — see below). My concerns are not bugs; they are two design-fit issues: (1) it forks the existing secrets logic rather than sharing it, and (2) it presents Design & Fit AssessmentI traced the whole data path before judging, so this is grounded rather than speculative. ✅ The idea is coherent with the system. The three keys map exactly onto the three model providers in the shared catalog (
Minor fit note: the same secret is now editable from two tabs (the generic Secrets tab and the new Models section). They're views over one store so it's consistent, but it's a second surface for the same data. Critical IssuesNone. No correctness, security, or data-integrity blockers — the frontend↔backend contract ( Suggestions
Nitpicks
Positive Feedback
Questions
VerdictComment. The implementation is correct, secure, and well-tested, and the feature genuinely fits the system's three-provider model. Not requesting changes on correctness grounds. Before merge, though, it's worth a conscious decision on the two design-fit points — the logic duplication and the Modal Methodology: the new component file isn't on the local checkout, so it was reviewed from the PR diff; every backend assumption was verified against the current |
Summary
Makes the API key settings for models explicit (similar to how it's setup in Cursor), to make it easier to configure them.
Changes
Adds fixed inputs for:
Supports both global and per-repo scopes using the same encrypted secrets APIs as the existing Secrets settings.
Shows whether each key is Set, Inherited, or Not set.
Allows repo-specific keys to override global keys.
Adds save/remove actions for direct keys.
Adds focused tests for global save, repo override save, inherited status, and repo key deletion.
Testing
Screenshot
Summary by CodeRabbit
New Features
Tests