Module: Clacky::MessageFormat::Anthropic

Defined in:
lib/clacky/message_format/anthropic.rb

Overview

Static helpers for Anthropic API message format.

Responsibilities:

- Identify Anthropic-style messages stored in @messages
- Convert internal @messages → Anthropic API request body
- Parse Anthropic API response → internal format
- Format tool results for the next turn

Internal @messages always use OpenAI-style canonical format:

assistant tool_calls: { role: "assistant", tool_calls: [{id:, function:{name:,arguments:}}] }
tool result:          { role: "tool", tool_call_id:, content: }

This module converts that canonical format to Anthropic native on the way OUT, and converts Anthropic native back to canonical on the way IN.

Class Method Summary collapse

Class Method Details

.build_request_body(messages, model, tools, max_tokens, caching_enabled) ⇒ Hash

Convert canonical @messages + tools into an Anthropic API request body.

Parameters:

  • messages (Array<Hash>)

    canonical messages (may include system)

  • model (String)
  • tools (Array<Hash>)

    OpenAI-style tool definitions

  • max_tokens (Integer)
  • caching_enabled (Boolean)

Returns:

  • (Hash)

    ready to serialize as JSON body



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/clacky/message_format/anthropic.rb', line 51

def build_request_body(messages, model, tools, max_tokens, caching_enabled)
  system_messages = messages.select { |m| m[:role] == "system" }
  regular_messages = messages.reject { |m| m[:role] == "system" }

  system_text = system_messages.map { |m| extract_text(m[:content]) }.join("\n\n")

  api_messages = regular_messages.map { |msg| to_api_message(msg, caching_enabled) }
  api_tools    = tools&.map { |t| to_api_tool(t) }

  if caching_enabled && api_tools&.any?
    api_tools.last[:cache_control] = { type: "ephemeral" }
  end

  body = { model: model, max_tokens: max_tokens, messages: api_messages }
  body[:system] = system_text unless system_text.empty?
  body[:tools]  = api_tools   if api_tools&.any?
  body
end

.format_tool_results(response, tool_results) ⇒ Object

Format tool results into canonical messages to append to @messages. Input: response (canonical, has :tool_calls), tool_results array Output: canonical messages: [{ role: “tool”, tool_call_id:, content: }]



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/clacky/message_format/anthropic.rb', line 111

def format_tool_results(response, tool_results)
  results_map = tool_results.each_with_object({}) { |r, h| h[r[:id]] = r }

  response[:tool_calls].map do |tc|
    result = results_map[tc[:id]]
    {
      role: "tool",
      tool_call_id: tc[:id],
      content: result ? result[:content] : { error: "Tool result missing" }.to_json
    }
  end
end

.parse_response(data) ⇒ Hash

Parse Anthropic API response into canonical internal format.

Parameters:

  • data (Hash)

    parsed JSON response body

Returns:

  • (Hash)

    canonical response: { content:, tool_calls:, finish_reason:, usage: }



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/clacky/message_format/anthropic.rb', line 75

def parse_response(data)
  blocks  = data["content"] || []
  usage   = data["usage"]   || {}

  content = blocks.select { |b| b["type"] == "text" }.map { |b| b["text"] }.join("")

  # tool_calls use canonical format (id, function: {name, arguments})
  tool_calls = blocks.select { |b| b["type"] == "tool_use" }.map do |tc|
    args = tc["input"].is_a?(String) ? tc["input"] : tc["input"].to_json
    { id: tc["id"], type: "function", name: tc["name"], arguments: args }
  end

  finish_reason = case data["stop_reason"]
                  when "end_turn"   then "stop"
                  when "tool_use"   then "tool_calls"
                  when "max_tokens" then "length"
                  else data["stop_reason"]
                  end

  usage_data = {
    prompt_tokens:      usage["input_tokens"],
    completion_tokens:  usage["output_tokens"],
    total_tokens:       usage["input_tokens"].to_i + usage["output_tokens"].to_i
  }
  usage_data[:cache_read_input_tokens]     = usage["cache_read_input_tokens"]     if usage["cache_read_input_tokens"]
  usage_data[:cache_creation_input_tokens] = usage["cache_creation_input_tokens"] if usage["cache_creation_input_tokens"]

  { content: content, tool_calls: tool_calls, finish_reason: finish_reason,
    usage: usage_data, raw_api_usage: usage }
end

.tool_result_message?(msg) ⇒ Boolean

Returns true if the message is an Anthropic-native tool result stored in NOTE: After the refactor, new tool results are stored in canonical format (role: “tool”). This helper handles legacy messages that might exist in older sessions.

Returns:

  • (Boolean)


29
30
31
32
33
# File 'lib/clacky/message_format/anthropic.rb', line 29

def tool_result_message?(msg)
  msg[:role] == "user" &&
    msg[:content].is_a?(Array) &&
    msg[:content].any? { |b| b.is_a?(Hash) && b[:type] == "tool_result" }
end

.tool_use_ids(msg) ⇒ Object

Returns the tool_use_ids referenced in an Anthropic-native tool result message.



36
37
38
39
40
# File 'lib/clacky/message_format/anthropic.rb', line 36

def tool_use_ids(msg)
  return [] unless tool_result_message?(msg)

  msg[:content].select { |b| b[:type] == "tool_result" }.map { |b| b[:tool_use_id] }
end