Class: Legate::Session
- Inherits:
-
Object
- Object
- Legate::Session
- Defined in:
- lib/legate/session.rb
Overview
Represents a single, ongoing conversation thread between a user and an agent system. It holds the history of interactions (Events) and temporary session-specific data (State).
Constant Summary collapse
- VALID_PREFIXES =
%w[user app temp].freeze
Instance Attribute Summary collapse
-
#app_name ⇒ Object
readonly
Returns the value of attribute app_name.
-
#created_at ⇒ Object
readonly
Returns the value of attribute created_at.
-
#id ⇒ Object
readonly
Returns the value of attribute id.
-
#session_service ⇒ Object
Returns the value of attribute session_service.
-
#updated_at ⇒ Object
Returns the value of attribute updated_at.
-
#user_id ⇒ Object
readonly
Returns the value of attribute user_id.
Class Method Summary collapse
-
.from_h(hash) ⇒ Legate::Session
Deserializes session data from a hash into a Session object.
Instance Method Summary collapse
-
#add_event(event) ⇒ Legate::Event?
Adds an event to the session’s history and updates state if needed.
-
#clear_state! ⇒ Object
Clears all key-value pairs from the session state.
-
#delete_state(key) ⇒ Object?
Deletes a key from the session state.
-
#events ⇒ Array<Legate::Event>
Thread-safe accessor for session events.
-
#get_state(key) ⇒ Object?
Gets a value from the session state.
-
#initialize(app_name:, user_id:, id: nil, initial_state: {}, events: [], session_service: nil) ⇒ Session
constructor
Initializes a new session.
-
#set_state(key, value) ⇒ Object
Sets a value in the session state.
-
#state ⇒ Hash
Provides access to the session’s temporary state data.
-
#state_to_h ⇒ Hash
Provides a plain Hash representation of the current state.
-
#to_h ⇒ Hash
Serializes the entire session object to a Hash suitable for JSON conversion.
-
#update_state(hash) ⇒ Object
Merges a hash into the session state.
Constructor Details
#initialize(app_name:, user_id:, id: nil, initial_state: {}, events: [], session_service: nil) ⇒ Session
Initializes a new session. Typically called by a SessionService.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/legate/session.rb', line 26 def initialize(app_name:, user_id:, id: nil, initial_state: {}, events: [], session_service: nil) @id = id || SecureRandom.uuid @app_name = app_name @user_id = user_id @created_at = Time.now.utc # Use UTC @updated_at = @created_at @session_service = session_service @mutex = Mutex.new # Use Concurrent::Map for thread-safe state storage within the session object itself @state = Concurrent::Map.new # Ensure initial_state keys are symbols and manually populate the map initial_state = {} unless initial_state.is_a?(Hash) # Ensure it's a hash symbolized_initial_state = initial_state.transform_keys { |k| begin k.to_sym rescue StandardError k end } symbolized_initial_state.each_pair do |key, value| @state[key] = value end # Events array stores the history, ensure it's mutable if passed and validate contents @events = events.map do |e| if e.is_a?(Legate::Event) e else (Legate.logger.warn("Session Init: Invalid event data skipped: #{e.inspect}") nil) end end.compact Legate.logger.debug("Session initialized: id=#{@id}, app=#{@app_name}, user=#{@user_id}, event_count=#{@events.size}") end |
Instance Attribute Details
#app_name ⇒ Object (readonly)
Returns the value of attribute app_name.
16 17 18 |
# File 'lib/legate/session.rb', line 16 def app_name @app_name end |
#created_at ⇒ Object (readonly)
Returns the value of attribute created_at.
16 17 18 |
# File 'lib/legate/session.rb', line 16 def created_at @created_at end |
#id ⇒ Object (readonly)
Returns the value of attribute id.
16 17 18 |
# File 'lib/legate/session.rb', line 16 def id @id end |
#session_service ⇒ Object
Returns the value of attribute session_service.
17 18 19 |
# File 'lib/legate/session.rb', line 17 def session_service @session_service end |
#updated_at ⇒ Object
Returns the value of attribute updated_at.
17 18 19 |
# File 'lib/legate/session.rb', line 17 def updated_at @updated_at end |
#user_id ⇒ Object (readonly)
Returns the value of attribute user_id.
16 17 18 |
# File 'lib/legate/session.rb', line 16 def user_id @user_id end |
Class Method Details
.from_h(hash) ⇒ Legate::Session
Deserializes session data from a hash into a Session object.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/legate/session.rb', line 202 def self.from_h(hash) sym_hash = hash.transform_keys(&:to_sym) # Ensure keys are symbols events_data = sym_hash[:events] || [] events = events_data.map { |event_hash| Legate::Event.from_h(event_hash.transform_keys(&:to_sym)) }.compact new( id: sym_hash[:id], app_name: sym_hash[:app_name], user_id: sym_hash[:user_id], initial_state: sym_hash[:state] || {}, events: events ).tap do |session| # Set timestamps after initialization session.instance_variable_set(:@created_at, Time.iso8601(sym_hash[:created_at])) if sym_hash[:created_at] session.instance_variable_set(:@updated_at, Time.iso8601(sym_hash[:updated_at])) if sym_hash[:updated_at] end rescue ArgumentError, TypeError => e Legate.logger.error("Session.from_h: Failed to deserialize session data. Error: #{e.}. Data: #{hash.inspect}") nil # Return nil on deserialization error end |
Instance Method Details
#add_event(event) ⇒ Legate::Event?
Adds an event to the session’s history and updates state if needed.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/legate/session.rb', line 76 def add_event(event) return nil unless event.is_a?(Legate::Event) @mutex.synchronize do @events << event @updated_at = Time.now.utc end # Apply the event's state delta OUTSIDE the mutex: update_state may call the # session service (save_scoped_state), and holding the non-reentrant @mutex # across that external call risks deadlock if a service implementation calls # back into the session (e.g. #events/#to_h). @state is a Concurrent::Map, so # it is safe without the lock. update_state(event.state_delta) if event.state_delta && !event.state_delta.empty? Legate.logger.debug("Session #{@id}: Event added - Role: #{event.role}, Tool: #{event.tool_name || 'N/A'}") event end |
#clear_state! ⇒ Object
Clears all key-value pairs from the session state.
167 168 169 170 171 172 173 |
# File 'lib/legate/session.rb', line 167 def clear_state! touch! @state.clear VALID_PREFIXES.each do |prefix| @session_service&.clear_scoped_state(scoped_namespace(prefix), '*') end end |
#delete_state(key) ⇒ Object?
Deletes a key from the session state.
154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/legate/session.rb', line 154 def delete_state(key) prefix, real_key = parse_key(key) validate_prefix!(prefix) if prefix touch! if prefix @session_service&.clear_scoped_state(scoped_namespace(prefix), real_key) else @state.delete(real_key.to_sym) end end |
#events ⇒ Array<Legate::Event>
Thread-safe accessor for session events.
62 63 64 |
# File 'lib/legate/session.rb', line 62 def events @mutex.synchronize { @events.dup.freeze } end |
#get_state(key) ⇒ Object?
Gets a value from the session state.
100 101 102 103 104 105 106 107 108 |
# File 'lib/legate/session.rb', line 100 def get_state(key) prefix, real_key = parse_key(key) validate_prefix!(prefix) if prefix # match set/update/delete: reads and writes agree on what a key means if prefix @session_service&.load_scoped_state(scoped_namespace(prefix), real_key) else @state[real_key.to_sym] end end |
#set_state(key, value) ⇒ Object
Sets a value in the session state.
116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/legate/session.rb', line 116 def set_state(key, value) validate_serializable!(value) prefix, real_key = parse_key(key) validate_prefix!(prefix) if prefix touch! if prefix @session_service&.save_scoped_state(scoped_namespace(prefix), real_key, value) else @state[real_key.to_sym] = value end value end |
#state ⇒ Hash
Provides access to the session’s temporary state data.
68 69 70 71 |
# File 'lib/legate/session.rb', line 68 def state # Ensure external modifications don't affect internal state directly. @state.dup # Return a shallow copy end |
#state_to_h ⇒ Hash
Provides a plain Hash representation of the current state.
177 178 179 180 |
# File 'lib/legate/session.rb', line 177 def state_to_h # Convert Concurrent::Map to a regular Hash Hash[@state.to_enum(:each_pair).to_a] end |
#to_h ⇒ Hash
Serializes the entire session object to a Hash suitable for JSON conversion.
186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/legate/session.rb', line 186 def to_h serialized_events = @mutex.synchronize { @events.map(&:to_h) } { id: @id, app_name: @app_name, user_id: @user_id, created_at: @created_at.iso8601(3), updated_at: @updated_at.iso8601(3), state: state_to_h, events: serialized_events } end |
#update_state(hash) ⇒ Object
Merges a hash into the session state.
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/legate/session.rb', line 134 def update_state(hash) return unless hash.is_a?(Hash) touch! hash.each do |k, v| validate_serializable!(v) prefix, real_key = parse_key(k) validate_prefix!(prefix) if prefix if prefix @session_service&.save_scoped_state(scoped_namespace(prefix), real_key, v) else @state[real_key.to_sym] = v end end end |