Class: RubyLLM::MCP::Handlers::AsyncResponse

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_llm/mcp/handlers/async_response.rb

Overview

Represents an async response for deferred completion

Constant Summary collapse

VALID_STATES =
%i[pending completed rejected cancelled timed_out].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(elicitation_id:, timeout: nil, timeout_handler: nil) ⇒ AsyncResponse

Initialize async response

Parameters:

  • elicitation_id (String)

    ID of the elicitation

  • timeout (Integer, nil) (defaults to: nil)

    optional timeout in seconds

  • timeout_handler (Proc, Symbol, nil) (defaults to: nil)

    handler for timeout



16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 16

def initialize(elicitation_id:, timeout: nil, timeout_handler: nil)
  @elicitation_id = elicitation_id
  @state = :pending
  @result = nil
  @error = nil
  @mutex = Mutex.new
  @timeout = timeout
  @timeout_handler = timeout_handler
  @completion_callbacks = []
  @created_at = Time.now

  RubyLLM::MCP.logger.debug("AsyncResponse created for #{@elicitation_id} with timeout: #{@timeout || 'none'}")
  schedule_timeout if @timeout
end

Instance Attribute Details

#elicitation_idObject (readonly)

Returns the value of attribute elicitation_id.



8
9
10
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 8

def elicitation_id
  @elicitation_id
end

Instance Method Details

#cancel(reason) ⇒ Object

Cancel the async operation

Parameters:

  • reason (String)

    reason for cancellation



65
66
67
68
69
70
71
72
73
74
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 65

def cancel(reason)
  callbacks_to_execute = nil

  transitioned = transition_state(:cancelled) do
    @error = reason
    callbacks_to_execute = @completion_callbacks.dup
  end

  execute_callbacks_safely(callbacks_to_execute, :cancelled, reason) if transitioned && callbacks_to_execute
end

#cancelled?Boolean

Check if operation is cancelled

Returns:

  • (Boolean)


112
113
114
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 112

def cancelled?
  @mutex.synchronize { @state == :cancelled }
end

#complete(data) ⇒ Object

Complete the async operation with data

Parameters:

  • data (Object)

    the completion data



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 33

def complete(data)
  callbacks_to_execute = nil

  transitioned = transition_state(:completed) do
    @result = data
    callbacks_to_execute = @completion_callbacks.dup
  end

  if transitioned
    duration = Time.now - @created_at
    RubyLLM::MCP.logger.debug("AsyncResponse #{@elicitation_id} completed after #{duration.round(3)}s")
  end

  # Execute callbacks outside mutex to avoid deadlocks
  execute_callbacks_safely(callbacks_to_execute, :completed, data) if transitioned && callbacks_to_execute
end

#completed?Boolean

Check if operation is completed

Returns:

  • (Boolean)


102
103
104
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 102

def completed?
  @mutex.synchronize { @state == :completed }
end

#errorObject



134
135
136
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 134

def error
  @mutex.synchronize { @error }
end

#finished?Boolean

Check if operation is finished (any terminal state)

Returns:

  • (Boolean)


122
123
124
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 122

def finished?
  !pending?
end

#on_complete(&callback) ⇒ Object

Register a callback for when operation completes/fails

Parameters:

  • callback (Proc)

    callback to execute



90
91
92
93
94
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 90

def on_complete(&callback)
  @mutex.synchronize do
    @completion_callbacks << callback
  end
end

#pending?Boolean

Check if operation is pending

Returns:

  • (Boolean)


97
98
99
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 97

def pending?
  @mutex.synchronize { @state == :pending }
end

#reject(reason) ⇒ Object

Reject the async operation

Parameters:

  • reason (String)

    reason for rejection



52
53
54
55
56
57
58
59
60
61
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 52

def reject(reason)
  callbacks_to_execute = nil

  transitioned = transition_state(:rejected) do
    @error = reason
    callbacks_to_execute = @completion_callbacks.dup
  end

  execute_callbacks_safely(callbacks_to_execute, :rejected, reason) if transitioned && callbacks_to_execute
end

#rejected?Boolean

Check if operation is rejected

Returns:

  • (Boolean)


107
108
109
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 107

def rejected?
  @mutex.synchronize { @state == :rejected }
end

#resultObject



130
131
132
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 130

def result
  @mutex.synchronize { @result }
end

#stateObject



126
127
128
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 126

def state
  @mutex.synchronize { @state }
end

#timed_out?Boolean

Check if operation timed out

Returns:

  • (Boolean)


117
118
119
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 117

def timed_out?
  @mutex.synchronize { @state == :timed_out }
end

#timeout!Object

Mark as timed out



77
78
79
80
81
82
83
84
85
86
# File 'lib/ruby_llm/mcp/handlers/async_response.rb', line 77

def timeout!
  callbacks_to_execute = nil

  transitioned = transition_state(:timed_out) do
    @error = "Operation timed out"
    callbacks_to_execute = @completion_callbacks.dup
  end

  execute_callbacks_safely(callbacks_to_execute, :timed_out, @error) if transitioned && callbacks_to_execute
end