Skip to content

options.WithHTTPTransportWrapper: allow callers to inject HTTP middleware around all provider clients #3089

@simonferquel-clanker

Description

@simonferquel-clanker

Problem

When embedding docker-agent programmatically (via anthropic.NewClient, teamloader.Load, etc.), there is currently no way to inject custom HTTP middleware into the transport chain used by provider clients — neither in direct mode nor in gateway/proxy mode.

Both the Anthropic and OpenAI provider clients build their HTTP client internally:

// direct path (anthropic/client.go)
option.WithHTTPClient(httpclient.NewHTTPClient(ctx))

// gateway path (anthropic/client.go)
option.WithHTTPClient(httpclient.NewHTTPClient(ctx, httpOptions...))

The options.ModelOptions struct and config.RuntimeConfig carry no transport field, so there is no place to hook in.

Use cases

  • Bearer-token injection for a private LLM proxy: a private reverse proxy requires Authorization: Bearer <token> on every request. The token is known at client-construction time but must be injected regardless of whether the provider is in direct or gateway mode.
  • Custom observability / tracing: adding spans or metrics around every LLM HTTP call, beyond what OTel wrapping already provides.
  • Request/response logging and debugging: inspect raw HTTP traffic during development.
  • Retry/circuit-breaker policies: wrap the transport with custom retry logic specific to the deployment.

All of these share the same shape: wrap whatever transport docker-agent would build, apply some middleware, and delegate to the original.

Proposed API

Add a new Opt to the options package:

// WithHTTPTransportWrapper registers a function that wraps the HTTP transport
// used by all provider clients. The function receives the transport that
// docker-agent built (including OTel instrumentation, SSE decompression fix,
// and Desktop proxy support) and must return a new RoundTripper that delegates
// to it. The wrapper is applied in both direct mode and gateway/proxy mode.
//
// Example — inject a bearer token on every outbound LLM request:
//
//	options.WithHTTPTransportWrapper(func(base http.RoundTripper) http.RoundTripper {
//	    return &bearerTransport{token: myToken, base: base}
//	})
func WithHTTPTransportWrapper(fn func(base http.RoundTripper) http.RoundTripper) Opt {
	return func(cfg *ModelOptions) {
		cfg.transportWrapper = fn
	}
}

And surface it via ModelOptions:

func (o *ModelOptions) TransportWrapper() func(http.RoundTripper) http.RoundTripper {
	return o.transportWrapper
}

Implementation sketch (Anthropic provider, direct path)

httpClient := httpclient.NewHTTPClient(ctx)
if wrapper := globalOptions.TransportWrapper(); wrapper != nil {
	httpClient.Transport = wrapper(httpClient.Transport)
}
requestOptions := []option.RequestOption{
	option.WithHTTPClient(httpClient),
	...
}

Same two-liner applied to the gateway path. The same pattern applies symmetrically to the OpenAI provider (and any future providers that use httpclient.NewHTTPClient).

Notes

  • The wrapper function receives the already-built transport (post-OTel, post-SSE-filter, post-Desktop-proxy), so callers compose on top of docker-agent's chain rather than replacing it.
  • A single WithHTTPTransportWrapper call is the common case; stacking multiple wrappers is possible by chaining the functions before passing them in.
  • This is intentionally lower-level than WithGateway — it targets use cases where callers manage their own transport-level concerns that docker-agent has no opinion on.

Metadata

Metadata

Labels

area/apiFor features/issues/fixes related to the usage of the cagent APIarea/providersFor features/issues/fixes related to LLM providers (Bedrock, LiteLLM, Qwen, custom, etc.)area/providers/anthropicFor features/issues/fixes related to the usage of Anthropic modelsarea/providers/openaiFor features/issues/fixes related to the usage of OpenAI models
No fields configured for Enhancement.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions