Module: Mistri::Providers::OpenAI::Serializer

Defined in:
lib/mistri/providers/openai/serializer.rb

Overview

Serializes protocol messages into Responses API input items.

Replay pairing is the rule that matters: with store: false the full history resends every turn, and a reasoning item must return VERBATIM (its encrypted_content is the model's chain of thought) followed by the same items it originally preceded. The signature slots carry what pairing needs: Thinking.signature holds the whole reasoning item, Text.signature the message item id, ToolCall.signature the function_call item id.

Class Method Summary collapse

Class Method Details

.assistant_item(block) ⇒ Object



68
69
70
71
72
73
74
# File 'lib/mistri/providers/openai/serializer.rb', line 68

def assistant_item(block)
  case block
  when Content::Thinking then reasoning_item(block)
  when Content::Text then message_item(block)
  when ToolCall then function_call_item(block)
  end
end

.assistant_items(msg) ⇒ Object



64
65
66
# File 'lib/mistri/providers/openai/serializer.rb', line 64

def assistant_items(msg)
  msg.content.filter_map { |block| assistant_item(block) }
end

.function_call_item(block) ⇒ Object



95
96
97
98
99
100
# File 'lib/mistri/providers/openai/serializer.rb', line 95

def function_call_item(block)
  item = { type: "function_call", call_id: block.id, name: block.name,
           arguments: JSON.generate(block.arguments) }
  item[:id] = block.signature if block.signature
  item
end

.input_items(history) ⇒ Object



20
21
22
# File 'lib/mistri/providers/openai/serializer.rb', line 20

def input_items(history)
  history.reject(&:system?).flat_map { |msg| items_for(msg) }
end

.items_for(msg) ⇒ Object



32
33
34
35
36
37
38
39
# File 'lib/mistri/providers/openai/serializer.rb', line 32

def items_for(msg)
  case msg.role
  when :user then [user_item(msg)]
  when :tool then [tool_result_item(msg)]
  when :assistant then assistant_items(msg)
  else []
  end
end

.message_item(block) ⇒ Object



88
89
90
91
92
93
# File 'lib/mistri/providers/openai/serializer.rb', line 88

def message_item(block)
  item = { type: "message", role: "assistant",
           content: [{ type: "output_text", text: block.text }] }
  item[:id] = block.signature if block.signature
  item
end

.reasoning_item(block) ⇒ Object

The reasoning item replays exactly as it arrived or not at all. An item without encrypted_content triggers a server-side id lookup that cannot succeed under store: false, so it drops rather than 404s.



79
80
81
82
83
84
85
86
# File 'lib/mistri/providers/openai/serializer.rb', line 79

def reasoning_item(block)
  return nil unless block.signature

  item = JSON.parse(block.signature)
  item.is_a?(Hash) && item["encrypted_content"] ? item : nil
rescue JSON::ParserError
  nil
end

.tool_result_item(msg) ⇒ Object

Non-text blocks in a tool result have no function_call_output encoding; note the omission rather than dropping it silently.



57
58
59
60
61
62
# File 'lib/mistri/providers/openai/serializer.rb', line 57

def tool_result_item(msg)
  omitted = msg.content.count { |block| !block.is_a?(Content::Text) }
  text = msg.text.to_s
  text = "#{text}\n[#{omitted} non-text block(s) omitted]".strip if omitted.positive?
  { type: "function_call_output", call_id: msg.tool_call_id, output: text }
end

.tools(definitions) ⇒ Object



24
25
26
27
28
29
30
# File 'lib/mistri/providers/openai/serializer.rb', line 24

def tools(definitions)
  definitions.map do |tool|
    spec = tool.transform_keys(&:to_sym)
    { type: "function", name: spec[:name], description: spec[:description],
      parameters: spec[:input_schema] }
  end
end

.user_block(block) ⇒ Object



45
46
47
48
49
50
51
52
53
# File 'lib/mistri/providers/openai/serializer.rb', line 45

def user_block(block)
  case block
  when Content::Text then { type: "input_text", text: block.text }
  when Content::Image
    { type: "input_image", image_url: "data:#{block.mime_type};base64,#{block.data}" }
  else
    raise SchemaError, "cannot serialize #{block.class} for OpenAI user input"
  end
end

.user_item(msg) ⇒ Object



41
42
43
# File 'lib/mistri/providers/openai/serializer.rb', line 41

def user_item(msg)
  { role: "user", content: msg.content.map { |block| user_block(block) } }
end