diff options
Diffstat (limited to 'libpanto/src/provider_anthropic_messages.zig')
| -rw-r--r-- | libpanto/src/provider_anthropic_messages.zig | 32 |
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" { |
