Skip to content

Conversation

@ssncferreira
Copy link
Contributor

@ssncferreira ssncferreira commented Dec 19, 2025

Description

Implements request routing for the AI Bridge Proxy. After MITM decryption, requests to known AI providers (Anthropic, OpenAI) are rewritten to the corresponding aibridged endpoint, while requests to unknown hosts are passed through to their original destination.

Changes

  • Add CoderAccessURL configuration option for specifying the Coder deployment URL
  • Add handleRequest to route decrypted requests based on target host
  • Route known AI providers (Anthropic and OpenAI) to AI Bridge specific endpoint.
  • Passthrough requests to unknown hosts directly to their original destination
  • Inject Coder session token (from feat: add proxy authorization to aibridgeproxyd #21342) as Authorization: Bearer header for aibridged
  • Add tests for routing and passthrough behavior

Depends on: #21342
Closes: coder/internal#1181

Copy link
Contributor Author

ssncferreira commented Dec 19, 2025

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.


// canonicalHost strips the port from a host:port string and lowercases it.
func canonicalHost(host string) string {
if i := strings.IndexByte(host, ':'); i != -1 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Were you aware of net.SplitHostPort but chose this approach for some reason?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used the same approach as the POC. We can use net.SplitHostPort, but it errors when there's no port in the address. After MITM decryption, req.Host is the HTTP Host header, which omits the port when using default ports (443 for HTTPS, 80 for HTTP), so api.anthropic.com instead of api.anthropic.com:443.

I've updated providerFromURL to receive a url.URL and use url.Hostname(), which handles both cases correctly.

Addressed in: 5e6a35c

// Check if this request is for a supported AI provider.
provider := providerFromHost(req.Host)
if provider == "" {
s.logger.Debug(s.ctx, "passthrough request to unknown host",
Copy link
Contributor

Choose a reason for hiding this comment

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

This shouldn't really happen, right? Since we only MITM allowlisted domains?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's correct, but right now we don't have the allowlist in place yet. Currently, the proxy always MITMs all requests, so unknown hosts reach handleRequest and get passed through after decryption.

Once coder/internal#1182 is implemented, unknown hosts will be tunneled (not decrypted), so they won't reach handleRequest and this case should never happen. At that point, we should probably update this to an error. I added a TODO comment: 82ddf4d

Comment on lines +338 to +505
targetPath: "/v1/messages",
expectedPath: "/api/v2/aibridge/anthropic/v1/messages",
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you also please include a case for known a provider whose route is neither bridged nor passthru?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you mean? 🤔 A route to a known provider is always bridged from the proxy to aibridge.
Do you mean a route that is not configured in aibridge as either a bridged route or a passthrough route? (Examples: bridged, passthrough). We could test that, but that's aibridge's responsibility to handle, not the proxy's. In these tests we're using a mock aibridge that just returns 200 OK.

@ssncferreira ssncferreira force-pushed the ssncferreira/feat-aiproxy-routing branch 2 times, most recently from bc4caed to 97198d5 Compare December 23, 2025 13:11
@ssncferreira ssncferreira force-pushed the ssncferreira/feat-aiproxy-proxy-authorization branch from 88a304c to 56e1c5c Compare December 23, 2025 13:26
@ssncferreira ssncferreira force-pushed the ssncferreira/feat-aiproxy-routing branch 4 times, most recently from b82c306 to 5e6a35c Compare December 23, 2025 20:07
@ssncferreira ssncferreira force-pushed the ssncferreira/feat-aiproxy-proxy-authorization branch from 4157564 to ac0129f Compare December 23, 2025 21:49
@ssncferreira ssncferreira force-pushed the ssncferreira/feat-aiproxy-routing branch from 5a1d2dc to 4196397 Compare December 23, 2025 21:49
@ssncferreira ssncferreira force-pushed the ssncferreira/feat-aiproxy-proxy-authorization branch from ac0129f to ae82ddd Compare December 23, 2025 21:52
@ssncferreira ssncferreira force-pushed the ssncferreira/feat-aiproxy-routing branch 3 times, most recently from 644e954 to 4654e31 Compare December 23, 2025 22:21
@ssncferreira ssncferreira force-pushed the ssncferreira/feat-aiproxy-proxy-authorization branch from 176e7e7 to 097fbc9 Compare December 23, 2025 22:21
@ssncferreira ssncferreira force-pushed the ssncferreira/feat-aiproxy-proxy-authorization branch from 097fbc9 to 7145548 Compare December 23, 2025 22:43
@ssncferreira ssncferreira force-pushed the ssncferreira/feat-aiproxy-routing branch from 4654e31 to e0906db Compare December 23, 2025 22:43
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