Class: Brute::MessageStore
- Inherits:
-
Object
- Object
- Brute::MessageStore
- Defined in:
- lib/brute/message_store.rb
Overview
Stores session messages as individual JSON files in the OpenCode parts format. Each session gets a directory; each message is a numbered JSON file inside it.
Storage layout:
~/.brute/sessions/{session-id}/
session.meta.json
msg_0001.json
msg_0002.json
...
Message format matches OpenCode’s MessageV2.WithParts:
{ info: { id:, sessionID:, role:, time:, ... },
parts: [{ id:, type:, ... }, ...] }
Instance Attribute Summary collapse
-
#dir ⇒ Object
readonly
Returns the value of attribute dir.
-
#session_id ⇒ Object
readonly
Returns the value of attribute session_id.
Instance Method Summary collapse
-
#add_step_finish(message_id:, tokens: nil) ⇒ Object
Add a step-finish part to an assistant message.
-
#add_text_part(message_id:, text:) ⇒ Object
Add a text part to an existing message.
-
#add_tool_part(message_id:, tool:, call_id:, input:) ⇒ Object
Add a tool part in “running” state.
-
#append_assistant(message_id: nil, parent_id: nil, model_id: nil, provider_id: nil) ⇒ Object
Record the start of an assistant message.
-
#append_user(text:, message_id: nil) ⇒ Object
Record a user message.
-
#complete_assistant(message_id:, tokens: nil) ⇒ Object
Finalize an assistant message with token counts and completion time.
-
#complete_tool_part(message_id:, call_id:, output:) ⇒ Object
Mark a tool part as completed with output.
-
#count ⇒ Object
Number of stored messages.
-
#error_tool_part(message_id:, call_id:, error:) ⇒ Object
Mark a tool part as errored.
-
#initialize(session_id:, dir: nil) ⇒ MessageStore
constructor
A new instance of MessageStore.
-
#message(id) ⇒ Object
Single message by ID.
-
#messages ⇒ Object
All messages in order.
Constructor Details
#initialize(session_id:, dir: nil) ⇒ MessageStore
Returns a new instance of MessageStore.
28 29 30 31 32 33 34 35 36 |
# File 'lib/brute/message_store.rb', line 28 def initialize(session_id:, dir: nil) @session_id = session_id @dir = dir || File.join(Dir.home, ".brute", "sessions", session_id) @messages = {} # id => { info:, parts: } @seq = 0 @part_seq = 0 @mutex = Mutex.new load_existing end |
Instance Attribute Details
#dir ⇒ Object (readonly)
Returns the value of attribute dir.
26 27 28 |
# File 'lib/brute/message_store.rb', line 26 def dir @dir end |
#session_id ⇒ Object (readonly)
Returns the value of attribute session_id.
26 27 28 |
# File 'lib/brute/message_store.rb', line 26 def session_id @session_id end |
Instance Method Details
#add_step_finish(message_id:, tokens: nil) ⇒ Object
Add a step-finish part to an assistant message.
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/brute/message_store.rb', line 151 def add_step_finish(message_id:, tokens: nil) @mutex.synchronize do msg = @messages[] return unless msg part = { id: next_part_id, sessionID: @session_id, messageID: , type: "step-finish", reason: "stop", tokens: tokens || { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, } msg[:parts] << part persist() end end |
#add_text_part(message_id:, text:) ⇒ Object
Add a text part to an existing message.
84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/brute/message_store.rb', line 84 def add_text_part(message_id:, text:) @mutex.synchronize do msg = @messages[] return unless msg part = { id: next_part_id, sessionID: @session_id, messageID: , type: "text", text: text } msg[:parts] << part persist() part[:id] end end |
#add_tool_part(message_id:, tool:, call_id:, input:) ⇒ Object
Add a tool part in “running” state. Returns the part ID.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/brute/message_store.rb', line 98 def add_tool_part(message_id:, tool:, call_id:, input:) @mutex.synchronize do msg = @messages[] return unless msg part = { id: next_part_id, sessionID: @session_id, messageID: , type: "tool", callID: call_id, tool: tool, state: { status: "running", input: input, time: { start: now_ms }, }, } msg[:parts] << part persist() part[:id] end end |
#append_assistant(message_id: nil, parent_id: nil, model_id: nil, provider_id: nil) ⇒ Object
Record the start of an assistant message. Returns the message ID. Call complete_assistant later to fill in tokens/timing.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/brute/message_store.rb', line 61 def append_assistant(message_id: nil, parent_id: nil, model_id: nil, provider_id: nil) id = || msg = { info: { id: id, sessionID: @session_id, role: "assistant", parentID: parent_id, time: { created: now_ms }, modelID: model_id, providerID: provider_id, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, cost: 0.0, }, parts: [], } (id, msg) id end |
#append_user(text:, message_id: nil) ⇒ Object
Record a user message.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/brute/message_store.rb', line 41 def append_user(text:, message_id: nil) id = || msg = { info: { id: id, sessionID: @session_id, role: "user", time: { created: now_ms }, }, parts: [ { id: next_part_id, sessionID: @session_id, messageID: id, type: "text", text: text }, ], } (id, msg) id end |
#complete_assistant(message_id:, tokens: nil) ⇒ Object
Finalize an assistant message with token counts and completion time.
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/brute/message_store.rb', line 170 def complete_assistant(message_id:, tokens: nil) @mutex.synchronize do msg = @messages[] return unless msg msg[:info][:time][:completed] = now_ms if tokens msg[:info][:tokens] = { input: tokens[:input] || tokens[:total_input] || 0, output: tokens[:output] || tokens[:total_output] || 0, reasoning: tokens[:reasoning] || tokens[:total_reasoning] || 0, cache: tokens[:cache] || { read: 0, write: 0 }, } end persist() end end |
#complete_tool_part(message_id:, call_id:, output:) ⇒ Object
Mark a tool part as completed with output.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/brute/message_store.rb', line 119 def complete_tool_part(message_id:, call_id:, output:) @mutex.synchronize do msg = @messages[] return unless msg part = msg[:parts].find { |p| p[:type] == "tool" && p[:callID] == call_id } return unless part part[:state][:status] = "completed" part[:state][:output] = output part[:state][:time][:end] = now_ms persist() end end |
#count ⇒ Object
Number of stored messages.
201 202 203 |
# File 'lib/brute/message_store.rb', line 201 def count @mutex.synchronize { @messages.size } end |
#error_tool_part(message_id:, call_id:, error:) ⇒ Object
Mark a tool part as errored.
135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/brute/message_store.rb', line 135 def error_tool_part(message_id:, call_id:, error:) @mutex.synchronize do msg = @messages[] return unless msg part = msg[:parts].find { |p| p[:type] == "tool" && p[:callID] == call_id } return unless part part[:state][:status] = "error" part[:state][:error] = error.to_s part[:state][:time][:end] = now_ms persist() end end |
#message(id) ⇒ Object
Single message by ID.
196 197 198 |
# File 'lib/brute/message_store.rb', line 196 def (id) @mutex.synchronize { @messages[id] } end |
#messages ⇒ Object
All messages in order.
191 192 193 |
# File 'lib/brute/message_store.rb', line 191 def @mutex.synchronize { @messages.values } end |