Repository navigation
[FEATURE]: Support Model-Level Configuration override Provider-Level, such as baseURL and apiKey #11287
Description
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
Summary
This proposal suggests extending opencode's configuration system to support model-level baseURL and apiKey options, allowing different models within the same provider to use different API endpoints and authentication credentials.
I'd like to contribute for this issue.
Motivation
Use Case
When using custom AI providers (e.g., enterprise proxy, multi-region deployments), different models may need to route to different endpoints:
Provider: modelhub-gpt
├── gpt-4o → https://chat.internal.com/api/v1 (Chat API)
└── gemini-2.0 → https://gemini.internal.com/api/v1 (Different region)
Each endpoint may also require different API keys (e.g., different quota pools, access controls).
Current Limitation
Currently, baseURL and apiKey can only be configured at the provider level:
{
"provider": {
"my-provider": {
"options": {
"baseURL": "https://api.example.com", // ← Provider-level only
"apiKey": "xxx" // ← Provider-level only
},
"models": {
"model-a": { "options": { /* cannot override baseURL/apiKey here */ } }
}
}
}
}Workaround Limitations
Workaround 1: Multiple providers - Create separate provider entries for each endpoint.
- Problem: Configuration duplication, harder to maintain, confusing UX.
Workaround 2: Plugin-level URL rewriting - Intercept requests in custom fetch and rewrite URLs.
- Problem: Complex, error-prone URL parsing, hard to maintain, duplicates logic that should be in core.
Proposed Solution
Design Principle
Allow model.options.baseURL and model.options.apiKey to override provider-level values when initializing the SDK.
Code Change
File: packages/opencode/src/provider/provider.ts
Location: getSDK() function (lines 959-1058)
Current code (lines 966-978):
const options = { ...provider.options }
if (!options["baseURL"]) options["baseURL"] = model.api.url
if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key
if (model.headers)
options["headers"] = {
...options["headers"],
...model.headers,
}Proposed change:
const options = { ...provider.options }
// NEW: Merge model-level options (model options take precedence)
if (model.options) {
for (const [key, value] of Object.entries(model.options)) {
if (value !== undefined) {
options[key] = value
}
}
}
if (!options["baseURL"]) options["baseURL"] = model.api.url
if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key
if (model.headers)
options["headers"] = {
...options["headers"],
...model.headers,
}Configuration Example
After this change, users can configure model-level endpoints:
{
"provider": {
"modelhub-gpt": {
"name": "ModelHub GPT",
"npm": "@ai-sdk/openai-compatible",
"options": {
"baseURL": "https://default.api.com/v1",
"apiKey": "default-key"
},
"models": {
"gpt-4o": {
"id": "gpt-4o",
"name": "GPT-4o",
"options": {
"baseURL": "https://chat.api.com/v1"
}
},
"gemini-2.0-flash": {
"id": "gemini-2.0-flash",
"name": "Gemini 2.0 Flash",
"options": {
"baseURL": "https://gemini.api.com/v1",
"apiKey": "gemini-specific-key"
}
}
}
}
}
}Impact Analysis
Backward Compatibility
✅ Fully backward compatible
- If
model.optionsis empty or undefined, behavior is unchanged. - Existing configurations continue to work without modification.
- Provider-level
baseURL/apiKeyremains the default.
SDK Caching
The current SDK caching mechanism (line 980-982) uses a hash of { providerID, npm, options }:
const key = Bun.hash.xxHash32(JSON.stringify({ providerID: model.providerID, npm: model.api.npm, options }))Since model-level options are now merged into options before computing the hash, different models with different baseURL/apiKey will correctly get separate SDK instances. No additional changes needed.
Type Safety
The Model type already supports options: z.record(z.string(), z.any()) (line 572), so no schema changes are required.
Security Considerations
- API keys in model-level options follow the same security model as provider-level options.
- No new attack vectors introduced.
Alternative Approaches Considered
Alternative A: Add dedicated model.baseURL and model.apiKey fields
// In Model schema
baseURL: z.string().optional(),
apiKey: z.string().optional(),Rejected because:
- Requires schema changes
- Less flexible than generic
optionsoverride - Inconsistent with existing
optionspattern
Alternative B: Use model.api.url for baseURL override
The current code already has:
if (!options["baseURL"]) options["baseURL"] = model.api.urlRejected because:
model.api.urlserves a different semantic purpose (API metadata)- Doesn't solve the
apiKeyoverride requirement - Conflates configuration with metadata
Alternative C: Handle in plugin custom fetch
Let plugins intercept and rewrite URLs in their custom fetch handler.
Rejected because:
- Complex URL parsing and reconstruction
- Error-prone (must handle all URL patterns)
- Duplicates logic across plugins
- Core should provide clean configuration mechanism
Implementation Checklist
- Add model options merging in
getSDK()(5 lines) - Add unit tests for model-level
baseURLoverride - Add unit tests for model-level
apiKeyoverride - Add unit tests for mixed provider + model options
- Update documentation
Test Plan
describe("getSDK with model-level options", () => {
it("should use model baseURL when specified", async () => {
// Provider: baseURL = "https://provider.com"
// Model: options.baseURL = "https://model.com"
// Expected: SDK uses "https://model.com"
})
it("should use model apiKey when specified", async () => {
// Provider: apiKey = "provider-key"
// Model: options.apiKey = "model-key"
// Expected: SDK uses "model-key"
})
it("should fall back to provider options when model options not specified", async () => {
// Provider: baseURL = "https://provider.com", apiKey = "provider-key"
// Model: options = {}
// Expected: SDK uses provider values
})
it("should create separate SDK instances for models with different options", async () => {
// Model A: options.baseURL = "https://a.com"
// Model B: options.baseURL = "https://b.com"
// Expected: Different SDK instances (different cache keys)
})
})Conclusion
This proposal provides a minimal, backward-compatible change that enables model-level baseURL and apiKey configuration. The change:
- Is minimal - Only 5 lines of code added
- Is safe - Fully backward compatible, no breaking changes
- Is consistent - Follows existing
optionspattern - Is complete - Solves both
baseURLandapiKeyrequirements - Is correct - SDK caching works correctly with no additional changes
Activity
github-actions commentedon Jan 30, 2026
This issue might be a duplicate of existing issues. Please check:
The scope of issue #11287 is broader and more systematic (supporting model-level baseURL and apiKey in the options object), while these related issues tackle specific aspects of this same problem. Consider reviewing these for additional context.
Feel free to ignore if this doesn't address your specific case.
legendtkl commentedon Jan 30, 2026
hi, @thdxr do you think if this is ok? If yes, I would like to contribute a PR for this.
[-][FEATURE]: Support Model-Level baseURL and apiKey Configuration[/-][+][FEATURE]: Support Model-Level Configuration override Provider-Level, such as baseURL and apiKey[/+]github-actions commentedon Apr 1, 2026
To stay organized issues are automatically closed after 90 days of no activity. If the issue is still relevant please open a new one.