Skip to content

feat(providers): auto-discover suffixed env providers#259

Merged
SantiagoDePolonia merged 2 commits into
mainfrom
feature/prov-env-variables-autodiscovery
Apr 22, 2026
Merged

feat(providers): auto-discover suffixed env providers#259
SantiagoDePolonia merged 2 commits into
mainfrom
feature/prov-env-variables-autodiscovery

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • auto-discovers suffixed provider env groups like OPENAI_EAST_API_KEY as provider name openai-east with type openai
  • supports suffixed BASE_URL, MODELS, and Azure API_VERSION across registered provider types
  • updates docs and .env template for env-only multi-provider setup

Tests

  • go test ./...
  • commit hooks: go import/fmt, make test-race, go mod tidy, hot-path performance guard, make lint

Summary by CodeRabbit

  • New Features

    • Added support for registering multiple provider instances of the same type using suffixed environment variables (e.g., OPENAI_EAST_API_KEY creates a provider named openai-east), reducing the need for configuration files.
  • Documentation

    • Updated configuration guides to explain how to set up multiple provider instances via environment variables and clarified when configuration files are necessary.
  • Tests

    • Added comprehensive test coverage for suffixed environment variable discovery and configuration overlay behavior.

@mintlify

mintlify Bot commented Apr 22, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
gomodel 🟢 Ready View Preview Apr 22, 2026, 6:35 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@SantiagoDePolonia has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 49 minutes and 35 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 49 minutes and 35 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d7777a6b-e2e9-46ca-b977-e471f3898d1c

📥 Commits

Reviewing files that changed from the base of the PR and between 28eca92 and 4871965.

📒 Files selected for processing (1)
  • internal/providers/config.go
📝 Walkthrough

Walkthrough

This PR adds support for registering multiple instances of the same provider type via suffixed environment variables (e.g., OPENAI_EAST_API_KEY creates a provider named openai-east). The implementation includes environment variable collection and grouping logic with two-phase overlay (unsuffixed and suffixed), updated documentation explaining the new capability, and comprehensive test coverage.

Changes

Cohort / File(s) Summary
Configuration Templates & README
.env.template, README.md
Added documentation describing suffixed environment variable registration pattern (<PROVIDER>_<SUFFIX>_*) for multiple provider instances.
Advanced Configuration Documentation
docs/advanced/config-yaml.mdx, docs/advanced/configuration.mdx
Updated guidance for when to use config.yaml, expanded environment-variable coverage with specific suffix patterns and normalization rules (underscores to hyphens), and revised provider-specific notes (Oracle, Azure) for suffixed env var usage.
Provider Configuration Logic
internal/providers/config.go
Replaced single-pass unsuffixed env var lookup with new collection/grouping mechanism scanning for all provider-related keys. Implemented two-phase overlay: unsuffixed values first, then suffixed values mapping each suffix to derived provider names (e.g., openai-east). Includes conditional API version parsing and selective field overwrites.
Configuration Tests
internal/providers/config_test.go
Added comprehensive test cases validating suffixed env var discovery across all provider types, suffix-to-provider-name mapping normalization, API version support (Azure), model parsing (Oracle), and overlay behavior preserving existing YAML resilience settings.

Sequence Diagram

sequenceDiagram
    participant Env as Environment<br/>(os.Environ)
    participant Parser as Provider<br/>Config Parser
    participant Grouper as Env Var<br/>Grouper
    participant Overlay as Overlay<br/>Engine
    participant Registry as Provider<br/>Registry

    Env->>Parser: Raw env vars
    Parser->>Grouper: Scan for prefix match<br/>(e.g., OPENAI_*)
    Grouper->>Grouper: Group by suffix<br/>(unsuffixed, east, west)
    Grouper->>Overlay: Grouped env values<br/>by field type
    
    Overlay->>Overlay: Phase 1: Apply<br/>unsuffixed vars
    Overlay->>Overlay: Phase 2: Apply<br/>suffixed vars<br/>(normalize suffix<br/>to name)
    Overlay->>Overlay: Selective overwrite<br/>+ validation<br/>(RequireBaseURL, etc)
    
    Overlay->>Registry: Provider instances<br/>(openai, openai-east,<br/>openai-west, ...)
    Registry->>Registry: Resolve & cache
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • feat(provider): add oracle provider #170: Both PRs modify provider environment-variable discovery and auto-registration logic in internal/providers/config.go, including environment-variable parsing and grouping mechanisms for provider instance registration.

Poem

🐰 A rabbit hops through suffixed fields,
Where OPENAI_EAST_* gently yields
New instances, each with name so bright,
Hyphens dancing, normalization's right!
Multi-providers, no YAML in sight! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.70% 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 'feat(providers): auto-discover suffixed env providers' directly and precisely summarizes the main change—adding auto-discovery of suffixed provider environment variables to support multi-provider configurations.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/prov-env-variables-autodiscovery

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 and usage tips.

