Skip to content

Brute

Brute::MessageTransport::Anthropic

module Brute::MessageTransport
class Anthropic < Brute::MessageTransport
end
end

MessageTransport for the official anthropic gem (https://github.com/anthropics/anthropic-sdk-ruby). Brute does not require it — you do:

require "anthropic"
client = Anthropic::Client.new(api_key: ENV["ANTHROPIC_API_KEY"])
response = client.messages.create(
model: "claude-opus-4-8",
max_tokens: 16_000,
system_: Brute::MessageTransport::Anthropic.system_text(env[:messages]),
messages: Brute::MessageTransport::Anthropic.dump_all(env[:messages]),
tools: ...,
)
Brute::MessageTransport::Anthropic.wrap_each(response) { |m| env[:messages] << m }

Anthropic’s Messages API differs from chat-completions-shaped APIs in two ways this transport absorbs: the system prompt is a top-level parameter (not a message), and tool results are content blocks inside a user message — consecutive tool results are folded into one user turn so roles keep alternating.

Constants

EmptyMessage

EmptyMessage = Class.new(StandardError)

An assistant message this cannot put on the wire, because Anthropic takes no empty content and there is nothing in it to send.

FORMAT

FORMAT = "anthropic-claude-v1"

Whose signatures these are. OpenRouter names the same format when it proxies Claude, so thinking that came back through it goes home.

Class Methods

self.assistant_content

assistant_content(message)

The assistant turn’s content blocks, in the order the wire wants them: thinking first, because the sequence is part of what Anthropic verifies, then what was said, then what was called.

Anthropic takes no empty content – not an empty string, not an empty block, not an empty array – so a message with nothing in it is not one this can express. It says so rather than sending a request that is already a 400.

self.dump

dump(message, model: nil)

Brute::Message -> an Anthropic message param hash.

self.dump_all

dump_all(messages, model: nil)

Brute log -> the messages: array. Drops :system messages (see .system_text) and folds consecutive :tool results into one user turn.

self.expressible?

expressible?(reasoning)

Whether every stored block has an Anthropic form. There are two of them and no third – thinking and redacted_thinking – and both carry a payload Anthropic issued and checks on the way back. So a block another provider signed, one carrying no signature at all, and a summary (which is a precis, and has no block of its own here) each have no form to take. The sequence goes whole or not at all, because what Anthropic verifies is the sequence, so one such block answers for the rest.

self.system_text

system_text(messages)

The :system messages’ text, for the top-level system_: parameter.

self.thinking_block

thinking_block(block)

A thinking block, or the redacted one that stands in for it when the provider will not show its working. Both go back whole.

self.thinking_blocks

thinking_blocks(message)

The stored blocks as Anthropic content blocks, in order. Where the sequence has no Anthropic form there is nothing to send – an unverifiable block is a 400, and no thinking beats a failed request.

self.thinking_param

thinking_param(block)

One stored block as the content block Anthropic named it. The opaque payload rides under data, the readable one under thinking with the signature that verifies it.

self.tool_result_block

tool_result_block(message)

Not documented.

Defined in

  • lib/brute/message_transport/anthropic.rb