Skip to content

fix: filter list models by virtual key providers#3187

Open
princepal9120 wants to merge 3 commits intomaximhq:mainfrom
princepal9120:prince/fix-2887-vk-list-models
Open

fix: filter list models by virtual key providers#3187
princepal9120 wants to merge 3 commits intomaximhq:mainfrom
princepal9120:prince/fix-2887-vk-list-models

Conversation

@princepal9120
Copy link
Copy Markdown
Contributor

Summary

Why

When /v1/models is called with a virtual key scoped to one provider, Bifrost previously attempted to list models from every configured provider. Governance then rejected the disallowed providers, creating noisy expected errors in logs and inflating error rates.

Validation

  • cd core && go test . -run TestFilterProvidersByContext -count=1
  • cd transports && go test ./bifrost-http/handlers -run TestListModels_VKFilter -count=1

Duplicate check

  • Checked open PRs for issue #2887 and the issue title before opening. No open PR was found.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 1, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 643f1647-9cac-4a14-a670-5d342d1bb121

📥 Commits

Reviewing files that changed from the base of the PR and between 997655e and e3f6817.

📒 Files selected for processing (1)
  • core/schemas/bifrost.go
✅ Files skipped from review due to trivial changes (1)
  • core/schemas/bifrost.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Virtual-key based provider filtering for model listing requests so access keys can control which providers are queried.
  • Performance

    • Provider queries now only fan out to available providers, reducing unnecessary concurrent calls.
  • Bug Fixes

    • Safer behavior when access-key data is missing or malformed: requests fail closed and avoid unintended queries.
  • Tests

    • Unit tests added to validate provider-filtering behavior and edge cases.

Walkthrough

Adds virtual-key-aware provider filtering: a new request helper resolves virtual keys to allowed providers and stores them in request context; the HTTP list-models handler short-circuits using that filter; core ListAllModels consults context via a new unexported helper to restrict which providers are queried; tests added for the helper.

Changes

Virtual-key → provider filtering

Layer / File(s) Summary
Context population / VK resolution
transports/bifrost-http/handlers/list_models_vk.go
New helper: when a virtual key is present, resolves it via the config store, extracts trimmed provider identifiers, converts them to schemas.ModelProvider, and sets them in request context under schemas.BifrostContextKeyAvailableProviders. Tolerates not-found virtual keys; sends internal error only on resolution failures.
Handler short-circuit
transports/bifrost-http/handlers/inference.go
listModels now runs the virtual-key filter when provider query param is empty and returns early (skipping Bifrost client call) if the filter indicates no providers should be queried.
Core filtering logic
core/bifrost.go
ListAllModels calls new unexported filterProvidersByContext(ctx, providerKeys) before spawning provider goroutines. The helper: passthrough if ctx/key absent, returns empty slice on type mismatch or empty inputs, otherwise intersects configured provider keys with available providers from context.
Tests
core/bifrost_test.go
Added TestFilterProvidersByContext with subtests for nil-context passthrough, single-provider restriction, empty-slice deny-all, and malformed-context (non-[]schemas.ModelProvider) resulting in empty provider list.
Schema comments/formatting
core/schemas/bifrost.go
Minor comment/whitespace adjustments around BifrostContextKeyAvailableProviders and nearby constants; no value/type changes.

Sequence Diagram

sequenceDiagram
    participant Client
    participant HTTP as HTTP Handler
    participant VK as VK Resolver (list_models_vk)
    participant Config as Config Store
    participant Context as Request Context
    participant Core as Bifrost Core (ListAllModels)

    Client->>HTTP: GET /models (no provider param, bearer token)
    HTTP->>VK: Check for virtual key
    VK->>Config: GetVirtualKeyByValue(token)
    Config-->>VK: Virtual key config (providers)
    VK->>Context: Set BifrostContextKeyAvailableProviders
    VK-->>HTTP: Return (filter applied)
    HTTP->>Core: ListAllModels(ctx)
    Core->>Context: Read BifrostContextKeyAvailableProviders
    Core->>Core: filterProvidersByContext()
    Core->>Core: Query only filtered providers
    Core-->>HTTP: Aggregated models
    HTTP-->>Client: Return filtered models
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related Issues

Poem

🐰 I hopped through keys and trimmed each name,
Only allowed providers join the game,
Context set tidy, core asks just those few,
No noisy errors, the logs feel new,
A small clean hop — voilà, fewer queues!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Title check ✅ Passed The title clearly and specifically describes the main change: filtering the list models operation by virtual key providers.
Description check ✅ Passed The PR description covers the summary, rationale, and validation steps, but is missing several template sections like Changes breakdown, Type of change checklist, Affected areas checklist, and Security considerations.
Linked Issues check ✅ Passed The code changes directly address issue #2887 by implementing provider filtering at the core level and virtual key validation at the handler level to prevent unnecessary provider calls.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #2887: filtering providers by virtual key in core logic, validating virtual keys in handlers, and adding corresponding tests.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.11.4)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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
Review rate limit: 5/8 reviews remaining, refill in 19 minutes and 11 seconds.

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

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 (1)
core/bifrost.go (1)

