Skip to content
Kward Search API index

Module: Kward::CLITranscriptFormatter

Defined in:
lib/kward/cli_transcript_formatter.rb

Overview

Formats conversation messages for terminal transcript display.

Class Method Summary collapse

Class Method Details

.assistant_content_text(message) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
# File 'lib/kward/cli_transcript_formatter.rb', line 49

def assistant_content_text(message)
  text = content_text(MessageAccess.content(message))
  return text unless text.empty?

  response_items(message).filter_map do |item|
    next unless MessageAccess.value(item, :type) == "message"
    next if MessageAccess.value(item, :phase).to_s == "commentary"

    response_item_text(MessageAccess.value(item, :content))
  end.join
end

.content_part_text(part) ⇒ Object



106
107
108
109
110
111
112
113
114
115
# File 'lib/kward/cli_transcript_formatter.rb', line 106

def content_part_text(part)
  type = MessageAccess.value(part, :type)
  if type == "text"
    MessageAccess.value(part, :text)
  elsif type == "image"
    path = MessageAccess.value(part, :path)
    media_type = MessageAccess.value(part, :media_type) || MessageAccess.value(part, :mimeType) || "image"
    "[#{media_type}#{path ? ": #{path}" : ""}]"
  end
end

.content_text(content) ⇒ Object



40
41
42
43
44
45
46
47
# File 'lib/kward/cli_transcript_formatter.rb', line 40

def content_text(content)
  case content
  when Array
    content.filter_map { |part| content_part_text(part) }.join("\n")
  else
    content.to_s
  end
end

.decoded_image_size(data) ⇒ Object



147
148
149
150
151
152
153
# File 'lib/kward/cli_transcript_formatter.rb', line 147

def decoded_image_size(data)
  return nil if data.to_s.empty?

  Base64.decode64(data.to_s.gsub(/\s+/, "")).bytesize
rescue ArgumentError
  nil
end

.full_text(message) ⇒ Object



102
103
104
# File 'lib/kward/cli_transcript_formatter.rb', line 102

def full_text(message)
  MessageText.full_text(message)
end

.image_part_reference(part) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/kward/cli_transcript_formatter.rb', line 133

def image_part_reference(part)
  data = MessageAccess.value(part, :data)
  path = MessageAccess.value(part, :path)
  media_type = MessageAccess.value(part, :media_type) || MessageAccess.value(part, :mimeType) || "image"
  {
    status: :attached,
    type: "image",
    label: path.to_s.empty? ? "pasted image" : File.basename(path),
    media_type: media_type,
    size_bytes: decoded_image_size(data),
    path: path
  }
end

.image_parts(message) ⇒ Object



83
84
85
86
87
88
# File 'lib/kward/cli_transcript_formatter.rb', line 83

def image_parts(message)
  content = MessageAccess.content(message)
  return [] unless content.is_a?(Array)

  content.select { |part| MessageAccess.value(part, :type) == "image" }
end

.image_references(message) ⇒ Object



90
91
92
# File 'lib/kward/cli_transcript_formatter.rb', line 90

def image_references(message)
  image_parts(message).map { |part| image_part_reference(part) }
end

.reasoning(message) ⇒ Object



12
13
14
# File 'lib/kward/cli_transcript_formatter.rb', line 12

def reasoning(message)
  reasoning_blocks(message).join("\n")
end

.reasoning_blocks(message) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/kward/cli_transcript_formatter.rb', line 16

def reasoning_blocks(message)
  response_blocks = response_items(message).flat_map do |item|
    next [] unless MessageAccess.value(item, :type) == "reasoning"

    summary = response_item_text_parts(MessageAccess.value(item, :summary))
    summary.empty? ? response_item_text_parts(MessageAccess.value(item, :content)) : summary
  end
  return response_blocks unless response_blocks.empty?

  content = MessageAccess.content(message)
  if content.is_a?(Array)
    blocks = content.filter_map do |part|
      type = MessageAccess.value(part, :type)
      next unless ["thinking", "reasoning"].include?(type)

      MessageAccess.value(part, :thinking) || MessageAccess.value(part, :reasoning) || MessageAccess.value(part, :text)
    end.map(&:to_s).reject(&:empty?)
    return blocks unless blocks.empty?
  end

  direct = MessageAccess.value(message, :reasoning_summary).to_s
  direct.empty? ? [] : [direct]
end

.response_item_text(parts) ⇒ Object



121
122
123
# File 'lib/kward/cli_transcript_formatter.rb', line 121

def response_item_text(parts)
  response_item_text_parts(parts).join
end

.response_item_text_parts(parts) ⇒ Object



125
126
127
128
129
130
131
# File 'lib/kward/cli_transcript_formatter.rb', line 125

def response_item_text_parts(parts)
  Array(parts).filter_map do |part|
    next unless part.is_a?(Hash)

    MessageAccess.value(part, :text) || MessageAccess.value(part, :refusal)
  end.map(&:to_s).reject(&:empty?)
end

.response_items(message) ⇒ Object



117
118
119
# File 'lib/kward/cli_transcript_formatter.rb', line 117

def response_items(message)
  MessageAccess.response_items(message)
end

.synthetic_tool_call(name, id) ⇒ Object



94
95
96
97
98
99
100
# File 'lib/kward/cli_transcript_formatter.rb', line 94

def synthetic_tool_call(name, id)
  {
    "id" => id || "restored_tool",
    "type" => "function",
    "function" => { "name" => name || "tool", "arguments" => "{}" }
  }
end

.user_display_text(message) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/kward/cli_transcript_formatter.rb', line 61

def user_display_text(message)
  display_content = MessageAccess.display_content(message)
  return display_content.to_s unless display_content.nil?

  content = MessageAccess.content(message)
  return content.to_s unless content.is_a?(Array)

  text = content.filter_map do |part|
    next unless MessageAccess.value(part, :type) == "text"

    MessageAccess.value(part, :text)
  end.join("\n")
  ImageAttachments.display_text_without_references(text, ImageAttachments.references_from_text(text).select { |reference| reference[:status] == :attached })
end

.user_transcript_input(message) ⇒ Object



76
77
78
79
80
81
# File 'lib/kward/cli_transcript_formatter.rb', line 76

def user_transcript_input(message)
  content = MessageAccess.content(message)
  return content.to_s unless content.is_a?(Array)

  user_display_text(message)
end