diff options
Diffstat (limited to 'libpanto/src/provider_anthropic_messages.zig')
| -rw-r--r-- | libpanto/src/provider_anthropic_messages.zig | 54 |
1 files changed, 14 insertions, 40 deletions
diff --git a/libpanto/src/provider_anthropic_messages.zig b/libpanto/src/provider_anthropic_messages.zig index 5c24b8f..e77d6a5 100644 --- a/libpanto/src/provider_anthropic_messages.zig +++ b/libpanto/src/provider_anthropic_messages.zig @@ -110,6 +110,12 @@ pub const AnthropicMessagesRequest = struct { @intFromEnum(response.head.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; + } return error.HttpError; } @@ -329,35 +335,6 @@ const StreamState = struct { self.stop_reason = if (reason) |r| try self.allocator.dupe(u8, r) else null; } - fn isJSONObject(input: []const u8) bool { - if (input.len == 0) return true; - var parsed = std.json.parseFromSlice(std.json.Value, std.heap.page_allocator, input, .{}) catch return false; - defer parsed.deinit(); - return parsed.value == .object; - } - - fn appendAnthropicFailureResult( - allocator: Allocator, - blocks: *std.ArrayList(conversation.ContentBlock), - tool_use_id: []const u8, - input: []const u8, - stop_reason: ?[]const u8, - ) !void { - const id_copy = try allocator.dupe(u8, tool_use_id); - errdefer allocator.free(id_copy); - const msg = try std.fmt.allocPrint( - allocator, - "Tool call was not executed: Anthropic stopped before completing valid tool input JSON (stop_reason={s}). Partial input: {s}", - .{ stop_reason orelse "unknown", input }, - ); - errdefer allocator.free(msg); - var content: conversation.TextualBlock = .empty; - errdefer content.deinit(allocator); - try content.appendSlice(allocator, msg); - allocator.free(msg); - try blocks.append(allocator, .{ .ToolResult = .{ .tool_use_id = id_copy, .content = content } }); - } - /// Close the active block: append it to `blocks` and emit onBlockComplete. fn closeBlock( self: *StreamState, @@ -391,16 +368,13 @@ const StreamState = struct { .text = a.text_buf, .signature = a.signature, } }, + // An interrupted/malformed tool_use (incomplete or non-object + // input JSON) is preserved as-is. The agent's dispatch path + // detects invalid input and answers it with a synthetic error + // ToolResult in the *following user message* — emitting a + // ToolResult here would wrongly place it in this assistant + // message, which Anthropic rejects. .tool_use => blk: { - if (!isJSONObject(a.text_buf.items)) { - try appendAnthropicFailureResult( - self.allocator, - &self.blocks, - a.tool_id.?, - a.text_buf.items, - self.stop_reason, - ); - } break :blk .{ .ToolUse = .{ .id = a.tool_id.?, .name = a.tool_name.?, @@ -444,10 +418,10 @@ const StreamState = struct { const moved_blocks = try self.blocks.toOwnedSlice(self.allocator); defer self.allocator.free(moved_blocks); - try conv.addAssistantMessage(moved_blocks); + const usage: ?provider_mod.Usage = if (self.usage_seen) self.usage else null; + try conv.addAssistantMessageWithUsage(moved_blocks, usage); const msg = conv.messages.items[conv.messages.items.len - 1]; - const usage: ?provider_mod.Usage = if (self.usage_seen) self.usage else null; try receiver.onMessageComplete(msg, usage); } }; |
