feat: add custom headers and base_url env expansion for providers#2108
feat: add custom headers and base_url env expansion for providers#2108alindsilva wants to merge 7 commits intodocker:mainfrom
Conversation
Add Headers map[string]string to ProviderConfig, allowing custom HTTP
headers on provider definitions. Headers flow through ProviderOpts to
the OpenAI client with env var expansion (${VAR_NAME} syntax).
Includes:
- ProviderConfig.Headers field in config schema (v3 and latest)
- Headers wiring in applyProviderDefaults
- OpenAI client: headers parsing, env expansion, auth middleware
for custom providers without token_key
- Schema normalization (normalizeUnionTypes) for gateway compatibility
- Handle both map[string]string and map[interface{}]interface{} YAML types
The filter, instructions, and toon toolset wrappers were not forwarding Start() and Stop() calls to their inner toolsets. This caused MCP tools to fail with 'toolset not started' errors in multi-agent configurations.
- Convert anyOf patterns like {anyOf: [{type:string},{type:null}]} to
{type:string} for compatibility with AI gateways (e.g. Cloudflare)
that don't support anyOf in tool parameter schemas.
- Log HTTP response body on non-2xx API errors for easier debugging.
Add custom headers support and ${VAR_NAME} expansion in base_url to the
Gemini and Anthropic provider clients, matching the existing OpenAI
client capability. Also add Headers field directly to ModelConfig for
convenience (no separate providers section needed).
- Gemini: read headers from ProviderOpts, expand env vars, set on
genai.HTTPOptions; expand env vars in base_url
- Anthropic: same pattern with option.WithHeader; expand env vars
in base_url
- ModelConfig.Headers: new field merged into ProviderOpts['headers']
with model-level taking precedence over provider-level
- Updated JSON schema and config types (v3 + latest)
…_url env expansion for providers * feature/provider-custom-headers-pr: feat: add custom headers and base_url env expansion to all providers fix: normalize anyOf schemas and add API error response body logging fix: forward Start/Stop to inner toolsets in teamloader wrappers feat: add custom headers support for provider configs
There was a problem hiding this comment.
Pull request overview
This PR expands provider/model configurability and robustness across the runtime by adding configurable HTTP headers, environment-variable expansion for certain endpoints, schema normalization for stricter gateways, and lifecycle forwarding for wrapped toolsets.
Changes:
- Add
headersfields to provider/model config (plus JSON schema + example) and wire them through provider defaults into per-provider HTTP client initialization (with env expansion in header values). - Fix multi-agent “toolset not started” issues by forwarding
Start()/Stop()through toolset wrapper types and adding tests. - Normalize union/
anyOftool schemas for gateway compatibility and add additional API error detail logging for streaming failures.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/teamloader/toon.go | Forwards Start/Stop to inner toolset when supported. |
| pkg/teamloader/toon_test.go | Adds tests validating Start/Stop forwarding behavior for Toon wrapper. |
| pkg/teamloader/instructions.go | Forwards Start/Stop to inner toolset for instructions wrapper. |
| pkg/teamloader/instructions_test.go | Adds tests for Start/Stop forwarding behavior in instructions wrapper. |
| pkg/teamloader/filter.go | Forwards Start/Stop to inner toolset for filtered toolsets. |
| pkg/teamloader/filter_test.go | Adds tests for Start/Stop forwarding and startable integration chaining. |
| pkg/runtime/streaming.go | Adds debug logging for OpenAI SDK API error response dumps in streaming loop. |
| pkg/model/provider/provider.go | Wires provider/model headers into ProviderOpts["headers"] with model-level precedence. |
| pkg/model/provider/custom_headers_test.go | Adds tests covering provider/model header wiring into ProviderOpts. |
| pkg/model/provider/openai/client.go | Adds support for custom headers (with env expansion) and changes auth handling for custom providers without token_key. |
| pkg/model/provider/openai/api_type_test.go | Updates expectation to reflect stripping Authorization when no token_key is set. |
| pkg/model/provider/gemini/client.go | Adds base_url env expansion and custom headers via genai.HTTPOptions. |
| pkg/model/provider/anthropic/client.go | Adds base_url env expansion and custom headers via request options. |
| pkg/model/provider/openai/schema.go | Adds normalizeUnionTypes() into schema conversion pipeline. |
| pkg/model/provider/openai/schema_test.go | Adds tests for schema normalization of anyOf optional patterns. |
| pkg/model/provider/schema_test.go | Updates expected schema output to match normalized union handling. |
| pkg/config/latest/types.go | Adds headers to provider/model config types. |
| pkg/config/v3/types.go | Adds headers to provider/model config types (older version). |
| pkg/config/gather.go | Gathers env vars referenced in headers (and provider base_url) for missing-env checks. |
| agent-schema.json | Extends JSON schema with headers fields for provider/model. |
| examples/custom_provider.yaml | Updates example to demonstrate Cloudflare gateway headers usage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
… feedback - Add Headers field to ModelConfig and ProviderConfig in v4, v5, v6 config types so headers survive the JSON upgrade chain from v3 to latest. This was causing Cloudflare AI Gateway 401 errors because custom headers were silently dropped at the v3→v4 boundary. - Address Copilot review comments on PR docker#2108: 1. Remove response body from streaming error logs to avoid leaking sensitive data (pkg/runtime/streaming.go) 2. Deep-copy provider headers map before merging to avoid mutating shared config across models (pkg/model/provider/provider.go) 3. Gather env vars from model-level base_url in addition to provider base_url (pkg/config/gather.go) 4. Expand env vars in OpenAI/Azure base_url consistently with Anthropic/Gemini (pkg/model/provider/openai/client.go) 5. Redact header values from error logs to prevent credential leaks (pkg/model/provider/openai/client.go) 6. Tighten union type normalization to only collapse nullable patterns (exactly 2 options with one being null), preserving non-nullable unions (pkg/model/provider/openai/schema.go)
Bug fix: Headers dropped during config upgrade chainThe original PR added Since configs are migrated through the chain v3 → v4 → v5 → v6 → latest via Fix (8d15048): Added |
- Fix import ordering (gci) in custom_headers_test.go and openai/client.go - Replace if-else-if chains with switch statements (gocritic) - Use maps.Copy instead of manual loops (modernize) - Fix nil return in normalizeUnionTypes (gocritic) - Replace else-if with else if in custom_headers_test.go (gocritic) - Use assert.Empty instead of assert.Equal with empty string (testifylint) - Remove extra blank line (gofmt)
Summary
Add support for custom HTTP headers on provider and model configurations, enabling use cases like API gateways (e.g., Cloudflare AI Gateway) that require additional authentication or routing headers.
Changes
1. Custom headers in config (
ee35e1a9)headersfield (map[string]string) to bothProviderConfigandModelConfigin the config schemaProviderOpts["headers"]inapplyProviderDefaults()agent-schema.json)2. Forward Start/Stop to inner toolsets (
19147d98)Start()/Stop()forwarding toFilteredToolset,InstructionsToolset, andToonToolsetwrappers3. Normalize anyOf schemas + API error logging (
14eb42df)normalizeUnionTypes()to collapse single-elementanyOf/oneOfarrays (required by some providers)ConvertParametersToSchemapipeline4. Headers + base_url env expansion on all providers (
11167d78)ProviderOpts, expand${VAR}env vars, auth middleware for custom providersgenai.HTTPOptions, base_url${VAR}expansionoption.WithHeader(), base_url${VAR}expansionGatherEnvVarsForModels()so tests auto-set dummy valuesexamples/custom_provider.yaml)Example usage
Testing
TestLoadExamplespasses with the newcustom_provider.yamlexample