611-614: ⚡ Quick win

Harden malformed available-providers handling to fail closed.

At Line 611–614, if the context value is present but wrongly typed, the code falls back to providerKeys (all configured providers). That re-opens broad fan-out in malformed-context cases. Prefer fail-closed when the key is present but invalid.

Suggested patch
 func filterProvidersByContext(ctx *schemas.BifrostContext, providerKeys []schemas.ModelProvider) []schemas.ModelProvider {
 	if ctx == nil {
 		return providerKeys
 	}
 
-	availableProviders, ok := ctx.Value(schemas.BifrostContextKeyAvailableProviders).([]schemas.ModelProvider)
-	if !ok {
+	rawAvailableProviders := ctx.Value(schemas.BifrostContextKeyAvailableProviders)
+	if rawAvailableProviders == nil {
 		return providerKeys
 	}
+	availableProviders, ok := rawAvailableProviders.([]schemas.ModelProvider)
+	if !ok {
+		return []schemas.ModelProvider{}
+	}
 
 	if len(availableProviders) == 0 || len(providerKeys) == 0 {
 		return []schemas.ModelProvider{}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/bifrost.go` around lines 611 - 614, When reading
ctx.Value(schemas.BifrostContextKeyAvailableProviders), change the logic to
distinguish "key absent" vs "key present but wrong type": first assign raw :=
ctx.Value(...); if raw == nil keep the existing behavior and return
providerKeys; if raw is non-nil but the type assertion to
[]schemas.ModelProvider fails (availableProviders, ok :=
raw.([]schemas.ModelProvider)), return a fail-closed value (e.g., nil or empty
slice) instead of falling back to providerKeys. Update the handling around
availableProviders and providerKeys accordingly so malformed context values do
not reopen broad fan-out.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@core/bifrost.go`:
- Around line 611-614: When reading
ctx.Value(schemas.BifrostContextKeyAvailableProviders), change the logic to
distinguish "key absent" vs "key present but wrong type": first assign raw :=
ctx.Value(...); if raw == nil keep the existing behavior and return
providerKeys; if raw is non-nil but the type assertion to
[]schemas.ModelProvider fails (availableProviders, ok :=
raw.([]schemas.ModelProvider)), return a fail-closed value (e.g., nil or empty
slice) instead of falling back to providerKeys. Update the handling around
availableProviders and providerKeys accordingly so malformed context values do
not reopen broad fan-out.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e785d1a4-381e-499b-b7b4-6a2d0c1bb632

📥 Commits

Reviewing files that changed from the base of the PR and between 734f02d and f36ec1e.

📒 Files selected for processing (4)
  • core/bifrost.go
  • core/bifrost_test.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/list_models_vk.go

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 1, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 1, 2026

Confidence Score: 4/5

Safe to merge with awareness of the unresolved IsActive check; incorrect scoping for inactive VKs is a bounded defect.

Core filtering logic in filterProvidersByContext is correct and well-tested. The unresolved IsActive guard (flagged in a previous review thread and still absent) means an inactive VK still scopes the fan-out to its provider configs rather than being treated as absent, producing a wrong—potentially empty—model list for callers using a deactivated key. That is a present defect on the changed path, capping confidence at 4.

transports/bifrost-http/handlers/list_models_vk.go — IsActive not checked before applying VK provider scope.

Important Files Changed

Filename Overview
core/bifrost.go Adds filterProvidersByContext call inside ListAllModels before the goroutine fan-out; logic is correct and well-guarded.
core/bifrost_test.go Adds TestFilterProvidersByContext covering nil ctx, valid filter, empty deny-all, and malformed type assertion; coverage is thorough for the new helper.
core/schemas/bifrost.go Updates BifrostContextKeyAvailableProviders comment to acknowledge internal components may set it; minor whitespace/alignment fixes elsewhere.
transports/bifrost-http/handlers/inference.go Wires applyListModelsVirtualKeyProviderFilter into listModels for the provider-empty (fan-out) path only; integration point is minimal and correct.
transports/bifrost-http/handlers/list_models_vk.go New file: resolves VK from request, builds availableProviders from its ProviderConfigs, and stamps BifrostContextKeyAvailableProviders on the context. IsActive is not checked before scoping the fan-out (flagged previously, still unresolved).

Reviews (3): Last reviewed commit: "docs: clarify provider filter context ow..." | Re-trigger Greptile

availableProviders = append(availableProviders, schemas.ModelProvider(provider))
}

bifrostCtx.SetValue(schemas.BifrostContextKeyAvailableProviders, availableProviders)
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.

P2 Sets a "DO NOT SET MANUALLY" context key

schemas.BifrostContextKeyAvailableProviders is annotated in the schema as // []ModelProvider (set by bifrost - DO NOT SET THIS MANUALLY). This PR sets it directly in a transport-layer handler, mirroring an identical pattern already present in transports/bifrost-http/integrations/router.go:775. The comment appears to be aimed at external callers, but it's misleading for internal components and could cause confusion for future contributors. Consider either updating the schema comment to clarify that internal bifrost components may set this key, or establishing a setter helper that makes the intent explicit.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +18 to +57
func (h *CompletionHandler) applyListModelsVirtualKeyProviderFilter(ctx *fasthttp.RequestCtx, bifrostCtx *schemas.BifrostContext) bool {
vkValue := governanceplugin.ParseVirtualKeyFromFastHTTPRequest(ctx)
if vkValue == nil {
return true
}

trimmedVKValue := strings.TrimSpace(*vkValue)
if trimmedVKValue == "" {
return true
}

if h.config == nil || h.config.ConfigStore == nil {
SendError(ctx, fasthttp.StatusServiceUnavailable, "database store unavailable")
return false
}

vk, err := h.config.ConfigStore.GetVirtualKeyByValue(ctx, trimmedVKValue)
if err != nil {
if errors.Is(err, configstore.ErrNotFound) {
return true
}
SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("Failed to resolve virtual key: %v", err))
return false
}
if vk == nil {
return true
}

availableProviders := make([]schemas.ModelProvider, 0, len(vk.ProviderConfigs))
for _, providerConfig := range vk.ProviderConfigs {
provider := strings.TrimSpace(providerConfig.Provider)
if provider == "" {
continue
}
availableProviders = append(availableProviders, schemas.ModelProvider(provider))
}

bifrostCtx.SetValue(schemas.BifrostContextKeyAvailableProviders, availableProviders)
return true
}
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.

P2 No unit tests for applyListModelsVirtualKeyProviderFilter

The existing TestListModels_VKFilter* tests in providers_test.go exercise ProviderHandler, not CompletionHandler. The new applyListModelsVirtualKeyProviderFilter on CompletionHandler — including the not-found fallthrough, the config-store-unavailable 503 path, and the inactive-VK edge case — has no direct test coverage. Adding a list_models_vk_test.go (or extending governance_test.go) would keep coverage consistent with the rest of the handlers package.

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 2, 2026
Comment on lines +44 to +56
}

