Skip to content

feat(ai): community AI providers (Browse Community) - #107

Merged
ddutchie merged 8 commits into
mainfrom
ddutchie/providers
Aug 1, 2026
Merged

ddutchie merged 8 commits into
mainfrom
ddutchie/providers

Conversation

@ddutchie

@ddutchie ddutchie commented Aug 1, 2026 •

Copy link
Copy Markdown
Owner

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.json manifest. Also fixes a models.dev context-window lookup bug for gateway/proxy model ids and improves community-logo rendering.

Type of change

  • Bug fix
  • New feature
  • Refactor / code quality
  • Docs / changelog
  • Tests

Screenshots / recording

Checklist

  • npm run type-check:all passes
  • npm run lint passes
  • npm test passes (1540 passed / 12 skipped)
  • npm run test:e2e passes (run before merging UI changes or cutting a release)
  • If this ships a major, user-facing feature: added an entry to scripts/features.config.js (v2.5.19-community-providers) so it appears in the "What's New" modal
  • No hardcoded colours — CSS variables only
  • No text-[Npx] pixel font classes — rem equivalents only
  • New IPC handlers wrapped in handle() and return IpcResult<T> (registry:fetchProviders / registry:refreshProviders)
  • New DB migrations appended (not edited) in schema.ts — N/A (providers persist to localStorage, no DB change)
  • New SQL goes in electron/db/queries.ts — N/A (no new SQL)
  • New dependencies or devDependencies — N/A (none added)
  • New MCP tools registered — N/A (no new MCP tools)
  • If you added/removed a --external:<pkg> flag — N/A (compile script unchanged)

