summaryrefslogtreecommitdiff
path: root/libpanto/src/provider_anthropic_messages.zig
diff options
context:
space:
mode:
Diffstat (limited to 'libpanto/src/provider_anthropic_messages.zig')
-rw-r--r--libpanto/src/provider_anthropic_messages.zig73
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));
+}