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.zig32
1 files changed, 20 insertions, 12 deletions
diff --git a/libpanto/src/provider_anthropic_messages.zig b/libpanto/src/provider_anthropic_messages.zig
index e77d6a5..55efb5f 100644
--- a/libpanto/src/provider_anthropic_messages.zig
+++ b/libpanto/src/provider_anthropic_messages.zig
@@ -33,6 +33,8 @@ pub const AnthropicMessagesRequest = struct {
io: Io,
config: *const config_mod.AnthropicMessagesConfig,
http_client: *http.Client,
+ /// Optional diagnostic side-channel; see `OpenAIChatRequest.diag`.
+ diag: ?*provider_mod.ProviderDiagnostic = null,
pub fn streamStep(
self: *AnthropicMessagesRequest,
@@ -106,17 +108,18 @@ pub const AnthropicMessagesRequest = struct {
try err_buf.appendSlice(self.allocator, tmp[0..n]);
if (err_buf.items.len > 16 * 1024) break;
}
- std.log.err("anthropic_messages HTTP {d}: {s}", .{
- @intFromEnum(response.head.status),
- err_buf.items,
- });
+ const status: u16 = @intFromEnum(response.head.status);
+ std.log.err("anthropic_messages HTTP {d}: {s}", .{ status, err_buf.items });
// Anthropic rejects oversized requests with HTTP 400 and a
- // "prompt is too long" message; surface as ContextOverflow so
- // the caller can compact and retry.
- if (@intFromEnum(response.head.status) == 400 and provider_mod.isContextOverflowBody(err_buf.items)) {
- return error.ContextOverflow;
+ // "prompt is too long" message; `classifyHttpStatus` maps that to
+ // ContextOverflow so the caller can compact and retry. Other
+ // statuses map to retryable/terminal provider errors.
+ const classified = provider_mod.classifyHttpStatus(status, err_buf.items);
+ if (self.diag) |d| {
+ d.status_code = status;
+ d.retry_after_ms = provider_mod.retryAfterFromHead(response.head);
}
- return error.HttpError;
+ return classified;
}
var transfer_buf: [4096]u8 = undefined;
@@ -140,7 +143,8 @@ pub const AnthropicMessagesRequest = struct {
while (true) {
const n = body_reader.readVec(&vecs) catch |err| switch (err) {
error.EndOfStream => break,
- else => return err,
+ // Transport failure before the message completed: retryable.
+ else => return error.ProviderStreamMalformed,
};
if (n == 0) continue;
@@ -473,7 +477,9 @@ fn handleEvent(
if (!@import("builtin").is_test) {
std.log.err("anthropic stream error: {?s}: {?s}", .{ e.kind, e.message });
}
- return error.StreamError;
+ // Mid-stream error event (e.g. `overloaded_error`) before the
+ // message was committed: retryable malformed-stream failure.
+ return error.ProviderStreamMalformed;
},
.unknown => {
// Forward-compatible: ignore unknown event types per Anthropic's
@@ -543,12 +549,14 @@ const RecordingReceiver = struct {
.onBlockComplete = onBlockComplete,
.onMessageComplete = onMessageComplete,
.onError = onError,
+ .onProviderRetry = onProviderRetry,
};
fn onMessageStart(ptr: *anyopaque, role: conversation.MessageRole) anyerror!void {
const self: *RecordingReceiver = @ptrCast(@alignCast(ptr));
try self.events.append(self.allocator, .{ .message_start = role });
}
+ fn onProviderRetry(_: *anyopaque, _: provider_mod.ProviderRetryInfo) void {}
fn onBlockStart(
ptr: *anyopaque,
bt: provider_mod.ContentBlockType,
@@ -988,7 +996,7 @@ test "error event propagates as Zig error" {
&state,
&recv,
);
- try testing.expectError(error.StreamError, result);
+ try testing.expectError(error.ProviderStreamMalformed, result);
}
test "two streamed turns persist assistant replies in the conversation" {