Skip to content

fix: allow custom providers without a list models endpoint to pass in any model rather than restrict it on vk#2694

Merged
akshaydeo merged 1 commit intomainfrom
fix/allow_custom_providers_without_a_list_models_endpoint_to_pass_in_any_model_rather_than_restrict_it_on_vk
Apr 14, 2026
Merged

fix: allow custom providers without a list models endpoint to pass in any model rather than restrict it on vk#2694
akshaydeo merged 1 commit intomainfrom
fix/allow_custom_providers_without_a_list_models_endpoint_to_pass_in_any_model_rather_than_restrict_it_on_vk

Conversation

@danpiths
Copy link
Copy Markdown
Collaborator

Summary

Fixes model allowance checking for custom providers by passing provider
configuration to the model catalog. This enables proper handling of custom
providers that have the list-models endpoint disabled, allowing them to bypass
catalog validation when using unrestricted model lists.

Changes

  • Modified IsModelAllowedForProvider to accept a ProviderConfig parameter
    and handle custom providers without list-models endpoints
  • Updated all callers of IsModelAllowedForProvider to pass the provider
    configuration
  • Added logic to bypass catalog validation for custom providers with disabled
    list-models when using unrestricted allowed models ([] or ["*"])
  • Enhanced BudgetResolver to accept and use an in-memory store for provider
    configuration access
  • Updated Nix flake lock file to newer nixpkgs version

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (Next.js)
  • Docs

How to test

Validate that custom providers with disabled list-models endpoints can use
unrestricted model lists:

# Core/Transports
go version
go test ./...

# Test specific model catalog functionality
go test ./framework/modelcatalog/...

# Test governance plugin integration
go test ./plugins/governance/...

Test scenarios:

  1. Custom provider with list-models disabled + ["*"] → should allow any model
  2. Custom provider with list-models enabled + ["*"] → should use catalog
    validation
  3. Regular providers → should continue using catalog validation as before

Screenshots/Recordings

N/A - Backend logic changes only

Breaking changes

  • Yes
  • No

The API signature change is internal and all callers have been updated in this
PR.

Related issues

#2351

Security considerations

This change relaxes validation for a specific case (custom providers without
list-models + unrestricted model lists), but maintains security by:

  • Only applying to custom providers with explicitly disabled list-models
  • Only when using unrestricted model configurations
  • Preserving all existing validation for other scenarios

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@danpiths danpiths requested a review from akshaydeo April 14, 2026 05:24
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Copy Markdown
Collaborator Author

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • Improvements

    • Enhanced model availability checks to properly account for custom provider configurations when validating which models are allowed for each provider.
    • Improved provider configuration handling to ensure that custom provider restrictions are respected during model allowance decisions.
  • Tests

    • Added test coverage for custom provider configuration scenarios in model allowance validation.

Walkthrough

A parameter providerConfig is added to model allowance-checking functions across the model catalog, governance plugin, and HTTP transport layers. The catalog function now accepts provider configuration to evaluate custom-provider exceptions. Governance components are updated to retrieve and pass provider configs from an in-memory store. The HTTP transport layer updates related function signatures to propagate this information.

Changes

Cohort / File(s) Summary
Model Catalog Core
framework/modelcatalog/main.go, framework/modelcatalog/main_test.go
IsModelAllowedForProvider now accepts providerConfig parameter and treats allowedModels as unrestricted when empty or exactly ["*"]; custom provider exception added where custom providers with ListModelsRequest disabled bypass catalog checks. Tests updated to pass provider config and verify custom provider behavior.
Governance Resolver
plugins/governance/resolver.go, plugins/governance/main.go
NewBudgetResolver constructor gains InMemoryStore parameter. isModelAllowed conditionally retrieves provider config from in-memory store and passes it to model catalog's allowance check when both catalog and store are available.
Governance Test Call Sites
plugins/governance/model_provider_governance_test.go, plugins/governance/resolver_test.go, plugins/governance/tracker_test.go
All NewBudgetResolver instantiations updated to pass additional nil argument for the new in-memory store parameter.
Governance Utils
plugins/governance/utils.go
filterModelsForVirtualKey now conditionally uses provider config from in-memory store when available; otherwise falls back to direct allowlist matching. Added slices import for membership check.
HTTP Transport
transports/bifrost-http/handlers/providers.go
keyAllowsModelForList and keyModelListAllowsModel functions updated to accept and pass providerConfig parameter to model allowance checks. Formatting adjustments to struct field tags/layout (no functional impact).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Through catalogs and configs we now trace,
With provider details passed to every place,
The custom exceptions skip the catalog's gaze,
While others still travel the traditional ways,
The changes ripple—consistent, but widely embraced!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main fix: allowing custom providers without list-models endpoints to pass any model rather than restricting it.
Description check ✅ Passed The description follows the template with all key sections completed: Summary, Changes, Type of change, Affected areas, How to test, Breaking changes, Related issues, Security considerations, and Checklist all properly filled out.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/allow_custom_providers_without_a_list_models_endpoint_to_pass_in_any_model_rather_than_restrict_it_on_vk

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Suite Available

