Class: Ask::AppServer::EventTranslator

Inherits:
Object
  • Object
show all
Defined in:
lib/ask/app_server/event_translator.rb

Overview

Translates ask-agent runtime events into canonical Ask::SessionProtocol events (the contract between the host and every client). Every emitted event is validated against the protocol registry, so a malformed translation fails fast at the boundary.

The translator owns the per-session event buffer and sequence numbers: clients poll (session/events after seq N) or subscribe (the server pushes drained events as session/event notifications).

Constant Summary collapse

MAX_EVENTS =

Events retained per session for replay/polling. Cursor-based delivery means the log is append-only; clients with cursors older than the cap miss the dropped events (durable log is a host storage concern).

2000

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeEventTranslator

Returns a new instance of EventTranslator.



28
29
30
31
32
33
34
35
# File 'lib/ask/app_server/event_translator.rb', line 28

def initialize
  @events = []
  @seq = 0
  @turn_id = nil
  @turn_active = false
  @streaming_text = +""
  @plan_interaction_id = nil
end

Instance Attribute Details

#on_eventObject

Optional observer called with every emitted canonical Event. This is the single choke point for all session events (translations plus approval/plan/session-lifecycle emissions), so host-side side effects (e.g. the pane reporter) hook here.



26
27
28
# File 'lib/ask/app_server/event_translator.rb', line 26

def on_event
  @on_event
end

Instance Method Details

#approval_required(action) ⇒ Object

An approval action was queued: emit approval.required.

Parameters:

  • action (Ask::Agent::ApprovalQueue::Action)


86
87
88
89
90
91
92
# File 'lib/ask/app_server/event_translator.rb', line 86

def approval_required(action)
  payload = { "toolName" => action.tool_name.to_s }
  payload["args"] = action.args if action.args
  payload["message"] = action.message if action.message
  payload["autoApprovable"] = action.auto_approvable unless action.auto_approvable.nil?
  emit("approval.required", payload.merge("id" => "act_#{action.id}"))
end

#approval_updated(action) ⇒ Object

An approval action changed status: emit approval.updated.

Parameters:

  • action (Ask::Agent::ApprovalQueue::Action)


97
98
99
100
101
102
# File 'lib/ask/app_server/event_translator.rb', line 97

def approval_updated(action)
  status = action.status.to_s
  return unless %w[approved rejected].include?(status)

  emit("approval.updated", { "id" => "act_#{action.id}", "status" => status })
end

#drain_eventsObject

Drain and return all pending events, clearing the buffer.



122
123
124
125
126
# File 'lib/ask/app_server/event_translator.rb', line 122

def drain_events
  evs = @events
  @events = []
  evs
end

#events_after(after_seq) ⇒ Object

Events after a given sequence number.



134
135
136
# File 'lib/ask/app_server/event_translator.rb', line 134

def events_after(after_seq)
  @events.select { |e| e.seq > after_seq }
end

#last_seqObject

The last sequence number we emitted.



129
130
131
# File 'lib/ask/app_server/event_translator.rb', line 129

def last_seq
  @seq
end

#pending_eventsObject

All events since the last drain.



117
118
119
# File 'lib/ask/app_server/event_translator.rb', line 117

def pending_events
  @events
end

#session_created(session_id) ⇒ Object

A session was created: emit session.created.



105
106
107
# File 'lib/ask/app_server/event_translator.rb', line 105

def session_created(session_id)
  emit("session.created", { "sessionId" => session_id })
end

#session_ended(session_id, reason: "closed") ⇒ Object

A session ended: emit session.ended.



110
111
112
# File 'lib/ask/app_server/event_translator.rb', line 110

def session_ended(session_id, reason: "closed")
  emit("session.ended", { "sessionId" => session_id, "reason" => reason })
end

#translate(agent_event) ⇒ Object

Translate one ask-agent event into zero or more canonical events. Returns an array of Ask::SessionProtocol::Events::Event (may be empty); events are buffered for delivery.



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
# File 'lib/ask/app_server/event_translator.rb', line 40

def translate(agent_event)
  case agent_event
  when Ask::Agent::Events::TurnStart
    turn_started
  when Ask::Agent::Events::TextDelta
    text_delta(agent_event)
  when Ask::Agent::Events::ThinkingDelta
    thinking_delta(agent_event)
  when Ask::Agent::Events::ToolCallDelta
    # Informational; execution events carry the tool lifecycle.
    []
  when Ask::Agent::Events::ToolExecutionStart
    tool_start(agent_event)
  when Ask::Agent::Events::ToolExecutionUpdate
    tool_update(agent_event)
  when Ask::Agent::Events::ToolExecutionEnd
    tool_end(agent_event)
  when Ask::Agent::Events::TodoUpdated
    todos_updated(agent_event)
  when Ask::Agent::Events::PlanProposed
    plan_proposed(agent_event)
  when Ask::Agent::Events::PlanApproved
    plan_approved(agent_event)
  when Ask::Agent::Events::PlanRejected
    plan_rejected(agent_event)
  when Ask::Agent::Events::MaxTurnsExceeded
    turn_failed("Max turns exceeded (#{agent_event.max_turns})")
  when Ask::Agent::Events::LoopDetected
    turn_failed("Loop detected on tool: #{agent_event.tool_name}")
  when Ask::Agent::Events::Error
    error(agent_event)
  when Ask::Agent::Events::SessionEnd
    session_end(agent_event)
  else
    # TurnEnd, MessageStart/End, Reflection*, Compaction*,
    # SessionRolledBack/Forked, Evaluation*, MetaAgentAnalysis —
    # internal detail, no canonical representation.
    []
  end
end