Skip to content

feat: add Type() to Provider interface and configurable instance Name#237

Merged
ssncferreira merged 1 commit intomainfrom
ssncf/feat-provider-type-name
Mar 31, 2026
Merged

feat: add Type() to Provider interface and configurable instance Name#237
ssncferreira merged 1 commit intomainfrom
ssncf/feat-provider-type-name

Conversation

@ssncferreira
Copy link
Copy Markdown
Contributor

@ssncferreira ssncferreira commented Mar 30, 2026

Problem

There is no way to register multiple provider instances of the same type with distinct identities. For example, introducing a copilot-business provider (targeting api.business.githubcopilot.com) alongside the default copilot provider (targeting api.individual.githubcopilot.com) would result in both sharing the same name, making them indistinguishable in metrics, logs, circuit breaker, API dump, database records, and the coder aibridge HTTP route prefix.

Changes

  • Add Type() to Provider interface returning the provider type ("anthropic", "openai", "copilot")
  • Add configurable Name field to provider configs, defaulting to the type when not set
  • Name() now returns the instance name, used for metrics, logs, circuit breaker, routing, and the coder aibridge endpoint (RoutePrefix() is derived from Name())
  • Type() is used for the database Provider field
  • Add ProviderName field to InterceptionRecord for the instance name

Related to: #152

Disclaimer: initially produced by Claude Opus 4.6, heavily modified and reviewed by @ssncferreira .

Copy link
Copy Markdown
Contributor Author

ssncferreira commented Mar 30, 2026

Comment on lines +201 to +202
Provider: p.Type(),
ProviderName: p.Name(),
Copy link
Copy Markdown
Contributor Author

@ssncferreira ssncferreira Mar 30, 2026

Choose a reason for hiding this comment

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

Keeping the provider type ("anthropic", "openai", "copilot") so interceptions preserve this value and we are still easily able to aggregate by provider. The ProviderName field provides the specific instance identity. Alternatively, we could store the upstream URL instead.

Examples:

  • Anthropic: Provider: anthropic, ProviderName: anthropic
  • Copilot: Provider: copilot, ProviderName: copilot
  • Copilot Business: Provider: copilot, ProviderName: copilot-business
  • Copilot Enterprise: Provider: copilot, ProviderName: copilot-enterprise

@ssncferreira ssncferreira marked this pull request as ready for review March 30, 2026 18:29
config/config.go Outdated
)

