Module: Clacky::MessageFormat::OpenAI

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

Overview

Static helpers for OpenAI-compatible API message format.

The canonical internal @messages format IS OpenAI format, so this module mainly handles response parsing, tool result formatting, and message type identification — minimal transformation needed.

Class Method Summary collapse

Class Method Details

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

Build an OpenAI-compatible request body. Canonical messages are already in OpenAI format — no conversion needed.

Parameters:

  • messages (Array<Hash>)

    canonical messages

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

    OpenAI-style tool definitions

  • max_tokens (Integer)
  • caching_enabled (Boolean)

    (only effective for Claude via OpenRouter)

Returns:

  • (Hash)


37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/clacky/message_format/open_ai.rb', line 37

def build_request_body(messages, model, tools, max_tokens, caching_enabled)
  body = { model: model, max_tokens: max_tokens, messages: messages }

  if tools&.any?
    if caching_enabled
      cached_tools = deep_clone(tools)
      cached_tools.last[:cache_control] = { type: "ephemeral" }
      body[:tools] = cached_tools
    else
      body[:tools] = tools
    end
  end

  body
end

.format_tool_results(response, tool_results) ⇒ Array<Hash>

Format tool results into canonical messages to append to @messages.

Returns:

  • (Array<Hash>)

    canonical tool messages



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/clacky/message_format/open_ai.rb', line 97

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]]
    raw_content = result ? result[:content] : { error: "Tool result missing" }.to_json

    # OpenAI tool message content must be a String.
    # If a tool returned multipart Array blocks (e.g. screenshot image), convert to JSON.
    content = raw_content.is_a?(Array) ? JSON.generate(raw_content) : raw_content

    {
      role:         "tool",
      tool_call_id: tc[:id],
      content:      content
    }
  end
end

.parse_response(data) ⇒ Hash

Parse OpenAI-compatible API response into canonical internal format.

Parameters:

  • data (Hash)

    parsed JSON response body

Returns:

  • (Hash)


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/clacky/message_format/open_ai.rb', line 58

def parse_response(data)
  message       = data["choices"].first["message"]
  usage         = data["usage"] || {}
  raw_api_usage = usage.dup

  usage_data = {
    prompt_tokens:     usage["prompt_tokens"],
    completion_tokens: usage["completion_tokens"],
    total_tokens:      usage["total_tokens"]
  }

  usage_data[:api_cost]                    = usage["cost"]                            if usage["cost"]
  usage_data[:cache_creation_input_tokens] = usage["cache_creation_input_tokens"]     if usage["cache_creation_input_tokens"]
  usage_data[:cache_read_input_tokens]     = usage["cache_read_input_tokens"]         if usage["cache_read_input_tokens"]

  # OpenRouter stores cache info under prompt_tokens_details
  if (details = usage["prompt_tokens_details"])
    usage_data[:cache_read_input_tokens]     = details["cached_tokens"]    if details["cached_tokens"].to_i > 0
    usage_data[:cache_creation_input_tokens] = details["cache_write_tokens"] if details["cache_write_tokens"].to_i > 0
  end

  result = {
    content:       message["content"],
    tool_calls:    parse_tool_calls(message["tool_calls"]),
    finish_reason: data["choices"].first["finish_reason"],
    usage:         usage_data,
    raw_api_usage: raw_api_usage
  }

  # Preserve reasoning_content (e.g. Kimi/Moonshot extended thinking)
  result[:reasoning_content] = message["reasoning_content"] if message["reasoning_content"]

  result
end

.tool_call_ids(msg) ⇒ Object

Returns the tool_call_ids referenced in a tool result message.



21
22
23
24
25
# File 'lib/clacky/message_format/open_ai.rb', line 21

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

  [msg[:tool_call_id]]
end

.tool_result_message?(msg) ⇒ Boolean

Returns true if the message is a canonical tool result.

Returns:

  • (Boolean)


16
17
18
# File 'lib/clacky/message_format/open_ai.rb', line 16

def tool_result_message?(msg)
  msg[:role] == "tool" && !msg[:tool_call_id].nil?
end