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.zig54
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);
}
};