Class: Copilot::CopilotSession
- Inherits:
-
Object
- Object
- Copilot::CopilotSession
- Defined in:
- lib/copilot/session.rb
Overview
Represents a single conversation session with the Copilot CLI.
A session maintains conversation state, handles events, and manages tool execution. Sessions are created via Copilot::CopilotClient#create_session or resumed via Copilot::CopilotClient#resume_session.
Instance Attribute Summary collapse
-
#session_id ⇒ String
readonly
The unique session identifier.
-
#workspace_path ⇒ String?
readonly
Workspace path when infinite sessions are enabled.
Instance Method Summary collapse
- #_dispatch_event(event) ⇒ Object private
- #_get_tool_handler(name) ⇒ Object private
- #_handle_hooks_invoke(hook_type, input_data) ⇒ Object private
- #_handle_permission_request(request) ⇒ Object private
- #_handle_user_input_request(params) ⇒ Object private
- #_register_hooks(hooks) ⇒ Object private
- #_register_permission_handler(handler) ⇒ Object private
- #_register_tools(tools) ⇒ Object private
- #_register_user_input_handler(handler) ⇒ Object private
-
#abort ⇒ Object
Abort the currently processing message.
-
#destroy ⇒ Object
Destroy this session and release associated resources.
-
#get_messages ⇒ Array<SessionEvent>
Retrieve all events/messages from this session’s history.
-
#get_metadata ⇒ Hash
Retrieve metadata for this session.
-
#initialize(session_id, rpc_client, workspace_path = nil) ⇒ CopilotSession
constructor
private
A new instance of CopilotSession.
-
#on(event_type = nil, &handler) ⇒ Object
Subscribe to events from this session.
-
#send(prompt:, attachments: nil, mode: nil, response_format: nil, image_options: nil) ⇒ String
Send a message to this session.
-
#send_and_wait(prompt:, attachments: nil, mode: nil, response_format: nil, image_options: nil, timeout: 60) ⇒ SessionEvent?
Send a message and wait until the session becomes idle.
Constructor Details
#initialize(session_id, rpc_client, workspace_path = nil) ⇒ CopilotSession
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns a new instance of CopilotSession.
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/copilot/session.rb', line 36 def initialize(session_id, rpc_client, workspace_path = nil) @session_id = session_id @rpc_client = rpc_client @workspace_path = workspace_path @event_handlers = [] @typed_event_handlers = {} # type => [handler] @event_handlers_lock = Mutex.new @tool_handlers = {} @tool_handlers_lock = Mutex.new @permission_handler = nil @permission_handler_lock = Mutex.new @user_input_handler = nil @user_input_handler_lock = Mutex.new @hooks = nil @hooks_lock = Mutex.new end |
Instance Attribute Details
#session_id ⇒ String (readonly)
Returns the unique session identifier.
27 28 29 |
# File 'lib/copilot/session.rb', line 27 def session_id @session_id end |
#workspace_path ⇒ String? (readonly)
Returns workspace path when infinite sessions are enabled.
30 31 32 |
# File 'lib/copilot/session.rb', line 30 def workspace_path @workspace_path end |
Instance Method Details
#_dispatch_event(event) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/copilot/session.rb', line 215 def _dispatch_event(event) handlers = @event_handlers_lock.synchronize do typed = @typed_event_handlers[event.type]&.dup || [] wildcard = @event_handlers.dup typed + wildcard end handlers.each do |handler| handler.call(event) rescue StandardError => e $stderr.puts("[CopilotSDK] Session event handler error: #{e.class}: #{e.}") end end |
#_get_tool_handler(name) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
244 245 246 |
# File 'lib/copilot/session.rb', line 244 def _get_tool_handler(name) @tool_handlers_lock.synchronize { @tool_handlers[name] } end |
#_handle_hooks_invoke(hook_type, input_data) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/copilot/session.rb', line 290 def _handle_hooks_invoke(hook_type, input_data) hooks = @hooks_lock.synchronize { @hooks } return nil unless hooks handler_map = { "preToolUse" => hooks.on_pre_tool_use, "postToolUse" => hooks.on_post_tool_use, "userPromptSubmitted" => hooks.on_user_prompt_submitted, "sessionStart" => hooks.on_session_start, "sessionEnd" => hooks.on_session_end, "errorOccurred" => hooks.on_error_occurred, } handler = handler_map[hook_type] return nil unless handler begin handler.call(input_data, { session_id: @session_id }) rescue StandardError nil end end |
#_handle_permission_request(request) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/copilot/session.rb', line 254 def (request) handler = @permission_handler_lock.synchronize { @permission_handler } unless handler return { kind: PermissionKind::DENIED_NO_APPROVAL } end begin perm_request = PermissionRequest.from_hash(request) result = handler.call(perm_request, { session_id: @session_id }) result.is_a?(PermissionRequestResult) ? result.to_h : result rescue StandardError { kind: PermissionKind::DENIED_NO_APPROVAL } end end |
#_handle_user_input_request(params) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
275 276 277 278 279 280 281 282 |
# File 'lib/copilot/session.rb', line 275 def _handle_user_input_request(params) handler = @user_input_handler_lock.synchronize { @user_input_handler } raise "User input requested but no handler registered" unless handler request = UserInputRequest.from_hash(params) result = handler.call(request, { session_id: @session_id }) result.is_a?(UserInputResponse) ? result.to_h : result end |
#_register_hooks(hooks) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
285 286 287 |
# File 'lib/copilot/session.rb', line 285 def _register_hooks(hooks) @hooks_lock.synchronize { @hooks = hooks } end |
#_register_permission_handler(handler) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
249 250 251 |
# File 'lib/copilot/session.rb', line 249 def (handler) @permission_handler_lock.synchronize { @permission_handler = handler } end |
#_register_tools(tools) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
230 231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/copilot/session.rb', line 230 def _register_tools(tools) @tool_handlers_lock.synchronize do @tool_handlers.clear return unless tools tools.each do |tool| next unless tool.name && tool.handler @tool_handlers[tool.name] = tool.handler end end end |
#_register_user_input_handler(handler) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
270 271 272 |
# File 'lib/copilot/session.rb', line 270 def _register_user_input_handler(handler) @user_input_handler_lock.synchronize { @user_input_handler = handler } end |
#abort ⇒ Object
Abort the currently processing message.
208 209 210 |
# File 'lib/copilot/session.rb', line 208 def abort @rpc_client.request("session.abort", { sessionId: @session_id }) end |
#destroy ⇒ Object
Destroy this session and release associated resources.
195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/copilot/session.rb', line 195 def destroy @rpc_client.request("session.destroy", { sessionId: @session_id }) @event_handlers_lock.synchronize do @event_handlers.clear @typed_event_handlers.clear end @tool_handlers_lock.synchronize { @tool_handlers.clear } @permission_handler_lock.synchronize { @permission_handler = nil } @user_input_handler_lock.synchronize { @user_input_handler = nil } @hooks_lock.synchronize { @hooks = nil } end |
#get_messages ⇒ Array<SessionEvent>
Retrieve all events/messages from this session’s history.
181 182 183 184 185 |
# File 'lib/copilot/session.rb', line 181 def response = @rpc_client.request("session.getMessages", { sessionId: @session_id }) events = response["events"] || [] events.map { |e| SessionEvent.from_hash(e) } end |
#get_metadata ⇒ Hash
Retrieve metadata for this session.
190 191 192 |
# File 'lib/copilot/session.rb', line 190 def @rpc_client.request("session.getMetadata", { sessionId: @session_id }) end |
#on {|event| ... } ⇒ Proc #on(event_type) {|event| ... } ⇒ Proc
Subscribe to events from this session.
When called with a block only, subscribes to all events. When called with an event type and a block, subscribes to that specific type.
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/copilot/session.rb', line 156 def on(event_type = nil, &handler) raise ArgumentError, "Block required" unless handler @event_handlers_lock.synchronize do if event_type (@typed_event_handlers[event_type] ||= []) << handler else @event_handlers << handler end end -> { @event_handlers_lock.synchronize do if event_type @typed_event_handlers[event_type]&.delete(handler) else @event_handlers.delete(handler) end end } end |
#send(prompt:, attachments: nil, mode: nil, response_format: nil, image_options: nil) ⇒ String
Send a message to this session.
66 67 68 69 70 71 72 73 74 |
# File 'lib/copilot/session.rb', line 66 def send(prompt:, attachments: nil, mode: nil, response_format: nil, image_options: nil) payload = { sessionId: @session_id, prompt: prompt } payload[:attachments] = if payload[:mode] = mode if mode payload[:responseFormat] = response_format if response_format payload[:imageOptions] = .to_h if response = @rpc_client.request("session.send", payload) response["messageId"] end |
#send_and_wait(prompt:, attachments: nil, mode: nil, response_format: nil, image_options: nil, timeout: 60) ⇒ SessionEvent?
Send a message and wait until the session becomes idle.
This is a convenience method that combines #send with waiting for the session.idle event.
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 |
# File 'lib/copilot/session.rb', line 90 def send_and_wait(prompt:, attachments: nil, mode: nil, response_format: nil, image_options: nil, timeout: 60) idle_mutex = Mutex.new idle_cv = ConditionVariable.new idle_fired = false = nil error_event = nil # Register BEFORE send to avoid race condition unsub = on do |event| case event.type when SessionEventType::ASSISTANT_MESSAGE = event when SessionEventType::SESSION_IDLE idle_mutex.synchronize do idle_fired = true idle_cv.signal end when SessionEventType::SESSION_ERROR error_event = RuntimeError.new( event.data.is_a?(Hash) ? event.data["message"] : event.data.to_s ) idle_mutex.synchronize do idle_fired = true idle_cv.signal end end end begin self.send(prompt: prompt, attachments: , mode: mode, response_format: response_format, image_options: ) idle_mutex.synchronize do unless idle_fired idle_cv.wait(idle_mutex, timeout) end end raise error_event if error_event unless idle_fired raise Timeout::Error, "Timeout after #{timeout}s waiting for session.idle" end ensure unsub.call end end |