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_exit_plan_mode_request(params) ⇒ 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_exit_plan_mode_handler(handler) ⇒ Object private
- #_register_hooks(hooks) ⇒ Object private
- #_register_permission_handler(handler) ⇒ Object private
- #_register_tools(tools) ⇒ Object private
- #_register_trace_context_provider(provider) ⇒ 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 57 58 59 60 61 |
# 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 @exit_plan_mode_handler = nil @exit_plan_mode_handler_lock = Mutex.new @trace_context_provider = nil 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.
238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/copilot/session.rb', line 238 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.
267 268 269 |
# File 'lib/copilot/session.rb', line 267 def _get_tool_handler(name) @tool_handlers_lock.synchronize { @tool_handlers[name] } end |
#_handle_exit_plan_mode_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.
318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/copilot/session.rb', line 318 def _handle_exit_plan_mode_request(params) handler = @exit_plan_mode_handler_lock.synchronize { @exit_plan_mode_handler } unless handler return { approved: true } end begin request = ExitPlanModeRequest.from_hash(params) result = handler.call(request) result.is_a?(ExitPlanModeResponse) ? result.to_h : result rescue StandardError { approved: true } end 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.
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/copilot/session.rb', line 339 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.
277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/copilot/session.rb', line 277 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.
298 299 300 301 302 303 304 305 |
# File 'lib/copilot/session.rb', line 298 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_exit_plan_mode_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.
313 314 315 |
# File 'lib/copilot/session.rb', line 313 def _register_exit_plan_mode_handler(handler) @exit_plan_mode_handler_lock.synchronize { @exit_plan_mode_handler = handler } 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.
308 309 310 |
# File 'lib/copilot/session.rb', line 308 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.
272 273 274 |
# File 'lib/copilot/session.rb', line 272 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.
253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/copilot/session.rb', line 253 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_trace_context_provider(provider) ⇒ 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.
334 335 336 |
# File 'lib/copilot/session.rb', line 334 def _register_trace_context_provider(provider) @trace_context_provider = provider 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.
293 294 295 |
# File 'lib/copilot/session.rb', line 293 def _register_user_input_handler(handler) @user_input_handler_lock.synchronize { @user_input_handler = handler } end |
#abort ⇒ Object
Abort the currently processing message.
231 232 233 |
# File 'lib/copilot/session.rb', line 231 def abort @rpc_client.request("session.abort", { sessionId: @session_id }) end |
#destroy ⇒ Object
Destroy this session and release associated resources.
216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/copilot/session.rb', line 216 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 } @exit_plan_mode_handler_lock.synchronize { @exit_plan_mode_handler = nil } @trace_context_provider = nil end |
#get_messages ⇒ Array<SessionEvent>
Retrieve all events/messages from this session’s history.
202 203 204 205 206 |
# File 'lib/copilot/session.rb', line 202 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.
211 212 213 |
# File 'lib/copilot/session.rb', line 211 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.
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/copilot/session.rb', line 177 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.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/copilot/session.rb', line 71 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 # Inject trace context if provider is available if @trace_context_provider begin tc = @trace_context_provider.call if tc tp = tc.respond_to?(:traceparent) ? tc.traceparent : tc[:traceparent] ts = tc.respond_to?(:tracestate) ? tc.tracestate : tc[:tracestate] payload[:traceparent] = tp if tp payload[:tracestate] = ts if ts end rescue StandardError # ignore trace context errors end end 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.
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 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/copilot/session.rb', line 111 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 |