This PR can be tested by a repository admin.

Run tests for PR #2694

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 14, 2026

Confidence Score: 4/5

  • Addresses a real bug but introduces a regression in the three caller sites that can silently skip catalog validation for existing users.
  • Two P1 findings: resolver.go and main.go both now skip model-catalog validation when inMemoryStore is nil, which regresses cross-provider alias resolution for Go SDK users without HTTP transport. The fix in IsModelAllowedForProvider itself is correct, but the callers need adjusting before this is safe to merge.
  • plugins/governance/resolver.go and plugins/governance/main.go (loadBalanceProvider) — the inMemoryStore != nil guard needs to be decoupled from the catalog nil-check.

Important Files Changed

Filename Overview
framework/modelcatalog/main.go Adds custom-provider bypass logic to IsModelAllowedForProvider; the hasListModelsEndpointDisabled assignment calls a method on a potentially nil CustomProviderConfig pointer (safe but confusing — see inline comment).
plugins/governance/resolver.go Changes condition from modelCatalog != nil to modelCatalog != nil && governanceInMemoryStore != nil, silently skipping catalog validation when inMemoryStore is nil — a behavioral regression for Go SDK users without HTTP transport.
plugins/governance/main.go Same catalog-bypass regression as resolver.go in loadBalanceProvider; misleading "model catalog not available" comment now also fires when catalog is present but inMemoryStore is nil.
plugins/governance/utils.go Fixes a latent nil-dereference on p.modelCatalog in filterModelsForVirtualKey, but the inMemoryStore nil-guard introduces the same catalog-skipping regression as the other callers.
transports/bifrost-http/handlers/providers.go Correctly threads providerConfig through keyAllowsModelForList and keyModelListAllowsModel; also fixes minor alignment in two map literals.

Comments Outside Diff (2)

  1. plugins/governance/resolver.go, line 339-347 (link)

    P1 Catalog validation silently skipped when inMemoryStore is nil

    The condition changed from r.modelCatalog != nil to r.modelCatalog != nil && r.governanceInMemoryStore != nil. When the plugin is initialised without an in-memory store (documented valid use-case for Go SDK without HTTP transport), the model catalog is now skipped entirely and the code falls back to naïve slices.Contains matching. Provider-prefixed allowed-model entries (e.g. ["openai/gpt-4o"] on an OpenRouter VK) will be incorrectly denied because the cross-provider alias logic lives in the catalog.

    IsModelAllowedForProvider already accepts a nil providerConfig and skips the custom-provider bypass in that case, so the catalog can still be used safely:

    if r.modelCatalog != nil {
        var providerConfigPtr *configstore.ProviderConfig
        if r.governanceInMemoryStore != nil {
            cfg := r.governanceInMemoryStore.GetConfiguredProviders()[provider]
            providerConfigPtr = &cfg
        }
        return r.modelCatalog.IsModelAllowedForProvider(provider, model, providerConfigPtr, pc.AllowedModels)
    }

    The same pattern should be applied in loadBalanceProvider (main.go) and filterModelsForVirtualKey (utils.go).

  2. plugins/governance/main.go, line 579-591 (link)

    P1 Same catalog-bypass regression in loadBalanceProvider

    Changing p.modelCatalog != nil to p.modelCatalog != nil && p.inMemoryStore != nil means the catalog is not used when inMemoryStore is nil (Go SDK without HTTP transport), falling back to simple slices.Contains. Provider-prefixed allowed-model entries stop working. The comment "Fallback when model catalog is not available" is also now misleading since this branch now fires when the catalog is available.

    Apply the same fix as in resolver.go: pass nil as providerConfig when inMemoryStore is nil, so the catalog is still consulted:

    if p.modelCatalog != nil {
        provider := schemas.ModelProvider(config.Provider)
        var providerConfigPtr *configstore.ProviderConfig
        if p.inMemoryStore != nil {
            cfg := p.inMemoryStore.GetConfiguredProviders()[provider]
            providerConfigPtr = &cfg
        }
        isProviderAllowed = p.modelCatalog.IsModelAllowedForProvider(provider, modelStr, providerConfigPtr, config.AllowedModels)
    } else {
        // Fallback when model catalog is not available: simple string matching
        ...
    }

Reviews (1): Last reviewed commit: "fix: allow custom providers without a li..." | Re-trigger Greptile

Comment thread framework/modelcatalog/main.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
plugins/governance/resolver_test.go (1)

26-26: The new resolver dependency is still untested.

