diff options
Diffstat (limited to 'libpanto/src/provider_anthropic_messages.zig')
| -rw-r--r-- | libpanto/src/provider_anthropic_messages.zig | 56 |
1 files changed, 49 insertions, 7 deletions
diff --git a/libpanto/src/provider_anthropic_messages.zig b/libpanto/src/provider_anthropic_messages.zig index 95013d1..5721e4a 100644 --- a/libpanto/src/provider_anthropic_messages.zig +++ b/libpanto/src/provider_anthropic_messages.zig @@ -85,16 +85,34 @@ pub const AnthropicMessagesRequest = struct { defer self.allocator.free(body); std.log.debug("anthropic_messages => {s}", .{body}); - // Build headers. The four base headers are always present; the - // interleaved-thinking beta header is added only when the config - // explicitly requests manual extended thinking with interleaving. - // It is intentionally NOT sent for `.adaptive` (interleaving is - // automatic there and the header causes 400s on some backends) or - // `.disabled`. + // Build headers. Standard Anthropic-compatible backends use + // `x-api-key`. OAuth-backed Anthropic-compatible providers (e.g. + // Copilot) opt into `Authorization: Bearer ...` via + // `config.use_bearer_auth`, which is derived from the configured auth + // family rather than guessed from the URL/headers. The four base + // headers are always present; the interleaved-thinking beta header is + // added only when the config explicitly requests manual extended + // thinking with interleaving. It is intentionally NOT sent for + // `.adaptive` (interleaving is automatic there and the header causes + // 400s on some backends) or `.disabled`. + const use_bearer_auth = self.config.use_bearer_auth; + const auth_value = if (use_bearer_auth) + try std.fmt.allocPrint( + self.allocator, + "Bearer {s}", + .{self.config.api_key}, + ) + else + ""; + defer if (use_bearer_auth) self.allocator.free(auth_value); + const auth_header: http.Header = if (use_bearer_auth) + .{ .name = "authorization", .value = auth_value } + else + .{ .name = "x-api-key", .value = self.config.api_key }; var headers_buf: [5]http.Header = .{ .{ .name = "content-type", .value = "application/json" }, .{ .name = "accept", .value = "text/event-stream" }, - .{ .name = "x-api-key", .value = self.config.api_key }, + auth_header, .{ .name = "anthropic-version", .value = self.config.api_version }, undefined, // slot reserved for the optional beta header }; @@ -1226,6 +1244,11 @@ test "two streamed turns persist assistant replies in the conversation" { ); } +/// Helper: whether this config sends Bearer auth instead of `x-api-key`. +fn headerSliceUsesBearerAuth(cfg: *const config_mod.AnthropicMessagesConfig) bool { + return cfg.use_bearer_auth; +} + /// Helper: build the header slice exactly as `open` does, given a config, /// and return whether the interleaved beta header is present. /// This lets us test the header-selection logic without a live HTTP connection. @@ -1234,6 +1257,25 @@ fn headerSliceIncludesInterleaved(cfg: *const config_mod.AnthropicMessagesConfig return send_interleaved; } +test "oauth-backed anthropic auth uses bearer auth" { + const cfg: config_mod.AnthropicMessagesConfig = .{ + .api_key = "k", + .base_url = "https://api.individual.githubcopilot.com", + .model = "claude-sonnet-4-5", + .use_bearer_auth = true, + }; + try testing.expect(headerSliceUsesBearerAuth(&cfg)); +} + +test "plain anthropic auth uses x-api-key" { + const cfg: config_mod.AnthropicMessagesConfig = .{ + .api_key = "k", + .base_url = "https://api.anthropic.com/v1", + .model = "claude-sonnet-4-5", + }; + try testing.expect(!headerSliceUsesBearerAuth(&cfg)); +} + test "interleaved beta header: enabled when thinking=.enabled and interleaved=true" { const cfg: config_mod.AnthropicMessagesConfig = .{ .api_key = "k", |
