Class: RobotLab::Web::Event

Inherits:
Struct
  • Object
show all
Defined in:
lib/robot_lab/web/event.rb

Overview

Frontend-neutral, immutable value object for a single step in a robot run.

One Event model backs both the persisted transcript and the live SSE stream — serialize with #to_h, rebuild with .from_h. Role validation is kept strict so a bad role fails fast rather than rendering blank.

Roles:

:user        — text the human sent
:delta       — one streamed token/content delta (content is the text fragment)
:robot       — the robot's final reply (content is the full final text)
:tool_call   — a tool invocation (content: { name:, args: })
:tool_result — a tool's return (content: { name:, result:/error: })
:error       — the run raised (content: { class:, message: })

Constant Summary collapse

ROLES =

rubocop:disable Lint/ConstantDefinitionInBlock

%i[user delta robot tool_call tool_result error].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(role:, content:, robot_name: nil, timestamp: nil, event_id: nil) ⇒ Event

Returns a new instance of Event.

Raises:

  • (ArgumentError)


25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/robot_lab/web/event.rb', line 25

def initialize(role:, content:, robot_name: nil, timestamp: nil, event_id: nil)
  role = role.to_sym
  raise ArgumentError, "invalid role: #{role.inspect} (expected one of #{ROLES.inspect})" unless ROLES.include?(role)

  super(
    role: role,
    content: deep_freeze(content),
    robot_name: robot_name,
    timestamp: timestamp || Time.now.utc,
    event_id: event_id || SecureRandom.uuid
  )
  freeze
end

Instance Attribute Details

#contentObject

Returns the value of attribute content

Returns:

  • (Object)

    the current value of content



22
23
24
# File 'lib/robot_lab/web/event.rb', line 22

def content
  @content
end

#event_idObject

Returns the value of attribute event_id

Returns:

  • (Object)

    the current value of event_id



22
23
24
# File 'lib/robot_lab/web/event.rb', line 22

def event_id
  @event_id
end

#robot_nameObject

Returns the value of attribute robot_name

Returns:

  • (Object)

    the current value of robot_name



22
23
24
# File 'lib/robot_lab/web/event.rb', line 22

def robot_name
  @robot_name
end

#roleObject

Returns the value of attribute role

Returns:

  • (Object)

    the current value of role



22
23
24
# File 'lib/robot_lab/web/event.rb', line 22

def role
  @role
end

#timestampObject

Returns the value of attribute timestamp

Returns:

  • (Object)

    the current value of timestamp



22
23
24
# File 'lib/robot_lab/web/event.rb', line 22

def timestamp
  @timestamp
end

Class Method Details

.from_h(hash) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/robot_lab/web/event.rb', line 74

def self.from_h(hash)
  fetch = ->(key) { hash.key?(key) ? hash[key] : hash[key.to_s] }
  ts = fetch.call(:timestamp)
  new(
    role: fetch.call(:role)&.to_sym,
    content: fetch.call(:content),
    robot_name: fetch.call(:robot_name),
    timestamp: ts ? Time.iso8601(ts) : Time.now.utc,
    event_id: fetch.call(:event_id)
  )
rescue ArgumentError
  nil
end

Instance Method Details

#error?Boolean

Returns:

  • (Boolean)


45
46
47
# File 'lib/robot_lab/web/event.rb', line 45

def error?
  role == :error
end

#error_messageObject



49
50
51
52
53
# File 'lib/robot_lab/web/event.rb', line 49

def error_message
  return nil unless error?

  content.is_a?(Hash) ? (content[:message] || content['message']) : content.to_s
end

#textObject

The display text for this event, whatever its role.



56
57
58
59
60
# File 'lib/robot_lab/web/event.rb', line 56

def text
  return content unless content.is_a?(Hash)

  content[:result] || content[:message] || content[:args] || content
end

#to_hObject

--- Serialization (round-trips through JSON for SSE + storage) ---



64
65
66
67
68
69
70
71
72
# File 'lib/robot_lab/web/event.rb', line 64

def to_h
  {
    role: role,
    content: content,
    robot_name: robot_name,
    timestamp: timestamp.iso8601(3),
    event_id: event_id
  }
end

#tool_nameObject

--- Convenience readers so callers never reach into the content hash ---



41
42
43
# File 'lib/robot_lab/web/event.rb', line 41

def tool_name
  content.is_a?(Hash) ? (content[:name] || content['name']) : nil
end