Module: OllamaAgent::Console

Defined in:
lib/ollama_agent/console.rb

Overview

ANSI styling for TTY output. Respects no-color.org/ via NO_COLOR. Assistant replies use tty-markdown when enabled (headings, lists, bold, code blocks). rubocop:disable Metrics/ModuleLength – single-responsibility output module; methods are all short

Constant Summary collapse

THINKING_MARKDOWN_THEME =

Muted tty-markdown palette so “Thinking” stays visually distinct from the main reply (default TTY::Markdown theme uses cyan/yellow like normal assistant output).

{
  em: :bright_black,
  header: %i[bright_black bold],
  hr: :bright_black,
  link: %i[bright_black underline],
  list: :bright_black,
  strong: %i[bright_black bold],
  table: :bright_black,
  quote: :bright_black,
  image: :bright_black,
  note: :bright_black,
  comment: :bright_black
}.freeze
THINKING_FRAME_WIDTH =
44

Class Method Summary collapse

Class Method Details

.apply_prompt(text) ⇒ Object



289
290
291
# File 'lib/ollama_agent/console.rb', line 289

def apply_prompt(text)
  yellow(text)
end

.assistant_output(text) ⇒ Object



177
178
179
# File 'lib/ollama_agent/console.rb', line 177

def assistant_output(text)
  green(text)
end

.assistant_reply_headingObject



229
230
231
# File 'lib/ollama_agent/console.rb', line 229

def assistant_reply_heading
  bold(green("Assistant"))
end

.bold(text) ⇒ Object



161
# File 'lib/ollama_agent/console.rb', line 161

def bold(text) = style(text, 1)

.close_streaming_thinking_if_still_open!Object

When the model returns only thinking (no content), close ANSI state on stream end.



99
100
101
102
103
104
105
106
107
# File 'lib/ollama_agent/console.rb', line 99

def close_streaming_thinking_if_still_open!
  return unless Thread.current[:ollama_agent_stream_thinking_open]

  Thread.current[:ollama_agent_stream_thinking_open] = false
  Thread.current[:ollama_agent_stream_had_thinking] = false
  Thread.current[:ollama_agent_stream_thinking_buffer] = nil
  print "\e[0m" if color_enabled?
  puts
end

.color_enabled?Boolean

Returns:

  • (Boolean)


28
29
30
# File 'lib/ollama_agent/console.rb', line 28

def color_enabled?
  $stdout.tty? && ENV["NO_COLOR"].to_s.empty? && ENV["OLLAMA_AGENT_COLOR"] != "0"
end

.cyan(text) ⇒ Object



163
# File 'lib/ollama_agent/console.rb', line 163

def cyan(text) = style(text, 36)

.dim(text) ⇒ Object



162
# File 'lib/ollama_agent/console.rb', line 162

def dim(text) = style(text, 2)

.dim_indent_body(text) ⇒ Object



221
222
223
224
225
226
227
# File 'lib/ollama_agent/console.rb', line 221

def dim_indent_body(text)
  s = text.to_s.rstrip
  return "" if s.empty?

  indent = "  "
  dim(s.lines.map { |l| "#{indent}#{l.rstrip}" }.join("\n"))
end

.error_line(text) ⇒ Object



293
294
295
# File 'lib/ollama_agent/console.rb', line 293

def error_line(text)
  red(text)
end

.finalize_streaming_thinking_before_content!Object

Call before the first streamed content token: closes dim reasoning, optional Assistant heading.



86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ollama_agent/console.rb', line 86

def finalize_streaming_thinking_before_content!
  return unless Thread.current[:ollama_agent_stream_thinking_open]

  Thread.current[:ollama_agent_stream_thinking_open] = false
  had = Thread.current[:ollama_agent_stream_had_thinking]
  Thread.current[:ollama_agent_stream_had_thinking] = false
  Thread.current[:ollama_agent_stream_thinking_buffer] = nil
  print "\e[0m" if color_enabled?
  puts
  puts assistant_reply_heading if had
end

.format_assistant(text) ⇒ Object

Renders Markdown to the terminal (bold, lists, fenced code) when enabled; otherwise plain green text.



182
183
184
185
186
# File 'lib/ollama_agent/console.rb', line 182

def format_assistant(text)
  return assistant_output(text) unless markdown_enabled?

  markdown_parse(text) || assistant_output(text)
end

.format_thinking(text) ⇒ Object



188
189
190
191
192
193
194
195
196
197
# File 'lib/ollama_agent/console.rb', line 188

def format_thinking(text)
  line = thinking_frame_line
  header = "#{magenta(bold("Thinking"))}\n#{line}\n"
  body = if thinking_markdown_enabled?
           markdown_parse(text, thinking: true) || dim(text.to_s)
         else
           dim(text.to_s)
         end
  "#{header}#{body}\n#{line}"
end

.format_thinking_compact_merge(text) ⇒ Object

Later thinking in the same run (tool rounds, new chat chunks): same block, separated by a blank line.



206
207
208
# File 'lib/ollama_agent/console.rb', line 206

def format_thinking_compact_merge(text)
  "\n#{thinking_compact_body(text)}"
end

.format_thinking_compact_open(text) ⇒ Object



199
200
201
202
203
# File 'lib/ollama_agent/console.rb', line 199

def format_thinking_compact_open(text)
  label = color_enabled? ? dim("Thinking") : "Thinking"
  body = thinking_compact_body(text)
  "#{label}\n#{body}"
end

.green(text) ⇒ Object



164
# File 'lib/ollama_agent/console.rb', line 164

def green(text) = style(text, 32)

.magenta(text) ⇒ Object



167
# File 'lib/ollama_agent/console.rb', line 167

def magenta(text) = style(text, 35)

.mark_thinking_shown_in_session!Object



60
61
62
# File 'lib/ollama_agent/console.rb', line 60

def mark_thinking_shown_in_session!
  Thread.current[:ollama_agent_thinking_shown] = true
end

.markdown_enabled?Boolean

Returns:

  • (Boolean)


32
33
34
# File 'lib/ollama_agent/console.rb', line 32

def markdown_enabled?
  $stdout.tty? && ENV["NO_COLOR"].to_s.empty? && ENV["OLLAMA_AGENT_MARKDOWN"] != "0"
end

.patch_title(text) ⇒ Object



285
286
287
# File 'lib/ollama_agent/console.rb', line 285

def patch_title(text)
  bold(yellow(text))
end

.prompt_prefixObject



173
174
175
# File 'lib/ollama_agent/console.rb', line 173

def prompt_prefix
  cyan("> ")
end

.puts_assistant_message(message) ⇒ Object

Prints thinking (if any) then main content; duck-types #thinking and #content.



256
257
258
259
260
261
262
263
# File 'lib/ollama_agent/console.rb', line 256

def puts_assistant_message(message)
  thinking_present = assistant_message_thinking_present?(message.thinking)
  if thinking_present
    puts thinking_output_chunk(message.thinking)
    mark_thinking_shown_in_session! unless thinking_framed_style?
  end
  assistant_reply_if_present(message.content, thinking_present)
end

.red(text) ⇒ Object



166
# File 'lib/ollama_agent/console.rb', line 166

def red(text) = style(text, 31)

.reset_thinking_session!Object



49
50
51
52
53
54
# File 'lib/ollama_agent/console.rb', line 49

def reset_thinking_session!
  Thread.current[:ollama_agent_thinking_shown] = false
  Thread.current[:ollama_agent_stream_thinking_open] = false
  Thread.current[:ollama_agent_stream_had_thinking] = false
  Thread.current[:ollama_agent_stream_thinking_buffer] = nil
end

.style(text, *codes) ⇒ Object



152
153
154
155
156
157
158
159
# File 'lib/ollama_agent/console.rb', line 152

def style(text, *codes)
  return text.to_s unless color_enabled?

  t = text.to_s
  return t if t.empty? || codes.flatten.compact.empty?

  "\e[#{codes.flatten.compact.join(";")}m#{t}\e[0m"
end

.thinking_already_shown_in_session?Boolean

Returns:

  • (Boolean)


56
57
58
# File 'lib/ollama_agent/console.rb', line 56

def thinking_already_shown_in_session?
  Thread.current[:ollama_agent_thinking_shown] == true
end

.thinking_compact_body(text) ⇒ Object



210
211
212
213
214
215
216
217
218
219
# File 'lib/ollama_agent/console.rb', line 210

def thinking_compact_body(text)
  if thinking_markdown_enabled?
    parsed = markdown_parse(text, thinking: true)
    return parsed if parsed

    return dim_indent_body(text)
  end

  dim_indent_body(text)
end

.thinking_frame_lineObject



233
234
235
# File 'lib/ollama_agent/console.rb', line 233

def thinking_frame_line
  dim("-" * THINKING_FRAME_WIDTH)
end

.thinking_framed_style?Boolean

compact (default): one “Thinking” label per agent run; later reasoning uses blank lines only (Cursor-like). framed: repeat the full banner + rulers on every assistant message (legacy).

Returns:

  • (Boolean)


44
45
46
# File 'lib/ollama_agent/console.rb', line 44

def thinking_framed_style?
  ENV.fetch("OLLAMA_AGENT_THINKING_STYLE", "compact").to_s.strip.downcase == "framed"
end

.thinking_markdown_enabled?Boolean

Thinking uses dim plain text by default so it stays visually separate from the main reply. Set OLLAMA_AGENT_THINKING_MARKDOWN=1 to render thinking through tty-markdown (muted theme).

Returns:

  • (Boolean)


38
39
40
# File 'lib/ollama_agent/console.rb', line 38

def thinking_markdown_enabled?
  markdown_enabled? && ENV["OLLAMA_AGENT_THINKING_MARKDOWN"] == "1"
end

.tool_call_line(name, args) ⇒ Object



297
298
299
300
# File 'lib/ollama_agent/console.rb', line 297

def tool_call_line(name, args)
  keys = args.is_a?(Hash) ? args.keys.first(2).join(", ") : ""
  cyan("#{name}(#{keys})")
end

.tool_result_line(name, result) ⇒ Object



302
303
304
305
# File 'lib/ollama_agent/console.rb', line 302

def tool_result_line(name, result)
  preview = result.to_s[0, 60].gsub(/\s+/, " ")
  dim("#{name}: #{preview}")
end

.welcome_banner(text) ⇒ Object



169
170
171
# File 'lib/ollama_agent/console.rb', line 169

def welcome_banner(text)
  bold(cyan(text))
end

.write_stream_token(fragment) ⇒ Object



80
81
82
83
# File 'lib/ollama_agent/console.rb', line 80

def write_stream_token(fragment)
  print utf8_for_stream(fragment)
  $stdout.flush
end

.write_streaming_thinking_fragment(fragment) ⇒ Object

Print one dim “Thinking” label, then stream fragments in dim until content tokens arrive. Handles both cumulative thinking strings (common from Ollama) and plain deltas; sanitizes UTF-8.



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/ollama_agent/console.rb', line 68

def write_streaming_thinking_fragment(fragment)
  text = utf8_for_stream(fragment)
  return if text.empty?

  open_streaming_thinking_section_if_needed
  to_print = streaming_thinking_increment_to_print(text)
  return if to_print.empty?

  print to_print
  $stdout.flush
end

.yellow(text) ⇒ Object



165
# File 'lib/ollama_agent/console.rb', line 165

def yellow(text) = style(text, 33)