Skip to content

feat: restrict provider keys to specific models - #3418

Merged
steebchen merged 13 commits into
mainfrom
provider-key-model-allowlist
Aug 7, 2026
Merged

steebchen merged 13 commits into
mainfrom
provider-key-model-allowlist

Conversation

@steebchen

@steebchen steebchen commented Aug 4, 2026 •

Copy link
Copy Markdown
Member

Problem

Some upstream accounts only have a subset of a provider's catalogue enabled (e.g. a provider account that carries just a few models). The gateway had no way to know this, so auto-routing and fallback routing could pick a provider + credential that cannot serve the requested model, and the request would fail upstream instead of routing to a credential that works. Save-time credential validation also always probed the provider's default validation model — which is exactly the model such an account may not have — forcing admins to skip validation on the very credentials that needed it.

Approach

New nullable allowed_models (text[]) column on provider_key, holding canonical LLM Gateway model ids. NULL/empty means unrestricted; the shared providerKeyAllowsModel() helper (in @llmgateway/db) is the single comparison point.

Gateway enforcement (applies to both BYOK keys and managed credentials):

  • resolveProviderContext filters BYOK keys through the restriction (composed with the existing service-tier filter), so a restricted key is skipped in favor of a sibling key or — in hybrid mode — the credits fallback.
  • resolvePlatformCredential takes a new model option and skips managed credentials that exclude it, falling through to the next credential or the env vars. Filtering runs before variant narrowing, mirroring the existing filter semantics.
  • findManagedProviderIds accepts an optional modelId, so a provider whose every managed credential excludes the model is not advertised for it (unless an env key can still serve it).
  • All four routing sites in chat.ts (auto-routing candidate loop, unpinned routing, rate-limit fallback, low-uptime fallback) now compute availableProviders/providersWithKeys per model with restricted keys filtered out. For the auto loop this moved the availability computation inside the per-candidate loop; the extra findManagedProviderIds calls are served from the SWR cache.

