diff options
Diffstat (limited to 'libpanto/src/provider_anthropic_messages.zig')
| -rw-r--r-- | libpanto/src/provider_anthropic_messages.zig | 73 |
1 files changed, 71 insertions, 2 deletions
diff --git a/libpanto/src/provider_anthropic_messages.zig b/libpanto/src/provider_anthropic_messages.zig index d76793b..8e41695 100644 --- a/libpanto/src/provider_anthropic_messages.zig +++ b/libpanto/src/provider_anthropic_messages.zig @@ -75,15 +75,31 @@ pub const AnthropicMessagesRequest = struct { const body = try json_mod.serializeRequest(self.allocator, self.config, conv, tools); defer self.allocator.free(body); - const extra_headers = [_]http.Header{ + // 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`. + 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 }, .{ .name = "anthropic-version", .value = self.config.api_version }, + undefined, // slot reserved for the optional beta header }; + const send_interleaved = self.config.thinking == .enabled and + self.config.thinking_interleaved; + if (send_interleaved) { + headers_buf[4] = .{ + .name = "anthropic-beta", + .value = "interleaved-thinking-2025-05-14", + }; + } + const extra_headers = headers_buf[0 .. if (send_interleaved) @as(usize, 5) else @as(usize, 4)]; rr.req = try self.http_client.request(.POST, uri, .{ - .extra_headers = &extra_headers, + .extra_headers = extra_headers, // Disable compression: gzip buffers small SSE frames, defeating // the streaming property we paid for `stream: true` to get. .headers = .{ .accept_encoding = .{ .override = "identity" } }, @@ -1095,3 +1111,56 @@ test "two streamed turns persist assistant replies in the conversation" { conv.messages.items[4].content.items[0].Text.items, ); } + +/// 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. +fn headerSliceIncludesInterleaved(cfg: *const config_mod.AnthropicMessagesConfig) bool { + const send_interleaved = cfg.thinking == .enabled and cfg.thinking_interleaved; + return send_interleaved; +} + +test "interleaved beta header: enabled when thinking=.enabled and interleaved=true" { + const cfg: config_mod.AnthropicMessagesConfig = .{ + .api_key = "k", + .base_url = "u", + .model = "m", + .thinking = .enabled, + .thinking_interleaved = true, + }; + try testing.expect(headerSliceIncludesInterleaved(&cfg)); +} + +test "interleaved beta header: absent when thinking=.enabled and interleaved=false" { + const cfg: config_mod.AnthropicMessagesConfig = .{ + .api_key = "k", + .base_url = "u", + .model = "m", + .thinking = .enabled, + .thinking_interleaved = false, + }; + try testing.expect(!headerSliceIncludesInterleaved(&cfg)); +} + +test "interleaved beta header: absent when thinking=.adaptive even if interleaved=true" { + const cfg: config_mod.AnthropicMessagesConfig = .{ + .api_key = "k", + .base_url = "u", + .model = "m", + .thinking = .adaptive, + .thinking_interleaved = true, + }; + // .adaptive does not send the header; interleaving is automatic there. + try testing.expect(!headerSliceIncludesInterleaved(&cfg)); +} + +test "interleaved beta header: absent when thinking=.disabled" { + const cfg: config_mod.AnthropicMessagesConfig = .{ + .api_key = "k", + .base_url = "u", + .model = "m", + .thinking = .disabled, + .thinking_interleaved = true, + }; + try testing.expect(!headerSliceIncludesInterleaved(&cfg)); +} |
