Class: Ask::ACP::ReplayClient

Inherits:
Object
  • Object
show all
Defined in:
lib/ask/acp/replay_client.rb

Overview

ACP client that replays pre-recorded interactions from a fixture file.

Like VCR for stdio — no subprocess, instant response, deterministic. Records are newline-delimited JSON with format:

{"request": {...}}    ← request sent to agent
{"response": {...}}   ← response from agent (or mock)
{"notification": {...}} ← async notification from agent

Examples:

client = Ask::ACP::ReplayClient.new(fixture: "test/fixtures/opencode_session.jsonl")
client.start
client.initialize!(client_name: "test", client_version: "0.1.0")

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(fixture_path:) ⇒ ReplayClient

Returns a new instance of ReplayClient.



22
23
24
25
26
27
28
29
30
31
32
# File 'lib/ask/acp/replay_client.rb', line 22

def initialize(fixture_path:)
  @fixture_path = fixture_path
  @events = []
  @index = 0
  @running = false
  @initialized = false
  @event_handlers = []
  @pending = {}
  @next_id = 1
  @mutex = Mutex.new
end

Instance Attribute Details

#fixture_pathObject (readonly)

Returns the value of attribute fixture_path.



20
21
22
# File 'lib/ask/acp/replay_client.rb', line 20

def fixture_path
  @fixture_path
end

#runningObject (readonly)

Returns the value of attribute running.



20
21
22
# File 'lib/ask/acp/replay_client.rb', line 20

def running
  @running
end

Instance Method Details

#initialize!(client_name:, client_version:, capabilities: {}) ⇒ Object

── ACP Methods (same interface as Client) ──



54
55
56
57
58
59
# File 'lib/ask/acp/replay_client.rb', line 54

def initialize!(client_name:, client_version:, capabilities: {})
  @initialized = true
  make_request("initialize", {
    protocolVersion: 1, clientInfo: { name: client_name, version: client_version }, capabilities: capabilities
  })
end

#on_notification(&handler) ⇒ Object



48
49
50
# File 'lib/ask/acp/replay_client.rb', line 48

def on_notification(&handler)
  @mutex.synchronize { @event_handlers << handler }
end

#running?Boolean

Returns:

  • (Boolean)


44
45
46
# File 'lib/ask/acp/replay_client.rb', line 44

def running?
  @running
end

#session_close(session_id) ⇒ Object



88
89
90
91
# File 'lib/ask/acp/replay_client.rb', line 88

def session_close(session_id)
  ensure_initialized!
  make_request(Protocol::AGENT_METHODS[:session_close], { sessionId: session_id })
end

#session_list(cwd: nil) ⇒ Object



74
75
76
77
78
79
80
# File 'lib/ask/acp/replay_client.rb', line 74

def session_list(cwd: nil)
  ensure_initialized!
  params = {}
  params[:cwd] = cwd if cwd
  result = make_request(Protocol::AGENT_METHODS[:session_list], params)
  (result["sessions"] || result[:sessions] || []).map { |s| normalize_session(s) }
end

#session_load(session_id) ⇒ Object



68
69
70
71
72
# File 'lib/ask/acp/replay_client.rb', line 68

def session_load(session_id)
  ensure_initialized!
  result = make_request(Protocol::AGENT_METHODS[:session_load], { sessionId: session_id })
  normalize_session(result)
end

#session_new(cwd: ".", model: nil) ⇒ Object



61
62
63
64
65
66
# File 'lib/ask/acp/replay_client.rb', line 61

def session_new(cwd: ".", model: nil)
  ensure_initialized!
  params = { cwd: cwd, mcpServers: [] }
  params[:model] = model if model
  normalize_session(make_request(Protocol::AGENT_METHODS[:session_new], params))
end

#session_prompt(session_id, prompt, timeout: nil, &block) ⇒ Object



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
# File 'lib/ask/acp/replay_client.rb', line 93

def session_prompt(session_id, prompt, timeout: nil, &block)
  ensure_initialized!
  prompt_blocks = prompt.is_a?(Array) ? prompt : [{ type: "text", text: prompt.to_s }]
  params = { sessionId: session_id, prompt: prompt_blocks }

  # Same block form as Client: a temporary handler that streams events
  # as { method:, params: } before the response arrives.
  handler = nil
  if block
    handler = ->(msg) { block.call(method: msg["method"], params: msg["params"] || {}) if msg["method"] }
    on_notification(&handler)
  end

  begin
    # Dispatch any notifications before the response
    while @index < @events.length
      event = @events[@index]
      break if event.key?("response")
      if event.key?("notification")
        dispatch_event(event["notification"])
      end
      @index += 1
    end

    # Get the response
    event = @events[@index]
    @index += 1
    if event && event["response"]
      result = event["response"]["result"]
      error = event["response"]["error"]
      raise Error.new("[#{error["code"]}] #{error["message"]}") if error
      result
    else
      { "status" => "completed" }
    end
  ensure
    @mutex.synchronize { @event_handlers.delete(handler) } if handler
  end
end

#session_resume(session_id) ⇒ Object



82
83
84
85
86
# File 'lib/ask/acp/replay_client.rb', line 82

def session_resume(session_id)
  ensure_initialized!
  result = make_request(Protocol::AGENT_METHODS[:session_resume], { sessionId: session_id })
  normalize_session(result)
end

#startObject



34
35
36
37
# File 'lib/ask/acp/replay_client.rb', line 34

def start
  load_fixture
  @running = true
end

#stopObject



39
40
41
42
# File 'lib/ask/acp/replay_client.rb', line 39

def stop
  @running = false
  @initialized = false
end