All updated constructor call sites pass nil for the added store argument, so none of these tests exercise the provider-config-aware path that motivated this change. Please add at least one EvaluateVirtualKeyRequest case with a real custom-provider config and ListModels=false so the new behavior is covered end to end.

Also applies to: 41-41, 59-59, 82-82, 114-114, 137-137, 198-198, 220-220, 247-247, 281-281, 315-315, 338-338, 353-353, 401-401, 476-476

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/governance/resolver_test.go` at line 26, Tests currently construct
the resolver with NewBudgetResolver(store, nil, logger, nil) leaving the new
provider-config store untested; add at least one EvaluateVirtualKeyRequest test
case in resolver_test.go that constructs the resolver with a real
provider-config (non-nil store) and supplies an EvaluateVirtualKeyRequest
containing a custom-provider config and ListModels=false so the
provider-config-aware path in methods like EvaluateVirtualKeyRequest is executed
end-to-end; update the table-driven tests (the cases referenced around
NewBudgetResolver calls) to include this case and assert the expected
provider-specific behavior/output.
framework/modelcatalog/main_test.go (1)

163-209: Add coverage for the empty-allowlist branch.

The new logic treats both [] and ["*"] as unrestricted, but these additions only exercise the wildcard branch. A regression in the len(allowedModels) == 0 path would still pass this suite.

🧪 Suggested test
+func TestIsModelAllowedForProvider_CustomProviderListModelsDisabled_EmptyAllowedModels(t *testing.T) {
+	mc := newTestCatalog(nil, nil)
+
+	providerConfig := configstore.ProviderConfig{
+		CustomProviderConfig: &schemas.CustomProviderConfig{
+			AllowedRequests: &schemas.AllowedRequests{
+				ListModels: false,
+			},
+		},
+	}
+
+	assert.True(t, mc.IsModelAllowedForProvider("custom-provider", "any-model", &providerConfig, []string{}))
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/modelcatalog/main_test.go` around lines 163 - 209, Add a test
covering the empty-allowlist branch by calling mc.IsModelAllowedForProvider with
allowedModels set to an empty slice (e.g., []string{}) and verifying it behaves
like the wildcard case; specifically, add a test (similar to
TestIsModelAllowedForProvider_CustomProviderListModelsEnabled or
TestIsModelAllowedForProvider_NilProviderConfig) that uses newTestCatalog and
providerConfig as appropriate and asserts that an in-catalog model returns true
and an out-of-catalog model returns false when allowedModels is []string{} to
ensure the len(allowedModels) == 0 path is exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@framework/modelcatalog/main_test.go`:
- Around line 163-209: Add a test covering the empty-allowlist branch by calling
mc.IsModelAllowedForProvider with allowedModels set to an empty slice (e.g.,
[]string{}) and verifying it behaves like the wildcard case; specifically, add a
test (similar to TestIsModelAllowedForProvider_CustomProviderListModelsEnabled
or TestIsModelAllowedForProvider_NilProviderConfig) that uses newTestCatalog and
providerConfig as appropriate and asserts that an in-catalog model returns true
and an out-of-catalog model returns false when allowedModels is []string{} to
ensure the len(allowedModels) == 0 path is exercised.

In `@plugins/governance/resolver_test.go`:
- Line 26: Tests currently construct the resolver with NewBudgetResolver(store,
nil, logger, nil) leaving the new provider-config store untested; add at least
one EvaluateVirtualKeyRequest test case in resolver_test.go that constructs the
resolver with a real provider-config (non-nil store) and supplies an
EvaluateVirtualKeyRequest containing a custom-provider config and
ListModels=false so the provider-config-aware path in methods like
EvaluateVirtualKeyRequest is executed end-to-end; update the table-driven tests
(the cases referenced around NewBudgetResolver calls) to include this case and
assert the expected provider-specific behavior/output.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cc47d535-7027-4211-b3e7-4be196c3a76b

📥 Commits

Reviewing files that changed from the base of the PR and between 588e975 and 04a41a4.

⛔ Files ignored due to path filters (1)
  • flake.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • framework/modelcatalog/main.go
  • framework/modelcatalog/main_test.go
  • plugins/governance/main.go
  • plugins/governance/model_provider_governance_test.go
  • plugins/governance/resolver.go
  • plugins/governance/resolver_test.go
  • plugins/governance/tracker_test.go
  • plugins/governance/utils.go
  • transports/bifrost-http/handlers/providers.go

@danpiths danpiths linked an issue Apr 14, 2026 that may be closed by this pull request
2 tasks
@akshaydeo akshaydeo merged commit 1bda46a into main Apr 14, 2026
18 of 20 checks passed
@akshaydeo akshaydeo deleted the fix/allow_custom_providers_without_a_list_models_endpoint_to_pass_in_any_model_rather_than_restrict_it_on_vk branch April 14, 2026 09:12
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.

[Bug]: custom provider virtual key requires non-empty model list

3 participants