Repository navigation
feat(ai): community AI providers (Browse Community) - #107
Conversation
…els.dev lookup normalizeId dropped two id shapes: a leading "provider:" prefix (e.g. merge:deepseek-v4-flash) was mistaken for the model name, and the greedy version-tag strip (-v\d+.*) swallowed trailing words, collapsing deepseek-v4-flash to deepseek. Strip a leading provider: prefix first (with or without a / path) and only strip pure version tags (-v1, -v1:0) so suffixes like -flash / -luna survive. Adds normalizeId unit tests.
Add a Browse Community button in Settings -> AI & Chat that installs preset OpenAI-compatible providers (endpoint + optional default model) from the cairn-community catalog; the user just enters their API key, stored in the OS keychain. - Community providers use a SEPARATE providers.json manifest, decoupled from the tools/commands manifest - shared/chat/registry-schema.ts: RegistryProviderEntry, ProvidersManifest, parseProvidersManifest (https-validated baseUrl + apiKeyUrl, fail-soft per-entry parsing) + tests - src/types: mirrored ProvidersManifest / ProvidersFetchResult / RegistryProviderEntry - electron/lib/community-registry.ts: refactor fetch/cache into a generic cache-first + ETag core; add fetchProvidersManifest / refreshProvidersManifest - IPC + preload: registry:fetchProviders / registry:refreshProviders - store: SavedProvider gains source/communityId; installCommunityProvider (keychain key storage, dedup by communityId/name, no auto-select) + tests - BrowseProvidersModal: browse + inline API-key prompt + install/update - changelog v2.5.19 + What's New entry
The generic fallback glyph (plug for services/providers, MCP mark for MCP servers) was tinted with the entry's brandColor, so a near-black or near-white brand colour made it invisible against the card on one theme (e.g. a provider with no logo). The fallback glyph now uses a theme-safe colour; brandColor still tints real logos and the card background.
A dark monochrome brand logo (e.g. Merge's charcoal mark) tinted with brandColor vanished against the dark theme's near-black card. ConnectorLogo now draws real logos on a fixed light chip (app-store-style), so dark or light marks keep contrast on both themes; the no-logo fallback uses a theme-safe glyph on a neutral tile. Removed the competing brandColor-tinted wrapper tile at the Browse Providers/Tools call sites and bumped the icon size 22 -> 36.
…aces - Move the Browse Community row directly below the AI Provider selector (above the saved-providers switcher) in Settings -> AI & Chat, and add the same entry to Settings -> Coding Agent (shared saved-providers list) - Render the API-key prompt inline beneath the clicked provider (with Enter to confirm, Escape to cancel) instead of at the bottom of the list - Hide the redundant top-right button while the key prompt is open (shows a quiet "Enter key" hint); label the row action Add / Added / Update and the prompt confirm Confirm - changelog + What's New updated (available in both AI & Chat and Coding Agent)
|
Warning Review limit reached
Next review available in: 26 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: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (13)
📝 WalkthroughWalkthroughThe change adds a community provider catalog with validated manifests, cache-first fetching, IPC and preload APIs, browsing controls, secure API-key storage, deduplicated installation, and shared provider settings. It also updates model ID normalization and connector logo rendering. ChangesCommunity provider presets
Model identifier normalization
Connector logo rendering
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Settings
participant BrowseProvidersModal
participant RegistryAPI
participant CommunityRegistry
participant ProviderCache
participant UISlice
participant SecretsBridge
Settings->>BrowseProvidersModal: Open Browse Community
BrowseProvidersModal->>RegistryAPI: fetchProviders()
RegistryAPI->>CommunityRegistry: Fetch provider manifest
CommunityRegistry->>ProviderCache: Read or revalidate cache
ProviderCache-->>BrowseProvidersModal: ProvidersFetchResult
BrowseProvidersModal->>UISlice: installCommunityProvider(entry, apiKey)
UISlice->>SecretsBridge: Store API key
UISlice-->>BrowseProvidersModal: Provider ID
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 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: 8
🧹 Nitpick comments (3)
electron/lib/community-registry.ts (1)
246-255: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd provider-manifest cache hooks to
__testfor symmetry.
__testonly exposesreadCache/writeCachebound toCACHE_FILE(the tools/commands manifest). It has no equivalent bound toPROVIDERS_CACHE_FILE. Add matching hooks so tests can exercise cache validation, corruption recovery, and stale-cache fallback for the providers manifest the same way they can for the community manifest.♻️ Proposed addition
export const __test = { MANIFEST_URL, PROVIDERS_URL, CACHE_FILE, PROVIDERS_CACHE_FILE, cachePath: () => cacheFilePath(CACHE_FILE), readCache: () => readCacheFile(CACHE_FILE, parseManifest), writeCache: (env: CacheEnvelope<CommunityManifest>) => writeCacheFile(CACHE_FILE, env), + providersCachePath: () => cacheFilePath(PROVIDERS_CACHE_FILE), + readProvidersCache: () => readCacheFile(PROVIDERS_CACHE_FILE, parseProvidersManifest), + writeProvidersCache: (env: CacheEnvelope<ProvidersManifest>) => writeCacheFile(PROVIDERS_CACHE_FILE, env), };🤖 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 `@electron/lib/community-registry.ts` around lines 246 - 255, Extend the exported __test object with provider-manifest cache hooks bound to PROVIDERS_CACHE_FILE: add matching cache-path, read-cache, and write-cache helpers using the provider manifest parser and CacheEnvelope type, while preserving the existing community-manifest hooks unchanged.shared/chat/registry-schema.ts (2)
106-135: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueProvider manifest types are declared independently in three files.
RegistryProviderDefinition/RegistryProviderEntry/ProvidersManifest/ProvidersFetchResultcurrently match across all three sites, but none imports from the others, so a future field change in one will not be caught by the compiler in the other two. This mirrors a pre-existing pattern forCommunityManifest/RegistryMcpEntry/RegistryServiceEntry, so it is low-priority, but worth tracking.
shared/chat/registry-schema.ts#L106-L135: keep this as the Zod-validated source of truth; no change needed here beyond awareness.src/types/index.ts#L384-L418: if the renderer bundler allows it, importRegistryProviderEntry/ProvidersManifest/ProvidersFetchResulttypes fromshared/chat/registry-schema.tsinstead of redeclaring them.electron/preload.ts#L68-L79: if the preload bundling setup allows importing fromshared/, import the types fromshared/chat/registry-schema.tsinstead of redeclaring them locally; otherwise, add a short comment linking back to the canonical definition so future field changes are easier to keep in sync.🤖 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 `@shared/chat/registry-schema.ts` around lines 106 - 135, Keep shared/chat/registry-schema.ts lines 106-135 as the canonical Zod-validated provider type definition; no direct change is needed there. In src/types/index.ts lines 384-418, import RegistryProviderEntry, ProvidersManifest, and ProvidersFetchResult from the shared definition instead of redeclaring them if renderer bundling permits. In electron/preload.ts lines 68-79, likewise import the shared types when supported; otherwise add a brief comment linking to shared/chat/registry-schema.ts as the canonical source.
281-294: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse the current Zod 4 object key handler.
This project pins Zod 4, where
z.string().url()and.passthrough()are legacy APIs. ReplacebaseUrl/apiKeyUrlvalidators withz.url()and switchproviderEntryto.catchall(...)so the schema avoids deprecated Zod 3-shim paths.🤖 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 `@shared/chat/registry-schema.ts` around lines 281 - 294, Update providerDefinition to validate baseUrl and apiKeyUrl with Zod 4’s z.url() while preserving the existing HTTPS requirement and optionality. Replace providerEntry’s deprecated passthrough() usage with catchall(...) configured to preserve arbitrary entry metadata fields.
🤖 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 `@changelogs/v2.5.19.md`:
- Line 9: Update the changelog entry’s final validation statement to distinguish
the provider endpoint base URL, which must use HTTPS, from the defaultModel
value, which is validated separately as a model identifier.
In `@scripts/features.config.js`:
- Around line 259-271: Regenerate the runtime feature data by running node
scripts/generate-features.js after adding the v2.5.19-community-providers entry
to FEATURES, and commit the resulting update to src/generated/new-features.json.
In `@src/components/settings/tools/BrowseProvidersModal.tsx`:
- Around line 88-100: Update the tag comparison in the filtered useMemo to
normalize each tag to lowercase before checking whether it includes the
already-lowercased query q. Preserve the existing filtering behavior for
provider names, blurbs, and categories.
In `@src/components/settings/tools/ConnectorLogo.tsx`:
- Around line 79-80: Update the connector logo style in ConnectorLogo to replace
the raw background and fallback color hex values with connector-logo semantic
CSS custom properties consumed via var(...). Define the required connector-logo
color tokens in the appropriate stylesheet/theme, and preserve the existing
brand tint and contrast behavior while using those tokens.
- Around line 66-80: Update the brand-color validation in the registry schema
and the rendering path around ConnectorLogo so only valid, sufficiently
contrasting colors are accepted for the fixed light chip; reject or sanitize
light/invalid manifest values and fall back to the established safe dark color
before using brandColor as the SVG foreground.
In `@src/lib/models-dev.ts`:
- Around line 44-57: Update normalizeId’s providerPrefix handling so identifiers
like gpt-4:thinking-v2 retain the full model and suffix instead of being reduced
to thinking-v2; make the prefix-stripping condition distinguish provider
prefixes from colon-delimited model variants, while preserving stripping for
valid prefixed IDs such as merge:deepseek/deepseek-v4-flash.
In `@src/store/slices/ui.ts`:
- Around line 629-664: Prevent concurrent installCommunityProvider calls for the
same entry.id from creating duplicate saved providers. Serialize installations
by community ID, or re-resolve the existing provider and replace/deduplicate by
communityId inside the set commit, while preserving the keychain reference and
single-row result.
- Around line 638-645: The API key installation path must never fall back to
storing the raw key in renderer state when the Electron secrets bridge is
unavailable. Update the logic around apiKeyRef and secrets so keyed installation
is rejected without secure storage (or routed through an existing secure web
credential backend), and ensure row.apiKey receives only a secure reference
token.
---
Nitpick comments:
In `@electron/lib/community-registry.ts`:
- Around line 246-255: Extend the exported __test object with provider-manifest
cache hooks bound to PROVIDERS_CACHE_FILE: add matching cache-path, read-cache,
and write-cache helpers using the provider manifest parser and CacheEnvelope
type, while preserving the existing community-manifest hooks unchanged.
In `@shared/chat/registry-schema.ts`:
- Around line 106-135: Keep shared/chat/registry-schema.ts lines 106-135 as the
canonical Zod-validated provider type definition; no direct change is needed
there. In src/types/index.ts lines 384-418, import RegistryProviderEntry,
ProvidersManifest, and ProvidersFetchResult from the shared definition instead
of redeclaring them if renderer bundling permits. In electron/preload.ts lines
68-79, likewise import the shared types when supported; otherwise add a brief
comment linking to shared/chat/registry-schema.ts as the canonical source.
- Around line 281-294: Update providerDefinition to validate baseUrl and
apiKeyUrl with Zod 4’s z.url() while preserving the existing HTTPS requirement
and optionality. Replace providerEntry’s deprecated passthrough() usage with
catchall(...) configured to preserve arbitrary entry metadata fields.
🪄 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 Plus
Run ID: b8647073-aa1e-4b7d-a313-ed478c44e991
📒 Files selected for processing (17)
changelogs/v2.5.19.mdelectron/ipc/community-registry-handlers.tselectron/lib/community-registry.tselectron/preload.tsscripts/features.config.jsshared/chat/registry-schema.test.tsshared/chat/registry-schema.tssrc/components/settings/AISettings.tsxsrc/components/settings/AgentSettings.tsxsrc/components/settings/tools/BrowseCommunityModal.tsxsrc/components/settings/tools/BrowseProvidersModal.tsxsrc/components/settings/tools/ConnectorLogo.tsxsrc/lib/models-dev.test.tssrc/lib/models-dev.tssrc/store/slices/ui.test.tssrc/store/slices/ui.tssrc/types/index.ts
Remove the always-present empty check-mark spacer column that pushed model names far right of the favourite star; render the active check inline so names sit next to the star.
A forced Refresh sent a conditional GET with the cached ETag, so a stale CDN edge could answer 304 and keep serving the old catalog right after an update. Forced fetches now skip If-None-Match and cache-bust the URL, so Refresh always re-downloads the latest manifest.json / providers.json; background revalidation still uses the efficient conditional request. Also expose provider-manifest cache hooks on __test.
…edup
- models-dev normalizeId: don't mistake a hyphenated colon variant for a
provider prefix ("gpt-4:thinking-v2" no longer collapses to "thinking-v2");
only strip when the tail is a path or a non-variant model id. Added tests.
- installCommunityProvider: reject a keyed install when the OS keychain bridge
is unavailable (never persist a raw key in renderer state); re-resolve the
target row inside the set() commit so racing installs of the same entry
collapse to one row. Updated tests to mock the secrets bridge.
- ConnectorLogo: replace hardcoded chip hex with fixed --connector-chip-bg/-fg
tokens; add a luminance guard so a near-white/invalid brandColor falls back to
the safe dark foreground on the light chip.
- Browse modals: lowercase tags before matching the (lowercased) search query.
- changelog: distinguish HTTPS base-URL validation from model-id validation.
- preload: note the shared/chat/registry-schema.ts canonical source for the
hand-mirrored provider types.
What does this PR do?
Adds community AI providers: users can install preset OpenAI-compatible providers from the cairn-community catalog with one click and just enter their API key (stored in the OS keychain). Available in both Settings → AI & Chat and Settings → Coding Agent, backed by a separate
providers.jsonmanifest. Also fixes a models.dev context-window lookup bug for gateway/proxy model ids and improves community-logo rendering.Type of change
Screenshots / recording
Checklist
npm run type-check:allpassesnpm run lintpassesnpm testpasses (1540 passed / 12 skipped)npm run test:e2epasses (run before merging UI changes or cutting a release)scripts/features.config.js(v2.5.19-community-providers) so it appears in the "What's New" modaltext-[Npx]pixel font classes — rem equivalents onlyhandle()and returnIpcResult<T>(registry:fetchProviders/registry:refreshProviders)schema.ts— N/A (providers persist to localStorage, no DB change)electron/db/queries.ts— N/A (no new SQL)dependenciesordevDependencies— N/A (none added)--external:<pkg>flag — N/A (compile script unchanged)Notes for reviewer
providers.json(fetched viaregistry:fetchProviders) so the AI-provider catalog evolves independently of the tools/commandsmanifest.json. Types live inshared/chat/registry-schema.ts(parseProvidersManifest, https-validatedbaseUrl/apiKeyUrl, fail-soft per-entry parsing) and are mirrored insrc/types/index.ts+electron/preload.ts.installCommunityProvideradds to the sharedsavedProviderslist and dedups bycommunityId/name; it deliberately does not hijack either surface's active provider.secrets.set("llm", …)), only asecret://ref lands in the store — same path as the manual provider form.ConnectorLogonow renders real logos on a fixed light chip so dark monochrome brand marks stay legible on both themes; the no-logo fallback uses a theme-safe glyph. This also affects the existing Browse Tools modal.normalizeIdfix (fix(ai): resolve provider-prefixed and word-suffixed model ids) is bundled here since it's in the same area; it has its own unit tests.Summary by CodeRabbit
New Features
Bug Fixes