@codecov-commenter

codecov-commenter commented Apr 22, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 80.79096% with 34 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/providers/config.go 80.79% 25 Missing and 9 partials ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Apr 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR extends the provider env-var discovery system to auto-register additional provider instances from suffixed environment variables (e.g. OPENAI_EAST_API_KEY + OPENAI_EAST_BASE_URL → provider openai-east, type openai), removing the need for config.yaml in common multi-provider setups.

Key changes:

  • Replaces per-provider os.Getenv calls in applyProviderEnvVars with a full os.Environ() scan in collectProviderEnvValues, grouping env vars by provider type and suffix before applying them.
  • Introduces parseProviderEnvKey with a right-anchored field match and validProviderEnvSuffix for input validation.
  • applySuffixedProviderEnvVars creates or overlays <type>-<suffix> providers, respecting RequireBaseURL and type-safety guards.
  • Suffixed env vars overlay matching YAML-defined providers (preserving their resilience config), consistent with existing unsuffixed overlay semantics.
  • Six new tests cover the primary scenarios; docs and .env.template are updated.

Minor concerns (non-blocking):

  • os.Environ() is called once per registered provider type in the loop; a one-line snapshot outside the loop would eliminate redundant copies.
  • The right-anchored greedy field matching in parseProviderEnvKey is correct but deserves a brief comment explaining the intentional ordering.
  • No test covers the coexistence of unsuffixed and suffixed env vars for the same provider type simultaneously.

Confidence Score: 4/5

Safe to merge; all primary paths are tested and the logic is correct — remaining notes are non-blocking P2 style improvements.

The implementation is well-structured and handles all critical edge cases (RequireBaseURL, YAML overlay, type-safety guards, Azure API_VERSION). Tests are comprehensive for the intended scenarios. The two non-blocking concerns (os.Environ() allocation in a loop, missing coexistence test) do not affect correctness.

internal/providers/config.go (os.Environ() per-type allocation) and internal/providers/config_test.go (coexistence test gap)

Important Files Changed

Filename Overview
internal/providers/config.go Core env-discovery refactor: introduces collectProviderEnvValues, parseProviderEnvKey, applySuffixedProviderEnvVars and helpers; logic is correct with minor performance and documentation concerns.
internal/providers/config_test.go Adds 6 new test cases covering suffixed provider discovery; coexistence of unsuffixed and suffixed providers for the same type is not tested.
.env.template Adds a brief comment block documenting the suffixed multi-instance env var pattern.
README.md Adds a short paragraph explaining how suffixed env vars register additional provider instances without config.yaml.
docs/advanced/config-yaml.mdx Updates when to prefer YAML over env vars and documents the suffixed env var pattern.
docs/advanced/configuration.mdx Expands provider configuration section with shell examples for suffixed env vars and updates when YAML is still preferred.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant applyProviderEnvVars
    participant collectProviderEnvValues
    participant applyUnsuffixed
    participant applySuffixed

    Caller->>applyProviderEnvVars: raw YAML map + discovery config
    applyProviderEnvVars->>applyProviderEnvVars: copy raw into result
    loop for each sorted provider type
        applyProviderEnvVars->>collectProviderEnvValues: providerType, spec
        collectProviderEnvValues->>collectProviderEnvValues: scan os.Environ, group entries
        collectProviderEnvValues-->>applyProviderEnvVars: grouped env values
        opt base provider group present
            applyProviderEnvVars->>applyUnsuffixed: result, providerType, spec, values
            applyUnsuffixed->>applyUnsuffixed: findEnvOverlayTarget or create entry
            applyUnsuffixed-->>applyProviderEnvVars: result updated
        end
        loop for each named instance
            applyProviderEnvVars->>applySuffixed: result, providerType, spec, name, values
            applySuffixed->>applySuffixed: derive target name, overlay or create entry
            applySuffixed-->>applyProviderEnvVars: result updated
        end
    end
    applyProviderEnvVars-->>Caller: merged result map
Loading

Comments Outside Diff (1)

  1. internal/providers/config_test.go, line 402-484 (link)

    P2 Missing test: coexistence of unsuffixed and suffixed env vars for the same provider type

    The test suite covers purely-suffixed providers and purely-unsuffixed providers separately, but there is no test for the case where both are set simultaneously — for example, OPENAI_API_KEY plus OPENAI_EAST_API_KEY. The expected outcome would be two separate entries in the resolved map (openai and openai-east). This is especially important to verify because findEnvOverlayTarget in applyUnsuffixedProviderEnvVars scans the result map, and suffixed entries are added to that same map in the same call. Without a test, it's easier to miss a regression where the unsuffixed env var accidentally targets a suffixed provider (or vice versa).

Reviews (1): Last reviewed commit: "feat(providers): auto-discover suffixed ..." | Re-trigger Greptile

