Class: Legate::Event
- Inherits:
-
Struct
- Object
- Struct
- Legate::Event
- Defined in:
- lib/legate/event.rb
Overview
Represents a single interaction or step within a Session’s history. Immutable object after creation.
Instance Attribute Summary collapse
-
#content ⇒ String, Hash
readonly
The payload of the event (e.g., user text, agent text, tool params, tool result hash).
-
#event_id ⇒ String
readonly
A unique ID for this specific event instance.
-
#role ⇒ Symbol
readonly
The origin of the event (:user, :agent, :tool_request, :tool_result).
-
#state_delta ⇒ Hash?
readonly
Optional hash representing state changes associated with this event.
-
#timestamp ⇒ Time
readonly
The UTC time the event occurred.
-
#tool_name ⇒ Symbol?
readonly
The name of the tool involved (for :tool_request, :tool_result roles).
Class Method Summary collapse
-
.from_h(hash) ⇒ Legate::Event
Basic deserialization from a hash (e.g., after reading from JSON).
Instance Method Summary collapse
-
#answer ⇒ Object?
The successful result value (nil on error).
-
#error? ⇒ Boolean
True if this carries an error result.
-
#error_message ⇒ String?
The error message, or nil when not an error.
-
#final_agent_response? ⇒ Boolean
Helper to check if the event represents a final agent response to the user.
-
#initialize(role:, content:, timestamp: nil, tool_name: nil, state_delta: nil, event_id: nil) ⇒ Event
constructor
A new instance of Event.
-
#success? ⇒ Boolean
True if this carries a successful result.
-
#to_h ⇒ Hash
Basic serialization for storage (e.g., in Redis).
Constructor Details
#initialize(role:, content:, timestamp: nil, tool_name: nil, state_delta: nil, event_id: nil) ⇒ Event
Returns a new instance of Event.
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 |
# File 'lib/legate/event.rb', line 31 def initialize(role:, content:, timestamp: nil, tool_name: nil, state_delta: nil, event_id: nil) # Basic validation raise ArgumentError, "Invalid role: #{role}. Must be :user, :agent, :tool_request, or :tool_result." unless %i[user agent tool_request tool_result].include?(role) Legate.logger.warn("Event: :#{role} event created without a valid :tool_name symbol.") if %i[tool_request tool_result].include?(role) && (tool_name.nil? || !tool_name.is_a?(Symbol)) # Validate state_delta is a Hash or nil unless state_delta.nil? || state_delta.is_a?(Hash) Legate.logger.warn("Event: :state_delta must be a Hash or nil, received #{state_delta.class}.") state_delta = nil # Force to nil if invalid end # Ensure content is somewhat reasonable (avoids deep inspection for performance) Legate.logger.warn("Event: Content is of unusual type (#{content.class}): #{content.inspect}") unless content.is_a?(String) || content.is_a?(Hash) || content.is_a?(Array) || content.is_a?(NilClass) || content.is_a?(Numeric) || content.is_a?(TrueClass) || content.is_a?(FalseClass) super( role: role, content: deep_freeze(content), timestamp: || Time.now.utc, tool_name: tool_name, state_delta: deep_freeze(state_delta&.transform_keys(&:to_sym)), event_id: event_id || SecureRandom.uuid ) freeze end |
Instance Attribute Details
#content ⇒ String, Hash (readonly)
Returns The payload of the event (e.g., user text, agent text, tool params, tool result hash).
24 25 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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/legate/event.rb', line 24 Event = Struct.new(:role, :content, :timestamp, :tool_name, :state_delta, :event_id, keyword_init: true) do # @param role [Symbol] :user, :agent, :tool_request, :tool_result # @param content [String, Hash] Event payload. Should be JSON-serializable. # @param timestamp [Time, nil] Timestamp (defaults to Time.now.utc). # @param tool_name [Symbol, nil] Name of the tool if role is tool related. # @param state_delta [Hash, nil] State changes to apply with this event. # @param event_id [String, nil] Unique event ID (defaults to SecureRandom.uuid). def initialize(role:, content:, timestamp: nil, tool_name: nil, state_delta: nil, event_id: nil) # Basic validation raise ArgumentError, "Invalid role: #{role}. Must be :user, :agent, :tool_request, or :tool_result." unless %i[user agent tool_request tool_result].include?(role) Legate.logger.warn("Event: :#{role} event created without a valid :tool_name symbol.") if %i[tool_request tool_result].include?(role) && (tool_name.nil? || !tool_name.is_a?(Symbol)) # Validate state_delta is a Hash or nil unless state_delta.nil? || state_delta.is_a?(Hash) Legate.logger.warn("Event: :state_delta must be a Hash or nil, received #{state_delta.class}.") state_delta = nil # Force to nil if invalid end # Ensure content is somewhat reasonable (avoids deep inspection for performance) Legate.logger.warn("Event: Content is of unusual type (#{content.class}): #{content.inspect}") unless content.is_a?(String) || content.is_a?(Hash) || content.is_a?(Array) || content.is_a?(NilClass) || content.is_a?(Numeric) || content.is_a?(TrueClass) || content.is_a?(FalseClass) super( role: role, content: deep_freeze(content), timestamp: || Time.now.utc, tool_name: tool_name, state_delta: deep_freeze(state_delta&.transform_keys(&:to_sym)), event_id: event_id || SecureRandom.uuid ) freeze end private def deep_freeze(obj) case obj when Hash obj.each_value { |v| deep_freeze(v) } obj.freeze when Array obj.each { |v| deep_freeze(v) } obj.freeze when String obj.freeze else obj end end public # Helper to check if the event represents a final agent response to the user. # @return [Boolean] def final_agent_response? role == :agent end # --- Result accessors --- # Convenience readers over the standard { status:, result: / error_message: } # content hash, so callers don't reach into it. Meaningful on a final agent # event (e.g. the return of Agent#ask / #run_task); harmless elsewhere. # @return [Boolean] true if this carries a successful result def success? content.is_a?(Hash) && content[:status] == :success end # @return [Boolean] true if this carries an error result def error? content.is_a?(Hash) && content[:status] == :error end # The successful result value (nil on error). Non-Hash content is returned # as-is (e.g. a scalar result stored directly). # @return [Object, nil] def answer content.is_a?(Hash) ? content[:result] : content end # @return [String, nil] the error message, or nil when not an error def content.is_a?(Hash) ? content[:error_message] : nil end # Basic serialization for storage (e.g., in Redis). # @return [Hash] A hash representation suitable for JSON conversion. def to_h { role: role, content: content, # Assumes content is already JSON-serializable timestamp: .iso8601(3), # Use ISO8601 format with milliseconds tool_name: tool_name, state_delta: state_delta, # Store the hash directly (must be JSON-serializable) event_id: event_id } end # Basic deserialization from a hash (e.g., after reading from JSON). # @param hash [Hash] The hash containing event data (uses symbolized keys). # @return [Legate::Event] A new Event object. def self.from_h(hash) # Optimized: Extract fields manually to avoid full hash allocation via transform_keys role = hash.key?(:role) ? hash[:role] : hash['role'] content = hash.key?(:content) ? hash[:content] : hash['content'] ts_val = hash.key?(:timestamp) ? hash[:timestamp] : hash['timestamp'] tool_name = hash.key?(:tool_name) ? hash[:tool_name] : hash['tool_name'] state_delta = hash.key?(:state_delta) ? hash[:state_delta] : hash['state_delta'] # Validate state_delta type to preserve strict behavior (fail on invalid type) if state_delta && !state_delta.is_a?(Hash) Legate.logger.error("Event.from_h: Type error during deserialization (check state_delta?): state_delta must be a Hash. Hash: #{hash.inspect}") return nil end event_id = hash.key?(:event_id) ? hash[:event_id] : hash['event_id'] new( role: role&.to_sym, content: content, # Safely parse timestamp timestamp: ts_val ? Time.iso8601(ts_val) : Time.now.utc, tool_name: tool_name&.to_sym, # Pass state_delta directly; initialize handles validation and symbolization/copy state_delta: state_delta, event_id: event_id ) rescue ArgumentError => e Legate.logger.error("Event.from_h: Failed to parse timestamp or invalid role: #{e.}. Hash: #{hash.inspect}") # Decide on fallback: return nil, raise, or return partial object? # Returning nil might be safest to signal deserialization failure. nil rescue TypeError, NoMethodError => e # Also rescue NoMethodError Legate.logger.error("Event.from_h: Type error during deserialization (check state_delta?): #{e.}. Hash: #{hash.inspect}") nil end end |
#event_id ⇒ String (readonly)
Returns A unique ID for this specific event instance.
24 25 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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/legate/event.rb', line 24 Event = Struct.new(:role, :content, :timestamp, :tool_name, :state_delta, :event_id, keyword_init: true) do # @param role [Symbol] :user, :agent, :tool_request, :tool_result # @param content [String, Hash] Event payload. Should be JSON-serializable. # @param timestamp [Time, nil] Timestamp (defaults to Time.now.utc). # @param tool_name [Symbol, nil] Name of the tool if role is tool related. # @param state_delta [Hash, nil] State changes to apply with this event. # @param event_id [String, nil] Unique event ID (defaults to SecureRandom.uuid). def initialize(role:, content:, timestamp: nil, tool_name: nil, state_delta: nil, event_id: nil) # Basic validation raise ArgumentError, "Invalid role: #{role}. Must be :user, :agent, :tool_request, or :tool_result." unless %i[user agent tool_request tool_result].include?(role) Legate.logger.warn("Event: :#{role} event created without a valid :tool_name symbol.") if %i[tool_request tool_result].include?(role) && (tool_name.nil? || !tool_name.is_a?(Symbol)) # Validate state_delta is a Hash or nil unless state_delta.nil? || state_delta.is_a?(Hash) Legate.logger.warn("Event: :state_delta must be a Hash or nil, received #{state_delta.class}.") state_delta = nil # Force to nil if invalid end # Ensure content is somewhat reasonable (avoids deep inspection for performance) Legate.logger.warn("Event: Content is of unusual type (#{content.class}): #{content.inspect}") unless content.is_a?(String) || content.is_a?(Hash) || content.is_a?(Array) || content.is_a?(NilClass) || content.is_a?(Numeric) || content.is_a?(TrueClass) || content.is_a?(FalseClass) super( role: role, content: deep_freeze(content), timestamp: || Time.now.utc, tool_name: tool_name, state_delta: deep_freeze(state_delta&.transform_keys(&:to_sym)), event_id: event_id || SecureRandom.uuid ) freeze end private def deep_freeze(obj) case obj when Hash obj.each_value { |v| deep_freeze(v) } obj.freeze when Array obj.each { |v| deep_freeze(v) } obj.freeze when String obj.freeze else obj end end public # Helper to check if the event represents a final agent response to the user. # @return [Boolean] def final_agent_response? role == :agent end # --- Result accessors --- # Convenience readers over the standard { status:, result: / error_message: } # content hash, so callers don't reach into it. Meaningful on a final agent # event (e.g. the return of Agent#ask / #run_task); harmless elsewhere. # @return [Boolean] true if this carries a successful result def success? content.is_a?(Hash) && content[:status] == :success end # @return [Boolean] true if this carries an error result def error? content.is_a?(Hash) && content[:status] == :error end # The successful result value (nil on error). Non-Hash content is returned # as-is (e.g. a scalar result stored directly). # @return [Object, nil] def answer content.is_a?(Hash) ? content[:result] : content end # @return [String, nil] the error message, or nil when not an error def content.is_a?(Hash) ? content[:error_message] : nil end # Basic serialization for storage (e.g., in Redis). # @return [Hash] A hash representation suitable for JSON conversion. def to_h { role: role, content: content, # Assumes content is already JSON-serializable timestamp: .iso8601(3), # Use ISO8601 format with milliseconds tool_name: tool_name, state_delta: state_delta, # Store the hash directly (must be JSON-serializable) event_id: event_id } end # Basic deserialization from a hash (e.g., after reading from JSON). # @param hash [Hash] The hash containing event data (uses symbolized keys). # @return [Legate::Event] A new Event object. def self.from_h(hash) # Optimized: Extract fields manually to avoid full hash allocation via transform_keys role = hash.key?(:role) ? hash[:role] : hash['role'] content = hash.key?(:content) ? hash[:content] : hash['content'] ts_val = hash.key?(:timestamp) ? hash[:timestamp] : hash['timestamp'] tool_name = hash.key?(:tool_name) ? hash[:tool_name] : hash['tool_name'] state_delta = hash.key?(:state_delta) ? hash[:state_delta] : hash['state_delta'] # Validate state_delta type to preserve strict behavior (fail on invalid type) if state_delta && !state_delta.is_a?(Hash) Legate.logger.error("Event.from_h: Type error during deserialization (check state_delta?): state_delta must be a Hash. Hash: #{hash.inspect}") return nil end event_id = hash.key?(:event_id) ? hash[:event_id] : hash['event_id'] new( role: role&.to_sym, content: content, # Safely parse timestamp timestamp: ts_val ? Time.iso8601(ts_val) : Time.now.utc, tool_name: tool_name&.to_sym, # Pass state_delta directly; initialize handles validation and symbolization/copy state_delta: state_delta, event_id: event_id ) rescue ArgumentError => e Legate.logger.error("Event.from_h: Failed to parse timestamp or invalid role: #{e.}. Hash: #{hash.inspect}") # Decide on fallback: return nil, raise, or return partial object? # Returning nil might be safest to signal deserialization failure. nil rescue TypeError, NoMethodError => e # Also rescue NoMethodError Legate.logger.error("Event.from_h: Type error during deserialization (check state_delta?): #{e.}. Hash: #{hash.inspect}") nil end end |
#role ⇒ Symbol (readonly)
Returns The origin of the event (:user, :agent, :tool_request, :tool_result).
24 25 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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/legate/event.rb', line 24 Event = Struct.new(:role, :content, :timestamp, :tool_name, :state_delta, :event_id, keyword_init: true) do # @param role [Symbol] :user, :agent, :tool_request, :tool_result # @param content [String, Hash] Event payload. Should be JSON-serializable. # @param timestamp [Time, nil] Timestamp (defaults to Time.now.utc). # @param tool_name [Symbol, nil] Name of the tool if role is tool related. # @param state_delta [Hash, nil] State changes to apply with this event. # @param event_id [String, nil] Unique event ID (defaults to SecureRandom.uuid). def initialize(role:, content:, timestamp: nil, tool_name: nil, state_delta: nil, event_id: nil) # Basic validation raise ArgumentError, "Invalid role: #{role}. Must be :user, :agent, :tool_request, or :tool_result." unless %i[user agent tool_request tool_result].include?(role) Legate.logger.warn("Event: :#{role} event created without a valid :tool_name symbol.") if %i[tool_request tool_result].include?(role) && (tool_name.nil? || !tool_name.is_a?(Symbol)) # Validate state_delta is a Hash or nil unless state_delta.nil? || state_delta.is_a?(Hash) Legate.logger.warn("Event: :state_delta must be a Hash or nil, received #{state_delta.class}.") state_delta = nil # Force to nil if invalid end # Ensure content is somewhat reasonable (avoids deep inspection for performance) Legate.logger.warn("Event: Content is of unusual type (#{content.class}): #{content.inspect}") unless content.is_a?(String) || content.is_a?(Hash) || content.is_a?(Array) || content.is_a?(NilClass) || content.is_a?(Numeric) || content.is_a?(TrueClass) || content.is_a?(FalseClass) super( role: role, content: deep_freeze(content), timestamp: || Time.now.utc, tool_name: tool_name, state_delta: deep_freeze(state_delta&.transform_keys(&:to_sym)), event_id: event_id || SecureRandom.uuid ) freeze end private def deep_freeze(obj) case obj when Hash obj.each_value { |v| deep_freeze(v) } obj.freeze when Array obj.each { |v| deep_freeze(v) } obj.freeze when String obj.freeze else obj end end public # Helper to check if the event represents a final agent response to the user. # @return [Boolean] def final_agent_response? role == :agent end # --- Result accessors --- # Convenience readers over the standard { status:, result: / error_message: } # content hash, so callers don't reach into it. Meaningful on a final agent # event (e.g. the return of Agent#ask / #run_task); harmless elsewhere. # @return [Boolean] true if this carries a successful result def success? content.is_a?(Hash) && content[:status] == :success end # @return [Boolean] true if this carries an error result def error? content.is_a?(Hash) && content[:status] == :error end # The successful result value (nil on error). Non-Hash content is returned # as-is (e.g. a scalar result stored directly). # @return [Object, nil] def answer content.is_a?(Hash) ? content[:result] : content end # @return [String, nil] the error message, or nil when not an error def content.is_a?(Hash) ? content[:error_message] : nil end # Basic serialization for storage (e.g., in Redis). # @return [Hash] A hash representation suitable for JSON conversion. def to_h { role: role, content: content, # Assumes content is already JSON-serializable timestamp: .iso8601(3), # Use ISO8601 format with milliseconds tool_name: tool_name, state_delta: state_delta, # Store the hash directly (must be JSON-serializable) event_id: event_id } end # Basic deserialization from a hash (e.g., after reading from JSON). # @param hash [Hash] The hash containing event data (uses symbolized keys). # @return [Legate::Event] A new Event object. def self.from_h(hash) # Optimized: Extract fields manually to avoid full hash allocation via transform_keys role = hash.key?(:role) ? hash[:role] : hash['role'] content = hash.key?(:content) ? hash[:content] : hash['content'] ts_val = hash.key?(:timestamp) ? hash[:timestamp] : hash['timestamp'] tool_name = hash.key?(:tool_name) ? hash[:tool_name] : hash['tool_name'] state_delta = hash.key?(:state_delta) ? hash[:state_delta] : hash['state_delta'] # Validate state_delta type to preserve strict behavior (fail on invalid type) if state_delta && !state_delta.is_a?(Hash) Legate.logger.error("Event.from_h: Type error during deserialization (check state_delta?): state_delta must be a Hash. Hash: #{hash.inspect}") return nil end event_id = hash.key?(:event_id) ? hash[:event_id] : hash['event_id'] new( role: role&.to_sym, content: content, # Safely parse timestamp timestamp: ts_val ? Time.iso8601(ts_val) : Time.now.utc, tool_name: tool_name&.to_sym, # Pass state_delta directly; initialize handles validation and symbolization/copy state_delta: state_delta, event_id: event_id ) rescue ArgumentError => e Legate.logger.error("Event.from_h: Failed to parse timestamp or invalid role: #{e.}. Hash: #{hash.inspect}") # Decide on fallback: return nil, raise, or return partial object? # Returning nil might be safest to signal deserialization failure. nil rescue TypeError, NoMethodError => e # Also rescue NoMethodError Legate.logger.error("Event.from_h: Type error during deserialization (check state_delta?): #{e.}. Hash: #{hash.inspect}") nil end end |
#state_delta ⇒ Hash? (readonly)
Returns Optional hash representing state changes associated with this event. Keys should be symbols.
24 25 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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/legate/event.rb', line 24 Event = Struct.new(:role, :content, :timestamp, :tool_name, :state_delta, :event_id, keyword_init: true) do # @param role [Symbol] :user, :agent, :tool_request, :tool_result # @param content [String, Hash] Event payload. Should be JSON-serializable. # @param timestamp [Time, nil] Timestamp (defaults to Time.now.utc). # @param tool_name [Symbol, nil] Name of the tool if role is tool related. # @param state_delta [Hash, nil] State changes to apply with this event. # @param event_id [String, nil] Unique event ID (defaults to SecureRandom.uuid). def initialize(role:, content:, timestamp: nil, tool_name: nil, state_delta: nil, event_id: nil) # Basic validation raise ArgumentError, "Invalid role: #{role}. Must be :user, :agent, :tool_request, or :tool_result." unless %i[user agent tool_request tool_result].include?(role) Legate.logger.warn("Event: :#{role} event created without a valid :tool_name symbol.") if %i[tool_request tool_result].include?(role) && (tool_name.nil? || !tool_name.is_a?(Symbol)) # Validate state_delta is a Hash or nil unless state_delta.nil? || state_delta.is_a?(Hash) Legate.logger.warn("Event: :state_delta must be a Hash or nil, received #{state_delta.class}.") state_delta = nil # Force to nil if invalid end # Ensure content is somewhat reasonable (avoids deep inspection for performance) Legate.logger.warn("Event: Content is of unusual type (#{content.class}): #{content.inspect}") unless content.is_a?(String) || content.is_a?(Hash) || content.is_a?(Array) || content.is_a?(NilClass) || content.is_a?(Numeric) || content.is_a?(TrueClass) || content.is_a?(FalseClass) super( role: role, content: deep_freeze(content), timestamp: || Time.now.utc, tool_name: tool_name, state_delta: deep_freeze(state_delta&.transform_keys(&:to_sym)), event_id: event_id || SecureRandom.uuid ) freeze end private def deep_freeze(obj) case obj when Hash obj.each_value { |v| deep_freeze(v) } obj.freeze when Array obj.each { |v| deep_freeze(v) } obj.freeze when String obj.freeze else obj end end public # Helper to check if the event represents a final agent response to the user. # @return [Boolean] def final_agent_response? role == :agent end # --- Result accessors --- # Convenience readers over the standard { status:, result: / error_message: } # content hash, so callers don't reach into it. Meaningful on a final agent # event (e.g. the return of Agent#ask / #run_task); harmless elsewhere. # @return [Boolean] true if this carries a successful result def success? content.is_a?(Hash) && content[:status] == :success end # @return [Boolean] true if this carries an error result def error? content.is_a?(Hash) && content[:status] == :error end # The successful result value (nil on error). Non-Hash content is returned # as-is (e.g. a scalar result stored directly). # @return [Object, nil] def answer content.is_a?(Hash) ? content[:result] : content end # @return [String, nil] the error message, or nil when not an error def content.is_a?(Hash) ? content[:error_message] : nil end # Basic serialization for storage (e.g., in Redis). # @return [Hash] A hash representation suitable for JSON conversion. def to_h { role: role, content: content, # Assumes content is already JSON-serializable timestamp: .iso8601(3), # Use ISO8601 format with milliseconds tool_name: tool_name, state_delta: state_delta, # Store the hash directly (must be JSON-serializable) event_id: event_id } end # Basic deserialization from a hash (e.g., after reading from JSON). # @param hash [Hash] The hash containing event data (uses symbolized keys). # @return [Legate::Event] A new Event object. def self.from_h(hash) # Optimized: Extract fields manually to avoid full hash allocation via transform_keys role = hash.key?(:role) ? hash[:role] : hash['role'] content = hash.key?(:content) ? hash[:content] : hash['content'] ts_val = hash.key?(:timestamp) ? hash[:timestamp] : hash['timestamp'] tool_name = hash.key?(:tool_name) ? hash[:tool_name] : hash['tool_name'] state_delta = hash.key?(:state_delta) ? hash[:state_delta] : hash['state_delta'] # Validate state_delta type to preserve strict behavior (fail on invalid type) if state_delta && !state_delta.is_a?(Hash) Legate.logger.error("Event.from_h: Type error during deserialization (check state_delta?): state_delta must be a Hash. Hash: #{hash.inspect}") return nil end event_id = hash.key?(:event_id) ? hash[:event_id] : hash['event_id'] new( role: role&.to_sym, content: content, # Safely parse timestamp timestamp: ts_val ? Time.iso8601(ts_val) : Time.now.utc, tool_name: tool_name&.to_sym, # Pass state_delta directly; initialize handles validation and symbolization/copy state_delta: state_delta, event_id: event_id ) rescue ArgumentError => e Legate.logger.error("Event.from_h: Failed to parse timestamp or invalid role: #{e.}. Hash: #{hash.inspect}") # Decide on fallback: return nil, raise, or return partial object? # Returning nil might be safest to signal deserialization failure. nil rescue TypeError, NoMethodError => e # Also rescue NoMethodError Legate.logger.error("Event.from_h: Type error during deserialization (check state_delta?): #{e.}. Hash: #{hash.inspect}") nil end end |
#timestamp ⇒ Time (readonly)
Returns The UTC time the event occurred.
24 25 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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/legate/event.rb', line 24 Event = Struct.new(:role, :content, :timestamp, :tool_name, :state_delta, :event_id, keyword_init: true) do # @param role [Symbol] :user, :agent, :tool_request, :tool_result # @param content [String, Hash] Event payload. Should be JSON-serializable. # @param timestamp [Time, nil] Timestamp (defaults to Time.now.utc). # @param tool_name [Symbol, nil] Name of the tool if role is tool related. # @param state_delta [Hash, nil] State changes to apply with this event. # @param event_id [String, nil] Unique event ID (defaults to SecureRandom.uuid). def initialize(role:, content:, timestamp: nil, tool_name: nil, state_delta: nil, event_id: nil) # Basic validation raise ArgumentError, "Invalid role: #{role}. Must be :user, :agent, :tool_request, or :tool_result." unless %i[user agent tool_request tool_result].include?(role) Legate.logger.warn("Event: :#{role} event created without a valid :tool_name symbol.") if %i[tool_request tool_result].include?(role) && (tool_name.nil? || !tool_name.is_a?(Symbol)) # Validate state_delta is a Hash or nil unless state_delta.nil? || state_delta.is_a?(Hash) Legate.logger.warn("Event: :state_delta must be a Hash or nil, received #{state_delta.class}.") state_delta = nil # Force to nil if invalid end # Ensure content is somewhat reasonable (avoids deep inspection for performance) Legate.logger.warn("Event: Content is of unusual type (#{content.class}): #{content.inspect}") unless content.is_a?(String) || content.is_a?(Hash) || content.is_a?(Array) || content.is_a?(NilClass) || content.is_a?(Numeric) || content.is_a?(TrueClass) || content.is_a?(FalseClass) super( role: role, content: deep_freeze(content), timestamp: || Time.now.utc, tool_name: tool_name, state_delta: deep_freeze(state_delta&.transform_keys(&:to_sym)), event_id: event_id || SecureRandom.uuid ) freeze end private def deep_freeze(obj) case obj when Hash obj.each_value { |v| deep_freeze(v) } obj.freeze when Array obj.each { |v| deep_freeze(v) } obj.freeze when String obj.freeze else obj end end public # Helper to check if the event represents a final agent response to the user. # @return [Boolean] def final_agent_response? role == :agent end # --- Result accessors --- # Convenience readers over the standard { status:, result: / error_message: } # content hash, so callers don't reach into it. Meaningful on a final agent # event (e.g. the return of Agent#ask / #run_task); harmless elsewhere. # @return [Boolean] true if this carries a successful result def success? content.is_a?(Hash) && content[:status] == :success end # @return [Boolean] true if this carries an error result def error? content.is_a?(Hash) && content[:status] == :error end # The successful result value (nil on error). Non-Hash content is returned # as-is (e.g. a scalar result stored directly). # @return [Object, nil] def answer content.is_a?(Hash) ? content[:result] : content end # @return [String, nil] the error message, or nil when not an error def content.is_a?(Hash) ? content[:error_message] : nil end # Basic serialization for storage (e.g., in Redis). # @return [Hash] A hash representation suitable for JSON conversion. def to_h { role: role, content: content, # Assumes content is already JSON-serializable timestamp: .iso8601(3), # Use ISO8601 format with milliseconds tool_name: tool_name, state_delta: state_delta, # Store the hash directly (must be JSON-serializable) event_id: event_id } end # Basic deserialization from a hash (e.g., after reading from JSON). # @param hash [Hash] The hash containing event data (uses symbolized keys). # @return [Legate::Event] A new Event object. def self.from_h(hash) # Optimized: Extract fields manually to avoid full hash allocation via transform_keys role = hash.key?(:role) ? hash[:role] : hash['role'] content = hash.key?(:content) ? hash[:content] : hash['content'] ts_val = hash.key?(:timestamp) ? hash[:timestamp] : hash['timestamp'] tool_name = hash.key?(:tool_name) ? hash[:tool_name] : hash['tool_name'] state_delta = hash.key?(:state_delta) ? hash[:state_delta] : hash['state_delta'] # Validate state_delta type to preserve strict behavior (fail on invalid type) if state_delta && !state_delta.is_a?(Hash) Legate.logger.error("Event.from_h: Type error during deserialization (check state_delta?): state_delta must be a Hash. Hash: #{hash.inspect}") return nil end event_id = hash.key?(:event_id) ? hash[:event_id] : hash['event_id'] new( role: role&.to_sym, content: content, # Safely parse timestamp timestamp: ts_val ? Time.iso8601(ts_val) : Time.now.utc, tool_name: tool_name&.to_sym, # Pass state_delta directly; initialize handles validation and symbolization/copy state_delta: state_delta, event_id: event_id ) rescue ArgumentError => e Legate.logger.error("Event.from_h: Failed to parse timestamp or invalid role: #{e.}. Hash: #{hash.inspect}") # Decide on fallback: return nil, raise, or return partial object? # Returning nil might be safest to signal deserialization failure. nil rescue TypeError, NoMethodError => e # Also rescue NoMethodError Legate.logger.error("Event.from_h: Type error during deserialization (check state_delta?): #{e.}. Hash: #{hash.inspect}") nil end end |
#tool_name ⇒ Symbol? (readonly)
Returns The name of the tool involved (for :tool_request, :tool_result roles).
24 25 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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/legate/event.rb', line 24 Event = Struct.new(:role, :content, :timestamp, :tool_name, :state_delta, :event_id, keyword_init: true) do # @param role [Symbol] :user, :agent, :tool_request, :tool_result # @param content [String, Hash] Event payload. Should be JSON-serializable. # @param timestamp [Time, nil] Timestamp (defaults to Time.now.utc). # @param tool_name [Symbol, nil] Name of the tool if role is tool related. # @param state_delta [Hash, nil] State changes to apply with this event. # @param event_id [String, nil] Unique event ID (defaults to SecureRandom.uuid). def initialize(role:, content:, timestamp: nil, tool_name: nil, state_delta: nil, event_id: nil) # Basic validation raise ArgumentError, "Invalid role: #{role}. Must be :user, :agent, :tool_request, or :tool_result." unless %i[user agent tool_request tool_result].include?(role) Legate.logger.warn("Event: :#{role} event created without a valid :tool_name symbol.") if %i[tool_request tool_result].include?(role) && (tool_name.nil? || !tool_name.is_a?(Symbol)) # Validate state_delta is a Hash or nil unless state_delta.nil? || state_delta.is_a?(Hash) Legate.logger.warn("Event: :state_delta must be a Hash or nil, received #{state_delta.class}.") state_delta = nil # Force to nil if invalid end # Ensure content is somewhat reasonable (avoids deep inspection for performance) Legate.logger.warn("Event: Content is of unusual type (#{content.class}): #{content.inspect}") unless content.is_a?(String) || content.is_a?(Hash) || content.is_a?(Array) || content.is_a?(NilClass) || content.is_a?(Numeric) || content.is_a?(TrueClass) || content.is_a?(FalseClass) super( role: role, content: deep_freeze(content), timestamp: || Time.now.utc, tool_name: tool_name, state_delta: deep_freeze(state_delta&.transform_keys(&:to_sym)), event_id: event_id || SecureRandom.uuid ) freeze end private def deep_freeze(obj) case obj when Hash obj.each_value { |v| deep_freeze(v) } obj.freeze when Array obj.each { |v| deep_freeze(v) } obj.freeze when String obj.freeze else obj end end public # Helper to check if the event represents a final agent response to the user. # @return [Boolean] def final_agent_response? role == :agent end # --- Result accessors --- # Convenience readers over the standard { status:, result: / error_message: } # content hash, so callers don't reach into it. Meaningful on a final agent # event (e.g. the return of Agent#ask / #run_task); harmless elsewhere. # @return [Boolean] true if this carries a successful result def success? content.is_a?(Hash) && content[:status] == :success end # @return [Boolean] true if this carries an error result def error? content.is_a?(Hash) && content[:status] == :error end # The successful result value (nil on error). Non-Hash content is returned # as-is (e.g. a scalar result stored directly). # @return [Object, nil] def answer content.is_a?(Hash) ? content[:result] : content end # @return [String, nil] the error message, or nil when not an error def content.is_a?(Hash) ? content[:error_message] : nil end # Basic serialization for storage (e.g., in Redis). # @return [Hash] A hash representation suitable for JSON conversion. def to_h { role: role, content: content, # Assumes content is already JSON-serializable timestamp: .iso8601(3), # Use ISO8601 format with milliseconds tool_name: tool_name, state_delta: state_delta, # Store the hash directly (must be JSON-serializable) event_id: event_id } end # Basic deserialization from a hash (e.g., after reading from JSON). # @param hash [Hash] The hash containing event data (uses symbolized keys). # @return [Legate::Event] A new Event object. def self.from_h(hash) # Optimized: Extract fields manually to avoid full hash allocation via transform_keys role = hash.key?(:role) ? hash[:role] : hash['role'] content = hash.key?(:content) ? hash[:content] : hash['content'] ts_val = hash.key?(:timestamp) ? hash[:timestamp] : hash['timestamp'] tool_name = hash.key?(:tool_name) ? hash[:tool_name] : hash['tool_name'] state_delta = hash.key?(:state_delta) ? hash[:state_delta] : hash['state_delta'] # Validate state_delta type to preserve strict behavior (fail on invalid type) if state_delta && !state_delta.is_a?(Hash) Legate.logger.error("Event.from_h: Type error during deserialization (check state_delta?): state_delta must be a Hash. Hash: #{hash.inspect}") return nil end event_id = hash.key?(:event_id) ? hash[:event_id] : hash['event_id'] new( role: role&.to_sym, content: content, # Safely parse timestamp timestamp: ts_val ? Time.iso8601(ts_val) : Time.now.utc, tool_name: tool_name&.to_sym, # Pass state_delta directly; initialize handles validation and symbolization/copy state_delta: state_delta, event_id: event_id ) rescue ArgumentError => e Legate.logger.error("Event.from_h: Failed to parse timestamp or invalid role: #{e.}. Hash: #{hash.inspect}") # Decide on fallback: return nil, raise, or return partial object? # Returning nil might be safest to signal deserialization failure. nil rescue TypeError, NoMethodError => e # Also rescue NoMethodError Legate.logger.error("Event.from_h: Type error during deserialization (check state_delta?): #{e.}. Hash: #{hash.inspect}") nil end end |
Class Method Details
.from_h(hash) ⇒ Legate::Event
Basic deserialization from a hash (e.g., after reading from JSON).
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/legate/event.rb', line 125 def self.from_h(hash) # Optimized: Extract fields manually to avoid full hash allocation via transform_keys role = hash.key?(:role) ? hash[:role] : hash['role'] content = hash.key?(:content) ? hash[:content] : hash['content'] ts_val = hash.key?(:timestamp) ? hash[:timestamp] : hash['timestamp'] tool_name = hash.key?(:tool_name) ? hash[:tool_name] : hash['tool_name'] state_delta = hash.key?(:state_delta) ? hash[:state_delta] : hash['state_delta'] # Validate state_delta type to preserve strict behavior (fail on invalid type) if state_delta && !state_delta.is_a?(Hash) Legate.logger.error("Event.from_h: Type error during deserialization (check state_delta?): state_delta must be a Hash. Hash: #{hash.inspect}") return nil end event_id = hash.key?(:event_id) ? hash[:event_id] : hash['event_id'] new( role: role&.to_sym, content: content, # Safely parse timestamp timestamp: ts_val ? Time.iso8601(ts_val) : Time.now.utc, tool_name: tool_name&.to_sym, # Pass state_delta directly; initialize handles validation and symbolization/copy state_delta: state_delta, event_id: event_id ) rescue ArgumentError => e Legate.logger.error("Event.from_h: Failed to parse timestamp or invalid role: #{e.}. Hash: #{hash.inspect}") # Decide on fallback: return nil, raise, or return partial object? # Returning nil might be safest to signal deserialization failure. nil rescue TypeError, NoMethodError => e # Also rescue NoMethodError Legate.logger.error("Event.from_h: Type error during deserialization (check state_delta?): #{e.}. Hash: #{hash.inspect}") nil end |
Instance Method Details
#answer ⇒ Object?
The successful result value (nil on error). Non-Hash content is returned as-is (e.g. a scalar result stored directly).
100 101 102 |
# File 'lib/legate/event.rb', line 100 def answer content.is_a?(Hash) ? content[:result] : content end |
#error? ⇒ Boolean
Returns true if this carries an error result.
93 94 95 |
# File 'lib/legate/event.rb', line 93 def error? content.is_a?(Hash) && content[:status] == :error end |
#error_message ⇒ String?
Returns the error message, or nil when not an error.
105 106 107 |
# File 'lib/legate/event.rb', line 105 def content.is_a?(Hash) ? content[:error_message] : nil end |
#final_agent_response? ⇒ Boolean
Helper to check if the event represents a final agent response to the user.
78 79 80 |
# File 'lib/legate/event.rb', line 78 def final_agent_response? role == :agent end |
#success? ⇒ Boolean
Returns true if this carries a successful result.
88 89 90 |
# File 'lib/legate/event.rb', line 88 def success? content.is_a?(Hash) && content[:status] == :success end |
#to_h ⇒ Hash
Basic serialization for storage (e.g., in Redis).
111 112 113 114 115 116 117 118 119 120 |
# File 'lib/legate/event.rb', line 111 def to_h { role: role, content: content, # Assumes content is already JSON-serializable timestamp: .iso8601(3), # Use ISO8601 format with milliseconds tool_name: tool_name, state_delta: state_delta, # Store the hash directly (must be JSON-serializable) event_id: event_id } end |