type Anthropic struct {
// Name is the provider instance name. If empty, defaults to ProviderAnthropic.
Copy link
Copy Markdown
Contributor

@pawbana pawbana Mar 30, 2026

Choose a reason for hiding this comment

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

nit: comment is a bit confusing, defaults to "ProviderAnthropic" or value of ProviderAnthropic variable?

Maybe functions Name()and Type() with logic regarding defaults should be in config package and provider would simply call them? Then it would be simpler to understand the comment.

Same confusion in comments for other providers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated the comment to reference the name "anthropic", "openai", "copilot".
On moving the defaulting logic to config: I'd prefer to keep it in the provider constructors for consistency, that's the same pattern used for the other methods like BaseURL, Key, APIDumpDir, and CircuitBreaker.

Copy link
Copy Markdown
Contributor

pawbana commented Mar 30, 2026

RoutePrefix() is derived from Name()

I'm not sure how I fell about this. If Name is configurable I think route prefix should also be configurable but maybe default could be based on Nameto simplify configuration / UX.

If RoutePrefix would be configurable it needs good explanation how it works. Due to how Base URL behaves differently between OpenAI and Anthropic.
I've already tried to explain it here:

// Provider defines routes (bridged and passed through) for given provider.
// Bridged routes are processed by dedicated interceptors.
//
// All routes have following pattern:
// - https://coder.host.com/api/v2 + /aibridge + /{provider.RoutePrefix()} + /{bridged or passthrough route}
// {host} {aibridge root} {provider prefix} {provider route}
//
// {host} + {aibridge root} + {provider prefix} form the base URL used in tools/clients using AI Bridge (eg. Claude/Codex).
//
// When request is bridged, interceptor created based on route processes the request.
// When request is passed through the {host} + {aibridge root} + {provider prefix} URL part
// is replaced by provider's base URL and request is forwarded.
// This mirrors behaviour in bridged routes and SDKs used by interceptors.
//
// Example:
//
// - OpenAI chat completions
// AI Bridge base URL (set in Codex): "https://host.coder.com/api/v2/aibridge/openai/v1"
// Upstream base URl (set in coder config): http://api.openai.com/v1
// Request: Codex -> https://host.coder.com/api/v2/aibridge/openai/v1/chat/completions -> AI Bridge -> http://api.openai.com/v1/chat/completions
// url change: 'https://host.coder.com/api/v2/aibridge/openai/v1' -> 'http://api.openai.com/v1' | '/chat/completions' suffix remains the same
//
// - Anthropic messages
// AI Bridge base URL (set in Codex): "https://host.coder.com/api/v2/aibridge/anthropic"
// Upstream base URl (set in coder config): http://api.anthropic.com
// Request: Codex -> https://host.coder.com/api/v2/aibridge/anthropic/v1/messages -> AI Bridge -> http://api.anthropic.com/v1/messages
// url change: 'https://host.coder.com/api/v2/aibridge/anthropic' -> 'http://api.anthropic.com' | '/v1/messages' suffix remains the same
//
// !Note!
// OpenAI and Anthropic use different route patterns.
// OpenAI includes the version '/v1' in the base url while Anthropic does not.
// More details/examples: https://github.com/coder/aibridge/pull/174#discussion_r2782320152

and here is comment showing differences from PR: #174 (comment)

@ssncferreira ssncferreira force-pushed the ssncf/feat-provider-type-name branch from 2c8fad4 to 85648f5 Compare March 31, 2026 10:34
@ssncferreira
Copy link
Copy Markdown
Contributor Author

RoutePrefix() is derived from Name()

I'm not sure how I fell about this. If Name is configurable I think route prefix should also be configurable but maybe default could be based on Nameto simplify configuration / UX.

If RoutePrefix would be configurable it needs good explanation how it works. Due to how Base URL behaves differently between OpenAI and Anthropic. I've already tried to explain it here:

// Provider defines routes (bridged and passed through) for given provider.
// Bridged routes are processed by dedicated interceptors.
//
// All routes have following pattern:
// - https://coder.host.com/api/v2 + /aibridge + /{provider.RoutePrefix()} + /{bridged or passthrough route}
// {host} {aibridge root} {provider prefix} {provider route}
//
// {host} + {aibridge root} + {provider prefix} form the base URL used in tools/clients using AI Bridge (eg. Claude/Codex).
//
// When request is bridged, interceptor created based on route processes the request.
// When request is passed through the {host} + {aibridge root} + {provider prefix} URL part
// is replaced by provider's base URL and request is forwarded.
// This mirrors behaviour in bridged routes and SDKs used by interceptors.
//
// Example:
//
// - OpenAI chat completions
// AI Bridge base URL (set in Codex): "https://host.coder.com/api/v2/aibridge/openai/v1"
// Upstream base URl (set in coder config): http://api.openai.com/v1
// Request: Codex -> https://host.coder.com/api/v2/aibridge/openai/v1/chat/completions -> AI Bridge -> http://api.openai.com/v1/chat/completions
// url change: 'https://host.coder.com/api/v2/aibridge/openai/v1' -> 'http://api.openai.com/v1' | '/chat/completions' suffix remains the same
//
// - Anthropic messages
// AI Bridge base URL (set in Codex): "https://host.coder.com/api/v2/aibridge/anthropic"
// Upstream base URl (set in coder config): http://api.anthropic.com
// Request: Codex -> https://host.coder.com/api/v2/aibridge/anthropic/v1/messages -> AI Bridge -> http://api.anthropic.com/v1/messages
// url change: 'https://host.coder.com/api/v2/aibridge/anthropic' -> 'http://api.anthropic.com' | '/v1/messages' suffix remains the same
//
// !Note!
// OpenAI and Anthropic use different route patterns.
// OpenAI includes the version '/v1' in the base url while Anthropic does not.
// More details/examples: https://github.com/coder/aibridge/pull/174#discussion_r2782320152

and here is comment showing differences from PR: #174 (comment)

RoutePrefix is derived from Name, same as before, the difference is that Name is now configurable. This is intentional: the route prefix is what distinguishes provider instances of the same type:

  • https://coder.host.com/api/v2/aibridge/copilot/{route}
  • https://coder.host.com/api/v2/aibridge/copilot-business/{route}

I don't think RoutePrefix needs its own config field right now, if someone sets Name: "copilot-business", the route prefix /copilot-business is the obvious correct default (we can add validation to the name to avoid additional / if needed). We can always add an explicit RoutePrefix override later if a use case comes up where the name and route need to diverge.

Your comment about OpenAI vs Anthropic base URL differences still applies: that behavior is unchanged since each provider's RoutePrefix() implementation handles it (e.g. OpenAI appends /v1).

@ssncferreira ssncferreira requested a review from pawbana March 31, 2026 10:43
}

func (p *Anthropic) Name() string {
return p.cfg.Name
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.

Since Name defines route prefix does it need to be checked/sanitized?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, it should. I can add it in validateProviders on the upstack PR: #240

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added here: 7d0c198

Copy link
Copy Markdown
Contributor Author

ssncferreira commented Mar 31, 2026

Merge activity

  • Mar 31, 2:32 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Mar 31, 2:33 PM UTC: Graphite rebased this pull request as part of a merge.
  • Mar 31, 2:33 PM UTC: @ssncferreira merged this pull request with Graphite.

@ssncferreira ssncferreira force-pushed the ssncf/feat-provider-type-name branch from 85648f5 to f44164f Compare March 31, 2026 14:32
@ssncferreira ssncferreira merged commit cd592d0 into main Mar 31, 2026
4 checks passed
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.

3 participants