availableProviders := make([]schemas.ModelProvider, 0, len(vk.ProviderConfigs))
for _, providerConfig := range vk.ProviderConfigs {
provider := strings.TrimSpace(providerConfig.Provider)
if provider == "" {
continue
}
availableProviders = append(availableProviders, schemas.ModelProvider(provider))
}

bifrostCtx.SetValue(schemas.BifrostContextKeyAvailableProviders, availableProviders)
return true
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.

P1 Inactive VK not checked before applying provider scope

After retrieving the VK the function never checks vk.IsActive. Every other place that consumes a VK checks this flag before honoring it — resolver.go:261 returns DecisionVirtualKeyBlocked, and main.go:406 drops VK governance entirely when !IsActive. Without the same guard here, an inactive VK still scopes the fan-out to its provider configs, producing incorrect results (empty or restricted model list) instead of the expected behavior (treat the key as absent or return an explicit error).

Suggested change
}
availableProviders := make([]schemas.ModelProvider, 0, len(vk.ProviderConfigs))
for _, providerConfig := range vk.ProviderConfigs {
provider := strings.TrimSpace(providerConfig.Provider)
if provider == "" {
continue
}
availableProviders = append(availableProviders, schemas.ModelProvider(provider))
}
bifrostCtx.SetValue(schemas.BifrostContextKeyAvailableProviders, availableProviders)
return true
if vk == nil || !vk.IsActive {
return true
}
availableProviders := make([]schemas.ModelProvider, 0, len(vk.ProviderConfigs))
for _, providerConfig := range vk.ProviderConfigs {
provider := strings.TrimSpace(providerConfig.Provider)
if provider == "" {
continue
}
availableProviders = append(availableProviders, schemas.ModelProvider(provider))
}
bifrostCtx.SetValue(schemas.BifrostContextKeyAvailableProviders, availableProviders)
return true

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]: Listing models for a virtual key not associated with all providers adds errors to logs

1 participant