Comment thread internal/providers/config.go Outdated
Comment on lines +83 to +120
func collectProviderEnvValues(providerType string, spec DiscoveryConfig) map[string]providerEnvValues {
groups := make(map[string]providerEnvValues)
prefix := envPrefix(providerType)
prefixWithSeparator := prefix + "_"

for _, entry := range os.Environ() {
key, value, ok := strings.Cut(entry, "=")
if !ok || value == "" || !strings.HasPrefix(key, prefixWithSeparator) {
continue
}

targetKey, matched, ambiguous := findEnvOverlayTarget(result, providerType)
if matched {
existing := result[targetKey]
if apiKey != "" {
existing.APIKey = apiKey
}
if explicitBaseURL != "" {
existing.BaseURL = baseURL
} else if normalizeResolvedBaseURL(existing.BaseURL) == "" && apiKey != "" && spec.DefaultBaseURL != "" {
existing.BaseURL = spec.DefaultBaseURL
}
if apiVersion != "" {
existing.APIVersion = apiVersion
}
if len(models) > 0 {
existing.Models = models
}
result[targetKey] = existing
} else if ambiguous {
suffix, field, ok := parseProviderEnvKey(prefix, key, spec)
if !ok {
continue
} else {
if spec.RequireBaseURL && explicitBaseURL == "" {
continue
}
result[providerType] = config.RawProviderConfig{
Type: providerType,
APIKey: apiKey,
BaseURL: baseURL,
APIVersion: apiVersion,
Models: models,
}
}

values := groups[suffix]
switch field {
case providerEnvFieldAPIKey:
values.APIKey = value
case providerEnvFieldBaseURL:
values.BaseURL = normalizeResolvedBaseURL(value)
case providerEnvFieldAPIVersion:
values.APIVersion = value
case providerEnvFieldModels:
values.Models = parseCSVEnvList(value)
}
groups[suffix] = values
}

return result
for suffix, values := range groups {
if values.empty() {
delete(groups, suffix)
}
}

return groups
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 os.Environ() called once per provider type in a hot loop

collectProviderEnvValues is invoked inside the applyProviderEnvVars loop that iterates over every registered provider type. Each call to os.Environ() in Go allocates and copies the entire process environment (a slice of strings). With N registered provider types and M env vars, this is O(N × M) allocations.

A simple fix is to snapshot os.Environ() once in applyProviderEnvVars and pass it down:

func applyProviderEnvVars(raw map[string]config.RawProviderConfig, discovery map[string]DiscoveryConfig) map[string]config.RawProviderConfig {
    result := make(map[string]config.RawProviderConfig, len(raw))
    maps.Copy(result, raw)

    environ := os.Environ() // snapshot once

    for _, providerType := range sortedDiscoveryTypes(discovery) {
        spec := discovery[providerType]
        envGroups := collectProviderEnvValues(providerType, spec, environ)
        // ...
    }
    return result
}

For typical deployments (~10 provider types) the impact is small, but the fix is trivial and avoids N redundant environment copies.

Comment on lines +122 to +151
func parseProviderEnvKey(prefix, key string, spec DiscoveryConfig) (string, providerEnvField, bool) {
rest, ok := strings.CutPrefix(key, prefix+"_")
if !ok {
return "", 0, false
}

fields := []struct {
name string
field providerEnvField
}{
{name: "API_VERSION", field: providerEnvFieldAPIVersion},
{name: "BASE_URL", field: providerEnvFieldBaseURL},
{name: "API_KEY", field: providerEnvFieldAPIKey},
{name: "MODELS", field: providerEnvFieldModels},
}

for _, candidate := range fields {
if candidate.field == providerEnvFieldAPIVersion && !spec.SupportsAPIVersion {
continue
}
if rest == candidate.name {
return "", candidate.field, true
}
suffix, found := strings.CutSuffix(rest, "_"+candidate.name)
if found && validProviderEnvSuffix(suffix) {
return suffix, candidate.field, true
}
}

return "", 0, false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Greedy right-anchor match may produce surprising suffix for ambiguous field names

parseProviderEnvKey checks fields in order (API_VERSION, BASE_URL, API_KEY, MODELS) and applies strings.CutSuffix(rest, "_"+candidate.name). Because the match is greedy from the right, a key like OPENAI_EAST_MODELS_API_KEY yields suffix EAST_MODELS (field API_KEY) rather than being rejected as unrecognised.

This is correct and safe in practice since no sane env var is named that way, but a short comment near the field list explaining the intentional right-anchored greedy match and ordering would help future maintainers avoid accidentally reordering the candidates list and breaking disambiguation.

@SantiagoDePolonia SantiagoDePolonia merged commit d6b76a2 into main Apr 22, 2026
17 checks passed
@SantiagoDePolonia SantiagoDePolonia deleted the feature/prov-env-variables-autodiscovery branch June 11, 2026 09:17
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