Notes for reviewer

  • Separate manifest by design. Community providers use a new providers.json (fetched via registry:fetchProviders) so the AI-provider catalog evolves independently of the tools/commands manifest.json. Types live in shared/chat/registry-schema.ts (parseProvidersManifest, https-validated baseUrl/apiKeyUrl, fail-soft per-entry parsing) and are mirrored in src/types/index.ts + electron/preload.ts.
  • No auto-select. installCommunityProvider adds to the shared savedProviders list and dedups by communityId/name; it deliberately does not hijack either surface's active provider.
  • Keys are stored in the OS keychain (secrets.set("llm", …)), only a secret:// ref lands in the store — same path as the manual provider form.
  • ConnectorLogo now 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.
  • The models.dev normalizeId fix (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.
  • The matching cairn-community changes (provider kind + 10 providers, incl. Merge's official logo) are committed in the sibling repo and need pushing separately.

Summary by CodeRabbit

  • New Features

    • Added Browse Community controls in AI Chat and Coding Agent settings.
    • Browse, search, filter, refresh, and install community AI provider presets.
    • Securely store API keys and share installed providers across supported features.
    • Prevent duplicate providers and support updating existing presets.
  • Bug Fixes

    • Improved provider/model ID normalization.
    • Improved connector logo rendering and theme-safe fallback styling.
    • Added validation for provider endpoints and API-key URLs.

…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)
@coderabbitai

coderabbitai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ddutchie, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 26 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b9b122e7-fb45-40a3-91e3-34a132c0c25f

📥 Commits

Reviewing files that changed from the base of the PR and between b5b39ce and aca558e.

📒 Files selected for processing (13)
  • changelogs/v2.5.19.md
  • electron/lib/community-registry.test.ts
  • electron/lib/community-registry.ts
  • electron/preload.ts
  • src/app/globals.css
  • src/components/settings/tools/BrowseCommunityModal.tsx
  • src/components/settings/tools/BrowseProvidersModal.tsx
  • src/components/settings/tools/ConnectorLogo.tsx
  • src/components/ui/model-picker.tsx
  • src/lib/models-dev.test.ts
  • src/lib/models-dev.ts
  • src/store/slices/ui.test.ts
  • src/store/slices/ui.ts
📝 Walkthrough

Walkthrough

The 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.

Changes

Community provider presets

Layer / File(s) Summary
Provider manifest contracts and validation
src/types/index.ts, shared/chat/registry-schema.ts, shared/chat/registry-schema.test.ts
Defines provider manifest types and validates HTTPS endpoints, API-key URLs, metadata, and malformed entries.
Provider registry fetching and IPC
electron/lib/community-registry.ts, electron/ipc/community-registry-handlers.ts, electron/preload.ts
Adds generic cache handling, provider fetch and refresh APIs, stale-cache fallback, background revalidation, and typed IPC exposure.
Shared provider installation state
src/store/slices/ui.ts, src/store/slices/ui.test.ts
Adds community metadata and installs or updates providers with secret storage, deduplication, configuration reconciliation, and preserved active selections.
Provider browsing and settings entry points
src/components/settings/tools/BrowseProvidersModal.tsx, src/components/settings/AISettings.tsx, src/components/settings/AgentSettings.tsx, scripts/features.config.js, changelogs/v2.5.19.md
Adds catalog search, filtering, refresh, installation, API-key entry, status handling, settings entry points, and release metadata.

Model identifier normalization

Layer / File(s) Summary
Normalize model identifiers
src/lib/models-dev.ts, src/lib/models-dev.test.ts
Exports normalizeId, removes eligible provider prefixes, narrows version stripping, and preserves model variant suffixes with regression tests.

Connector logo rendering

Layer / File(s) Summary
Theme-safe connector logo containers
src/components/settings/tools/ConnectorLogo.tsx, src/components/settings/tools/BrowseCommunityModal.tsx
Updates real logos and fallback glyphs to use padded, rounded, centered, theme-safe containers.

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
Loading

Possibly related PRs

  • ddutchie/cairn#92: Introduced the community registry infrastructure extended here for provider manifests.
  • ddutchie/cairn#95: Added saved-provider infrastructure extended here for community-provider installation.
  • ddutchie/cairn#105: Extended related community registry manifest and cache handling.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: adding community AI providers with a Browse Community feature.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ddutchie/providers

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (3)
electron/lib/community-registry.ts (1)

246-255: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add provider-manifest cache hooks to __test for symmetry.

__test only exposes readCache/writeCache bound to CACHE_FILE (the tools/commands manifest). It has no equivalent bound to PROVIDERS_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 value

Provider manifest types are declared independently in three files. RegistryProviderDefinition/RegistryProviderEntry/ProvidersManifest/ProvidersFetchResult currently 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 for CommunityManifest/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, import RegistryProviderEntry/ProvidersManifest/ProvidersFetchResult types from shared/chat/registry-schema.ts instead of redeclaring them.
  • electron/preload.ts#L68-L79: if the preload bundling setup allows importing from shared/, import the types from shared/chat/registry-schema.ts instead 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 value

Use the current Zod 4 object key handler.

This project pins Zod 4, where z.string().url() and .passthrough() are legacy APIs. Replace baseUrl/apiKeyUrl validators with z.url() and switch providerEntry to .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

📥 Commits

Reviewing files that changed from the base of the PR and between 1ed22d8 and b5b39ce.

📒 Files selected for processing (17)
  • changelogs/v2.5.19.md
  • electron/ipc/community-registry-handlers.ts
  • electron/lib/community-registry.ts
  • electron/preload.ts
  • scripts/features.config.js
  • shared/chat/registry-schema.test.ts
  • shared/chat/registry-schema.ts
  • src/components/settings/AISettings.tsx
  • src/components/settings/AgentSettings.tsx
  • src/components/settings/tools/BrowseCommunityModal.tsx
  • src/components/settings/tools/BrowseProvidersModal.tsx
  • src/components/settings/tools/ConnectorLogo.tsx
  • src/lib/models-dev.test.ts
  • src/lib/models-dev.ts
  • src/store/slices/ui.test.ts
  • src/store/slices/ui.ts
  • src/types/index.ts

Comment thread changelogs/v2.5.19.md Outdated
Comment thread scripts/features.config.js
Comment thread src/components/settings/tools/BrowseProvidersModal.tsx
Comment thread src/components/settings/tools/ConnectorLogo.tsx Outdated
Comment thread src/components/settings/tools/ConnectorLogo.tsx Outdated
Comment thread src/lib/models-dev.ts
Comment thread src/store/slices/ui.ts
Comment thread src/store/slices/ui.ts Outdated
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.
@ddutchie
ddutchie merged commit 47b1794 into main Aug 1, 2026
6 checks passed
@ddutchie
ddutchie deleted the ddutchie/providers branch August 1, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant