Class: RubynCode::Agent::Conversation
- Inherits:
-
Object
- Object
- RubynCode::Agent::Conversation
- Defined in:
- lib/rubyn_code/agent/conversation.rb
Overview
rubocop:disable Metrics/ClassLength – message log + incremental token/tool-ID bookkeeping
Instance Attribute Summary collapse
-
#messages ⇒ Object
readonly
Returns the value of attribute messages.
Instance Method Summary collapse
-
#add_assistant_message(content, tool_calls: []) ⇒ Hash
Append an assistant turn to the conversation.
-
#add_tool_result(tool_use_id, _tool_name, output, is_error: false) ⇒ Hash
Append a tool result turn to the conversation.
-
#add_user_message(content) ⇒ Hash
Append a user turn to the conversation.
-
#clear! ⇒ void
Reset the conversation to an empty state.
-
#estimated_json_chars ⇒ Integer
Character length of the JSON-serialized messages array, maintained incrementally on append so per-turn token estimation doesn’t have to re-serialize the whole history.
-
#initialize ⇒ Conversation
constructor
A new instance of Conversation.
-
#last_assistant_text ⇒ String?
Extract the text from the most recent assistant message.
- #length ⇒ Integer
-
#refresh_derived_state! ⇒ void
Drops cached serialization/tool-ID bookkeeping.
-
#replace!(new_messages) ⇒ Object
Replace messages with a new array (used after compaction).
-
#to_api_format ⇒ Array<Hash>
Return the messages array formatted for the Claude API.
-
#undo_last! ⇒ void
Remove the last user + assistant exchange.
Constructor Details
#initialize ⇒ Conversation
Returns a new instance of Conversation.
10 11 12 13 |
# File 'lib/rubyn_code/agent/conversation.rb', line 10 def initialize @messages = [] reset_derived_state! end |
Instance Attribute Details
#messages ⇒ Object (readonly)
Returns the value of attribute messages.
8 9 10 |
# File 'lib/rubyn_code/agent/conversation.rb', line 8 def @messages end |
Instance Method Details
#add_assistant_message(content, tool_calls: []) ⇒ Hash
Append an assistant turn to the conversation.
31 32 33 34 35 36 37 |
# File 'lib/rubyn_code/agent/conversation.rb', line 31 def (content, tool_calls: []) blocks = normalize_content(content, tool_calls) = { role: 'assistant', content: blocks } @messages << () end |
#add_tool_result(tool_use_id, _tool_name, output, is_error: false) ⇒ Hash
Append a tool result turn to the conversation.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/rubyn_code/agent/conversation.rb', line 46 def add_tool_result(tool_use_id, _tool_name, output, is_error: false) result_block = { type: 'tool_result', tool_use_id: tool_use_id, content: output.to_s } result_block[:is_error] = true if is_error # The Claude API expects tool results as a user message whose content # is an array of tool_result blocks. When the previous message is # already a user/tool_result message we append to it so that multiple # tool results for the same assistant turn are batched together. if @messages.last && @messages.last[:role] == 'user' && (@messages.last) @messages.last[:content] << result_block track_appended_block(result_block) else = { role: 'user', content: [result_block] } @messages << () end result_block end |
#add_user_message(content) ⇒ Hash
Append a user turn to the conversation.
19 20 21 22 23 24 |
# File 'lib/rubyn_code/agent/conversation.rb', line 19 def (content) = { role: 'user', content: content } @messages << () end |
#clear! ⇒ void
This method returns an undefined value.
Reset the conversation to an empty state.
88 89 90 91 |
# File 'lib/rubyn_code/agent/conversation.rb', line 88 def clear! @messages.clear reset_derived_state! end |
#estimated_json_chars ⇒ Integer
Character length of the JSON-serialized messages array, maintained incrementally on append so per-turn token estimation doesn’t have to re-serialize the whole history. Matches JSON.generate(messages).length.
98 99 100 101 102 103 104 |
# File 'lib/rubyn_code/agent/conversation.rb', line 98 def estimated_json_chars @json_chars ||= @messages.sum { |msg| JSON.generate(msg).length } return 2 if @messages.empty? # "[" + per-message JSON joined by "," + "]" @json_chars + @messages.length + 1 end |
#last_assistant_text ⇒ String?
Extract the text from the most recent assistant message.
73 74 75 76 77 78 |
# File 'lib/rubyn_code/agent/conversation.rb', line 73 def last_assistant_text assistant_msg = @messages.reverse_each.find { |m| m[:role] == 'assistant' } return nil unless assistant_msg extract_text(assistant_msg[:content]) end |
#length ⇒ Integer
81 82 83 |
# File 'lib/rubyn_code/agent/conversation.rb', line 81 def length @messages.length end |
#refresh_derived_state! ⇒ void
This method returns an undefined value.
Drops cached serialization/tool-ID bookkeeping. Must be called after messages are mutated in place from outside this class (e.g. by Context::MicroCompact); the caches rebuild lazily on next access.
111 112 113 |
# File 'lib/rubyn_code/agent/conversation.rb', line 111 def refresh_derived_state! reset_derived_state! end |
#replace!(new_messages) ⇒ Object
Replace messages with a new array (used after compaction).
153 154 155 156 |
# File 'lib/rubyn_code/agent/conversation.rb', line 153 def replace!() @messages.replace() reset_derived_state! end |
#to_api_format ⇒ Array<Hash>
Return the messages array formatted for the Claude API. Ensures proper role alternation and content structure.
119 120 121 122 123 124 125 126 127 128 |
# File 'lib/rubyn_code/agent/conversation.rb', line 119 def to_api_format formatted = @messages.map do |msg| { role: msg[:role], content: format_content(msg[:content]) } end repair_orphaned_tool_uses(formatted) end |
#undo_last! ⇒ void
This method returns an undefined value.
Remove the last user + assistant exchange. Useful for undo. If the last two messages are assistant then user (most recent first), removes both. Otherwise removes only the last message.
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/rubyn_code/agent/conversation.rb', line 135 def undo_last! return if @messages.empty? # Walk backwards and remove the most recent user+assistant pair. # The typical pattern is: [..., user, assistant] or # [..., assistant, user(tool_results)]. removed = 0 while @messages.any? && removed < 2 last = @messages.last break if removed == 1 && last[:role] != 'assistant' && last[:role] != 'user' @messages.pop removed += 1 end reset_derived_state! end |