Admin API (/admin/provider-credentials):

  • allowedModels on create/update: trimmed, deduped, validated against the catalogue (every id must have a live mapping for the provider); an emptied list stores NULL.
  • Save-time validation now probes the first chat-capable allowed model instead of the default validation model when a restriction is set (skipping the live probe entirely for image/embedding-only lists), so restricted credentials validate without skipValidation.
  • New POST .../self-test: runs the save-time probe standalone against a stored credential or unsaved dialog values, reporting the outcome instead of gating a write.
  • New POST .../verify-models: probes each listed model through the credential (batches of 5, capped at 50) and returns a per-model report — inCatalog, live valid/null (non-chat models can't be probed), status code and redacted upstream error.
  • The catalog route now includes each provider's live model ids for the picker.

Admin UI (provider credentials dialog):

  • "Allowed models" searchable multi-select over the provider's catalogue; pasting a comma/whitespace-separated list (including provider/model-prefixed ids) adds all entries at once, with unknown ids flagged inline.
  • "Self-test key" and "Verify models" buttons run the new endpoints against the current (possibly unsaved) form values and render the per-model report; results clear whenever an input that affects the probe changes.
  • New "Models" table column showing the restriction (count with full list on hover, or "All").

Notes for review

  • Custom provider keys are exempt from the restriction — their per-key custom model catalog already scopes them.
  • The empty list deliberately means "unrestricted", not "no models": both API and UI clear the restriction by storing NULL, and a key that can serve nothing is only ever a misconfiguration.
  • Migration is a single additive ALTER TABLE ... ADD COLUMN, generated via pnpm migrations.

Verification

  • pnpm build — all 17 tasks pass.
  • Unit tests (serial, matching test:unit semantics): 130 files / 2703 tests pass, including new specs for the helper, findManagedProviderIds/resolvePlatformCredential model filtering, BYOK filtering, and the admin CRUD + self-test + verify-models endpoints.

Screenshots

Credentials table — the new Models column: the managed credential is restricted to 3 models while the env key serves the full catalogue:

Credentials table with the Models column showing a 3-model restriction

Model picker — searchable multi-select over the provider's catalogue (comma-separated paste fills it in one go):

Allowed models picker open with search filtering the provider's catalogue

Self-test + verify report — both probes run live through the key against the real upstream; every listed model verified green:

Self-test result and per-model verification report, all models verified

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added optional model restrictions for managed and BYOK provider credentials.
    • Added credential self-tests and per-model verification from the administration interface.
    • Added model coverage indicators, model selection, validation, and verification results.
    • Provider routing now selects credentials based on model eligibility, including fallback scenarios.
  • Bug Fixes

    • Prevented credentials from being used for models they do not support.
    • Improved validation for unavailable, inactive, or unsupported provider models.

Adds an allowedModels list on provider_key so a credential whose upstream
account only has a subset of the provider's catalogue is never picked by
routing for a model it cannot serve.

- gateway: BYOK + managed credential selection and routing availability
  (auto loop, unpinned routing, rate-limit/uptime fallbacks) skip keys
  whose allowedModels exclude the routed model
- admin API: allowedModels CRUD with catalogue validation, save-time
  validation probes an allowed model instead of the default one, plus
  self-test and verify-models endpoints reporting per-model results
- admin UI: searchable multi-select with comma-separated paste support,
  self-test and verify-models buttons with a per-model report

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 4, 2026 18:56
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

Next review available in: 25 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: acd5c337-5be3-4e20-bda2-50d23af0dad5

📥 Commits

Reviewing files that changed from the base of the PR and between 645e122 and cf092ee.

📒 Files selected for processing (17)
  • apps/api/src/routes/admin-provider-credentials.spec.ts
  • apps/api/src/routes/admin-provider-credentials.ts
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/chat/tools/parse-provider-response.ts
  • apps/gateway/src/chat/tools/resolve-platform-credential.ts
  • apps/gateway/src/chat/tools/resolve-provider-context.ts
  • apps/gateway/src/lib/cached-queries-swr.spec.ts
  • apps/gateway/src/lib/managed-provider-key.spec.ts
  • ee/admin/src/app/provider-credentials/page.tsx
  • ee/admin/src/components/provider-credentials-manager.tsx
  • packages/actions/src/validate-provider-key.ts
  • packages/db/migrations/1786115489_motionless_fallen_one.sql
  • packages/db/migrations/meta/1786115489_snapshot.json
  • packages/db/migrations/meta/_journal.json
  • packages/db/src/schema.ts
  • packages/shared/src/components/index.tsx
  • packages/shared/src/components/multi-model-id-selector.tsx

Walkthrough

Managed provider credentials now support optional model allowlists. The API validates and probes selected models, gateway routing filters credentials by model, and the admin interface supports model selection, self-tests, and per-model verification.

Changes

Model eligibility contract

Layer / File(s) Summary
Model restriction storage and matching
packages/db/...
The provider key schema stores nullable allowedModels. The shared helper treats null and empty lists as unrestricted and performs exact model matching.
Pinned provider model validation
packages/actions/src/validate-provider-key.ts
Credential validation can resolve an exact active catalog model, apply region matching, and report unavailable or non-chat-capable models.

Credential lifecycle and validation API

Layer / File(s) Summary
Catalog and credential restrictions
apps/api/src/routes/admin-provider-credentials.ts
The catalog returns active mapped models. Credential creation and updates normalize, validate, persist, and revalidate allowedModels.
Credential probes
apps/api/src/routes/admin-provider-credentials.ts, apps/api/src/routes/admin-provider-credentials.spec.ts
New self-test and model-verification endpoints support stored or unsaved credentials, redacted errors, catalog results, bounded live probes, and aggregate validation results.

Model-aware provider routing

Layer / File(s) Summary
Managed credential discovery
apps/gateway/src/lib/cached-queries.ts, apps/gateway/src/chat/tools/resolve-platform-credential.ts
Managed provider discovery and credential resolution accept a model ID and skip credentials that exclude it.
BYOK and request routing
apps/gateway/src/chat/chat.ts, apps/gateway/src/chat/tools/resolve-provider-context.ts
Auto-routing, normal routing, fallback paths, and BYOK lookups apply model allowlists before selecting credentials.
Routing validation
apps/gateway/src/lib/managed-provider-key.spec.ts
Tests cover restricted and unrestricted credentials, variant fallback, managed credential selection, and BYOK predicates.

Admin credential management interface

Layer / File(s) Summary
Admin actions and page wiring
ee/admin/src/lib/admin-provider-credentials.ts, ee/admin/src/app/provider-credentials/page.tsx
The admin client adds typed self-test and model-verification actions and passes them to the manager.
Model selection and credential probes
ee/admin/src/components/provider-credentials-manager.tsx
The credential dialog supports catalog-backed model selection, pasted model IDs, allowlist persistence, self-tests, verification actions, loading states, and result reset on input changes.
Model coverage display
ee/admin/src/components/provider-credentials-manager.tsx
The table displays restricted model counts or unrestricted coverage. Verification results show separate statuses for verified, rejected, unknown, and non-probeable models.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Admin
  participant ProviderCredentialsManager
  participant AdminAPI
  participant validateProviderKey
  Admin->>ProviderCredentialsManager: select models and enter credential data
  ProviderCredentialsManager->>AdminAPI: submit self-test or model verification
  AdminAPI->>validateProviderKey: probe selected catalog model
  validateProviderKey-->>AdminAPI: return validation result
  AdminAPI-->>ProviderCredentialsManager: return per-model results
  ProviderCredentialsManager-->>Admin: display verification status
Loading

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.00% 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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: restricting provider keys to specific models.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch provider-key-model-allowlist
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch provider-key-model-allowlist

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.

Copilot AI 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.

Pull request overview

Adds an allowedModels restriction to provider keys/credentials so the gateway can avoid selecting credentials that cannot serve a requested model, and updates admin APIs/UI to configure and validate that restriction.

Changes:

  • Adds allowed_models text[] to provider_key plus a shared providerKeyAllowsModel() helper.
  • Enforces model restrictions during routing/credential selection (BYOK and managed keys), and updates provider availability computation to be model-aware.
  • Extends the admin provider-credentials API + UI with allowed-model selection, self-test, and per-model verification endpoints.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/db/src/schema.ts Adds allowedModels column to the provider_key table schema.
packages/db/src/provider-key-allowed-models.ts Introduces providerKeyAllowsModel() helper (NULL/empty = unrestricted).
packages/db/src/provider-key-allowed-models.spec.ts Unit tests for providerKeyAllowsModel() semantics.
packages/db/src/index.ts Re-exports the new helper from @llmgateway/db.
packages/db/migrations/meta/_journal.json Records the new migration in the Drizzle journal.
packages/db/migrations/1785867564_orange_proudstar.sql Migration adding allowed_models column to provider_key.
packages/actions/src/validate-provider-key.ts Adds pinned-model validation support for probing specific models.
ee/admin/src/lib/admin-provider-credentials.ts Adds typed client helpers for self-test and verify-models endpoints; threads allowedModels through mutations.
ee/admin/src/components/provider-credentials-manager.tsx UI: allowed-model picker, self-test + verify-models actions, and table column showing restriction.
ee/admin/src/app/provider-credentials/page.tsx Wires new admin client functions into the page.
apps/gateway/src/lib/managed-provider-key.spec.ts Tests managed credential filtering/advertising behavior with allowedModels.
apps/gateway/src/lib/cached-queries.ts Adds optional model filtering to findManagedProviderIds() and carries allowedModels through row reads.
apps/gateway/src/chat/tools/resolve-provider-context.ts Filters BYOK keys by model restriction and passes model to managed credential selection.
apps/gateway/src/chat/tools/resolve-platform-credential.ts Adds model-aware managed credential filtering.
apps/gateway/src/chat/chat.ts Computes provider availability per model and filters restricted keys/credentials across routing paths.
apps/api/src/routes/admin-provider-credentials.ts Admin API: store/validate allowedModels; add self-test + verify-models endpoints; catalog now includes provider model ids.
apps/api/src/routes/admin-provider-credentials.spec.ts Tests for allowedModels normalization/validation + new probe endpoints.
Suppressed comments (2)

apps/api/src/routes/admin-provider-credentials.ts:1008

  • createCredential validates allowedModels without passing the credential’s managedCredentialValidationOptions (region/config). This can validate against the wrong regional mapping for region-scoped providers, and it also means the allowedModels validation and the subsequent save-time probe can disagree about what’s “available”.
	validateAllowedModels(body.provider, allowedModels);

apps/api/src/routes/admin-provider-credentials.ts:1129

  • updateCredential validates allowedModels without passing the effective managedCredentialValidationOptions (taking into account any region/config changes in the same PATCH). For region-scoped providers this can validate against a different region than the credential will actually use.
		validateAllowedModels(existing.provider, allowedModels);
		updates.allowedModels = allowedModels;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +356 to +372
function validateAllowedModels(
provider: string,
allowedModels: string[] | null,
): void {
if (!allowedModels) {
return;
}
const unknown = allowedModels.filter(
(modelId) =>
getPinnedValidationModel(provider as ProviderId, modelId) === null,
);
if (unknown.length > 0) {
throw new HTTPException(400, {
message: `Not available from ${provider} per the catalogue: ${unknown.join(", ")}`,
});
}
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 792501b — validateAllowedModels now takes the credential's managedCredentialValidationOptions (config + region) at both create and update, with update using the config/region the PATCH leaves in effect, so the catalogue check resolves the same regional mapping the save-time probe does.

Comment on lines +1289 to +1293
const token = body.token?.trim()
? body.token
: credential
? readProviderKey(credential)
: undefined;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 792501b — the trimmed token is now what the probe and error redaction use, matching the presence check.

Comment on lines +119 to +121
options.model !== undefined
? (key) => providerKeyAllowsModel(key.allowedModels, options.model!)
: undefined,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 792501b — the model is captured into a local const before building the filter, no non-null assertion.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
ee/admin/src/components/provider-credentials-manager.tsx (1)

1455-1512: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Announce the probe result to assistive technology.

The self-test and verify-models results appear only after the async probe finishes. A screen reader user gets no announcement, because the container is not a live region. Add aria-live to a wrapper that is present before the result arrives. Also mark the two buttons with aria-busy while their probe runs.

♻️ Proposed change
-					{selfTestOutcome ? (
+					<div aria-live="polite" className="flex flex-col gap-2">
+					{selfTestOutcome ? (
 						selfTestOutcome.error || !selfTestOutcome.result?.valid ? (

Close the wrapper after the verify-models block:

 						</div>
 					) : null}
+					</div>
 				</div>
🤖 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 `@ee/admin/src/components/provider-credentials-manager.tsx` around lines 1455 -
1512, Wrap the self-test and verifyOutcome result section in a wrapper rendered
before either async result is available, and add aria-live so assistive
technology announces probe updates. Update both probe-triggering buttons to set
aria-busy while their respective self-test or model-verification operation is
running, preserving the existing result content and behavior.
packages/actions/src/validate-provider-key.ts (1)

176-190: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared region and mapping resolution.

This block now exists three times in this file: getValidationModel (Lines 52-60), getPinnedValidationModel (Lines 176-183), and validateProviderKey (Lines 298-305). The mapping lookup with region fallback is also duplicated at Lines 185-190 and Lines 331-337. Two helpers would remove all six copies and keep the region-fallback rule in one place.

As per coding guidelines: "Apply DRY principles for code reuse, except that explicit model/provider mapping duplication is preferred in packages/models" — this file is in packages/actions, so the exemption does not apply.

♻️ Proposed extraction
+function resolveSelectedRegion(
+	provider: ProviderId,
+	providerKeyOptions?: ProviderKeyOptions,
+): string | undefined {
+	const providerDef = providers.find((p) => p.id === provider) as
+		ProviderDefinition | undefined;
+	const regionKey = providerDef?.regionConfig?.optionsKey;
+	return regionKey
+		? ((providerKeyOptions as Record<string, string | undefined> | undefined)?.[
+				regionKey
+			] ?? providerDef?.regionConfig?.defaultRegion)
+		: undefined;
+}
+
+function findProviderMapping(
+	modelDef: { providers: readonly unknown[] },
+	provider: ProviderId,
+	region: string | undefined,
+): ProviderModelMapping | undefined {
+	const mappings = modelDef.providers as readonly ProviderModelMapping[];
+	return (
+		mappings.find(
+			(p) =>
+				p.providerId === provider && (p.region ?? null) === (region ?? null),
+		) ?? mappings.find((p) => p.providerId === provider)
+	);
+}

Then in getPinnedValidationModel:

-	const providerDef = providers.find((p) => p.id === provider) as
-		ProviderDefinition | undefined;
-	const regionKey = providerDef?.regionConfig?.optionsKey;
-	const selectedRegion = regionKey
-		? ((providerKeyOptions as Record<string, string | undefined> | undefined)?.[
-				regionKey
-			] ?? providerDef?.regionConfig?.defaultRegion)
-		: undefined;
-
-	const mapping = (modelDef.providers.find(
-		(p) =>
-			p.providerId === provider &&
-			((p as ProviderModelMapping).region ?? null) === (selectedRegion ?? null),
-	) ?? modelDef.providers.find((p) => p.providerId === provider)) as
-		ProviderModelMapping | undefined;
+	const selectedRegion = resolveSelectedRegion(provider, providerKeyOptions);
+	const mapping = findProviderMapping(modelDef, provider, selectedRegion);
 	if (!mapping) {
 		return null;
 	}
🤖 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/actions/src/validate-provider-key.ts` around lines 176 - 190,
Extract the repeated region resolution and provider-model mapping logic from
getValidationModel, getPinnedValidationModel, and validateProviderKey into
shared helpers in this file. Have the region helper resolve the configured
option or default region, and have the mapping helper perform the
region-specific lookup with fallback to the provider-only mapping; replace all
six duplicated blocks while preserving their current behavior.

Source: Coding guidelines

apps/api/src/routes/admin-provider-credentials.spec.ts (1)

1326-1386: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the pinned-model probe selection.

These tests cover normalization, catalogue rejection, and persistence. They cannot reach the new branch in validateCredentialToken (Lines 450-463 of apps/api/src/routes/admin-provider-credentials.ts), because isCredentialTestEnv() returns at Line 440 before the pinning logic runs. So the two decisions that the restriction exists for are unexercised:

  • A restricted credential probes an allowed model, not the provider's default validation model.
  • A restriction whose models are all non-chat-capable skips the probe instead of failing the save.

Mock validateProviderKey and assert the pinnedModelId argument to cover both.

🤖 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 `@apps/api/src/routes/admin-provider-credentials.spec.ts` around lines 1326 -
1386, Extend the provider-credential tests around validateCredentialToken to
mock validateProviderKey and bypass the isCredentialTestEnv early return. Add
coverage confirming restricted credentials pass an allowed chat-capable model as
pinnedModelId instead of the default validation model, and that restrictions
containing only non-chat-capable models skip probing rather than failing
creation. Assert the mock calls and resulting save behavior for both cases.
🤖 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 `@apps/api/src/routes/admin-provider-credentials.ts`:
- Line 1436: In the verification handler around normalizeAllowedModels, reject
an empty normalized modelIds array with a 400 response before evaluating results
or allValid. Apply the same guard to the corresponding flow around the
referenced later section, while preserving normal verification for non-empty
model lists.

In `@apps/gateway/src/chat/tools/resolve-platform-credential.ts`:
- Around line 77-82: Make the model parameter required in the platform
credential resolution API and update both chat credential call sites to pass the
selected model alongside selectionScope. In the managed credential filtering
logic, always apply providerKeyAllowsModel using model, removing the conditional
path that bypasses this check when model is omitted.

In `@apps/gateway/src/lib/managed-provider-key.spec.ts`:
- Around line 357-359: Replace the dynamic imports in
apps/gateway/src/lib/managed-provider-key.spec.ts at lines 357-359 and 385-387
with a shared module-scope resolvePlatformCredential import, and replace the
dynamic import at lines 421-422 with a module-scope providerKeyAllowsModel
import; update each test to use those top-level bindings.

In `@ee/admin/src/components/provider-credentials-manager.tsx`:
- Around line 1059-1084: Update handleSelfTest and handleVerifyModels to wrap
each awaited server action in try/catch/finally, preserving the existing outcome
handling for successful and failed results while ensuring
setSelfTestLoading(false) and setVerifyLoading(false) always execute in finally,
including when the invocation rejects.

---

Nitpick comments:
In `@apps/api/src/routes/admin-provider-credentials.spec.ts`:
- Around line 1326-1386: Extend the provider-credential tests around
validateCredentialToken to mock validateProviderKey and bypass the
isCredentialTestEnv early return. Add coverage confirming restricted credentials
pass an allowed chat-capable model as pinnedModelId instead of the default
validation model, and that restrictions containing only non-chat-capable models
skip probing rather than failing creation. Assert the mock calls and resulting
save behavior for both cases.

In `@ee/admin/src/components/provider-credentials-manager.tsx`:
- Around line 1455-1512: Wrap the self-test and verifyOutcome result section in
a wrapper rendered before either async result is available, and add aria-live so
assistive technology announces probe updates. Update both probe-triggering
buttons to set aria-busy while their respective self-test or model-verification
operation is running, preserving the existing result content and behavior.

In `@packages/actions/src/validate-provider-key.ts`:
- Around line 176-190: Extract the repeated region resolution and provider-model
mapping logic from getValidationModel, getPinnedValidationModel, and
validateProviderKey into shared helpers in this file. Have the region helper
resolve the configured option or default region, and have the mapping helper
perform the region-specific lookup with fallback to the provider-only mapping;
replace all six duplicated blocks while preserving their current behavior.
🪄 Autofix

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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 040ba48f-cad9-4702-b1ca-a7fdab64020f

📥 Commits

Reviewing files that changed from the base of the PR and between 728894e and 645e122.

📒 Files selected for processing (18)
  • apps/api/src/routes/admin-provider-credentials.spec.ts
  • apps/api/src/routes/admin-provider-credentials.ts
  • apps/gateway/src/chat/chat.ts
  • apps/gateway/src/chat/tools/resolve-platform-credential.ts
  • apps/gateway/src/chat/tools/resolve-provider-context.ts
  • apps/gateway/src/lib/cached-queries.ts
  • apps/gateway/src/lib/managed-provider-key.spec.ts
  • ee/admin/src/app/provider-credentials/page.tsx
  • ee/admin/src/components/provider-credentials-manager.tsx
  • ee/admin/src/lib/admin-provider-credentials.ts
  • packages/actions/src/validate-provider-key.ts
  • packages/db/migrations/1785867564_orange_proudstar.sql
  • packages/db/migrations/meta/1785867564_snapshot.json
  • packages/db/migrations/meta/_journal.json
  • packages/db/src/index.ts
  • packages/db/src/provider-key-allowed-models.spec.ts
  • packages/db/src/provider-key-allowed-models.ts
  • packages/db/src/schema.ts

Comment thread apps/api/src/routes/admin-provider-credentials.ts
Comment thread apps/gateway/src/chat/tools/resolve-platform-credential.ts
Comment thread apps/gateway/src/lib/managed-provider-key.spec.ts Outdated
Comment thread ee/admin/src/components/provider-credentials-manager.tsx
steebchen and others added 12 commits August 4, 2026 20:25
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lowlist

# Conflicts:
#	apps/api/src/routes/admin-provider-credentials.ts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- pass model to the two chat retry-path resolvePlatformCredential calls
  so alternate-key fallback also honors allowedModels; document why the
  option stays optional (non-chat endpoints only hold upstream ids)
- validate allowedModels against the credential's config/region so
  region-scoped credentials check the mapping they will actually use
- reject whitespace-only verify-models lists instead of allValid: true
- use the trimmed token for test probes, matching the presence check
- reset probe loading flags in finally so a rejected server action
  cannot wedge both buttons
- aria-live region + aria-busy for the async probe results
- dedupe region/mapping resolution in validate-provider-key.ts
- top-level imports in the managed-provider-key spec
- cover the pinned-model probe selection with a mocked live path
- drop an unused map param that failed lint on main

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the admin dialog's hand-rolled AllowedModelsPicker with the
shared MultiModelSelector, fed with catalogue definitions filtered to
the models the catalog endpoint reports live on the provider. The
comma-separated paste support moves into the shared component so every
consumer gets it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The model-filtered lookups reuse the existing swrWrap + Drizzle Redis
cache paths (narrowing happens in memory after the cached row fetch),
so a Postgres outage keeps serving them from the mirror. These tests
pin that down for findManagedProviderIds and the filtered BYOK
selection, including the text[] surviving the JSON round-trip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MultiModelSelector renders mapping-derived detail and needs full
catalogue definitions, which is the wrong shape here — the admin deals
in plain root model ids. Adds a lean shared MultiModelIdSelector
(search, comma-separated paste, flagged unknown ids) and reverts the
paste handler bolted onto MultiModelSelector.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@steebchen
steebchen enabled auto-merge August 7, 2026 15:52
@steebchen
steebchen disabled auto-merge August 7, 2026 15:57
@steebchen
steebchen merged commit 638c031 into main Aug 7, 2026
11 checks passed
@steebchen
steebchen deleted the provider-key-model-allowlist branch August 7, 2026 15:57
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.

2 participants