nightona 0.191.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +22 -0
  4. data/.ruby-version +1 -0
  5. data/CODE_OF_CONDUCT.md +132 -0
  6. data/LICENSE +190 -0
  7. data/README.md +184 -0
  8. data/Rakefile +12 -0
  9. data/lib/nightona/code_interpreter.rb +359 -0
  10. data/lib/nightona/common/charts.rb +124 -0
  11. data/lib/nightona/common/code_interpreter.rb +56 -0
  12. data/lib/nightona/common/code_language.rb +14 -0
  13. data/lib/nightona/common/file_system.rb +26 -0
  14. data/lib/nightona/common/git.rb +19 -0
  15. data/lib/nightona/common/image.rb +500 -0
  16. data/lib/nightona/common/nightona.rb +230 -0
  17. data/lib/nightona/common/process.rb +149 -0
  18. data/lib/nightona/common/pty.rb +309 -0
  19. data/lib/nightona/common/resources.rb +39 -0
  20. data/lib/nightona/common/response.rb +83 -0
  21. data/lib/nightona/common/snapshot.rb +124 -0
  22. data/lib/nightona/computer_use.rb +919 -0
  23. data/lib/nightona/config.rb +116 -0
  24. data/lib/nightona/file_system.rb +451 -0
  25. data/lib/nightona/file_transfer.rb +383 -0
  26. data/lib/nightona/git.rb +334 -0
  27. data/lib/nightona/lsp_server.rb +139 -0
  28. data/lib/nightona/nightona.rb +336 -0
  29. data/lib/nightona/object_storage.rb +172 -0
  30. data/lib/nightona/otel.rb +183 -0
  31. data/lib/nightona/process.rb +550 -0
  32. data/lib/nightona/sandbox.rb +751 -0
  33. data/lib/nightona/sdk/version.rb +10 -0
  34. data/lib/nightona/sdk.rb +56 -0
  35. data/lib/nightona/snapshot_service.rb +238 -0
  36. data/lib/nightona/util.rb +80 -0
  37. data/lib/nightona/volume.rb +46 -0
  38. data/lib/nightona/volume_service.rb +61 -0
  39. data/lib/nightona.rb +10 -0
  40. data/project.json +100 -0
  41. data/scripts/generate-docs.rb +402 -0
  42. data/sig/nightona/sdk.rbs +6 -0
  43. metadata +242 -0
@@ -0,0 +1,550 @@
1
+ # Copyright Daytona Platforms Inc.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ # frozen_string_literal: true
5
+
6
+ require 'uri'
7
+
8
+ module Nightona
9
+ class Process # rubocop:disable Metrics/ClassLength
10
+ include Instrumentation
11
+
12
+ # @return [String] The ID of the Sandbox
13
+ attr_reader :sandbox_id
14
+
15
+ # @return [NightonaToolboxApiClient::ProcessApi] API client for Sandbox operations
16
+ attr_reader :toolbox_api
17
+
18
+ # @return [Proc] Function to get preview link for a port
19
+ attr_reader :get_preview_link
20
+
21
+ # @return [String] The language for code execution (e.g. 'python', 'typescript', 'javascript')
22
+ attr_reader :language
23
+
24
+ # Initialize a new Process instance
25
+ #
26
+ # @param sandbox_id [String] The ID of the Sandbox
27
+ # @param toolbox_api [NightonaToolboxApiClient::ProcessApi] API client for Sandbox operations
28
+ # @param get_preview_link [Proc] Function to get preview link for a port
29
+ # @param language [String] The language for code execution
30
+ # @param otel_state [Nightona::OtelState, nil]
31
+ def initialize(sandbox_id:, toolbox_api:, get_preview_link:, language: 'python', otel_state: nil)
32
+ @sandbox_id = sandbox_id
33
+ @toolbox_api = toolbox_api
34
+ @get_preview_link = get_preview_link
35
+ @language = language
36
+ @otel_state = otel_state
37
+ end
38
+
39
+ # Execute a shell command in the Sandbox
40
+ #
41
+ # @param command [String] Shell command to execute
42
+ # @param cwd [String, nil] Working directory for command execution. If not specified, uses the sandbox working directory
43
+ # @param env [Hash<String, String>, nil] Environment variables to set for the command
44
+ # @param timeout [Integer, nil] Maximum time in seconds to wait for the command to complete.
45
+ # @return [ExecuteResponse] Command execution results containing exit_code, result, and artifacts
46
+ #
47
+ # @example
48
+ # # Simple command
49
+ # response = sandbox.process.exec("echo 'Hello'")
50
+ # puts response.artifacts.stdout
51
+ # => "Hello\n"
52
+ #
53
+ # # Command with working directory
54
+ # result = sandbox.process.exec("ls", cwd: "workspace/src")
55
+ #
56
+ # # Command with timeout
57
+ # result = sandbox.process.exec("sleep 10", timeout: 5)
58
+ def exec(command:, cwd: nil, env: nil, timeout: nil)
59
+ envs = env&.empty? ? nil : env
60
+
61
+ response = toolbox_api.execute_command(NightonaToolboxApiClient::ExecuteRequest.new(command:, cwd:, envs:,
62
+ timeout:))
63
+ result = response.result || ''
64
+ ExecuteResponse.new(
65
+ exit_code: response.exit_code,
66
+ result:,
67
+ artifacts: ExecutionArtifacts.new(result, [])
68
+ )
69
+ end
70
+
71
+ # Execute code in the Sandbox using the appropriate language runtime
72
+ #
73
+ # @param code [String] Code to execute
74
+ # @param params [CodeRunParams, nil] Parameters for code execution
75
+ # @param timeout [Integer, nil] Maximum time in seconds to wait for the code to complete. 0 means wait indefinitely
76
+ # @return [ExecuteResponse] Code execution result containing exit_code, result, and artifacts
77
+ #
78
+ # @example
79
+ # # Run Python code
80
+ # response = sandbox.process.code_run(<<~CODE)
81
+ # x = 10
82
+ # y = 20
83
+ # print(f"Sum: {x + y}")
84
+ # CODE
85
+ # puts response.artifacts.stdout # Prints: Sum: 30
86
+ def code_run(code:, params: nil, timeout: nil)
87
+ response = toolbox_api.code_run(
88
+ NightonaToolboxApiClient::CodeRunRequest.new(
89
+ code:, language:, argv: params&.argv, envs: params&.env, timeout:
90
+ )
91
+ )
92
+
93
+ ExecuteResponse.new(
94
+ exit_code: response.exit_code,
95
+ result: response.result,
96
+ artifacts: ExecutionArtifacts.new(response.result, (response.artifacts&.charts || []).map do |c|
97
+ Charts.parse_chart(c)
98
+ end)
99
+ )
100
+ end
101
+
102
+ # Creates a new long-running background session in the Sandbox
103
+ #
104
+ # Sessions are background processes that maintain state between commands, making them ideal for
105
+ # scenarios requiring multiple related commands or persistent environment setup.
106
+ #
107
+ # @param session_id [String] Unique identifier for the new session
108
+ # @return [void]
109
+ #
110
+ # @example
111
+ # # Create a new session
112
+ # session_id = "my-session"
113
+ # sandbox.process.create_session(session_id)
114
+ # session = sandbox.process.get_session(session_id)
115
+ # # Do work...
116
+ # sandbox.process.delete_session(session_id)
117
+ def create_session(session_id)
118
+ toolbox_api.create_session(NightonaToolboxApiClient::CreateSessionRequest.new(session_id:))
119
+ end
120
+
121
+ # Gets a session in the Sandbox
122
+ #
123
+ # @param session_id [String] Unique identifier of the session to retrieve
124
+ # @return [NightonaApiClient::Session] Session information including session_id and commands
125
+ #
126
+ # @example
127
+ # session = sandbox.process.get_session("my-session")
128
+ # session.commands.each do |cmd|
129
+ # puts "Command: #{cmd.command}"
130
+ # end
131
+ def get_session(session_id) = toolbox_api.get_session(session_id)
132
+
133
+ # Gets the Sandbox entrypoint session
134
+ #
135
+ # @return [NightonaApiClient::Session] Entrypoint session information including session_id and commands
136
+ #
137
+ # @example
138
+ # session = sandbox.process.get_entrypoint_session()
139
+ # session.commands.each do |cmd|
140
+ # puts "Command: #{cmd.command}"
141
+ # end
142
+ def get_entrypoint_session = toolbox_api.get_entrypoint_session
143
+
144
+ # Gets information about a specific command executed in a session
145
+ #
146
+ # @param session_id [String] Unique identifier of the session
147
+ # @param command_id [String] Unique identifier of the command
148
+ # @return [NightonaApiClient::Command] Command information including id, command, and exit_code
149
+ #
150
+ # @example
151
+ # cmd = sandbox.process.get_session_command(session_id: "my-session", command_id: "cmd-123")
152
+ # if cmd.exit_code == 0
153
+ # puts "Command #{cmd.command} completed successfully"
154
+ # end
155
+ def get_session_command(session_id:, command_id:)
156
+ toolbox_api.get_session_command(session_id, command_id)
157
+ end
158
+
159
+ # Executes a command in the session
160
+ #
161
+ # @param session_id [String] Unique identifier of the session to use
162
+ # @param req [Nightona::SessionExecuteRequest] Command execution request containing command and run_async
163
+ # @return [Nightona::SessionExecuteResponse] Command execution results containing cmd_id, output, stdout, stderr, and exit_code
164
+ #
165
+ # @example
166
+ # # Execute commands in sequence, maintaining state
167
+ # session_id = "my-session"
168
+ #
169
+ # # Change directory
170
+ # req = Nightona::SessionExecuteRequest.new(command: "cd /workspace")
171
+ # sandbox.process.execute_session_command(session_id:, req:)
172
+ #
173
+ # # Create a file
174
+ # req = Nightona::SessionExecuteRequest.new(command: "echo 'Hello' > test.txt")
175
+ # sandbox.process.execute_session_command(session_id:, req:)
176
+ #
177
+ # # Read the file
178
+ # req = Nightona::SessionExecuteRequest.new(command: "cat test.txt")
179
+ # result = sandbox.process.execute_session_command(session_id:, req:)
180
+ # puts "Command stdout: #{result.stdout}"
181
+ # puts "Command stderr: #{result.stderr}"
182
+ def execute_session_command(session_id:, req:) # rubocop:disable Metrics/MethodLength
183
+ response = toolbox_api.session_execute_command(
184
+ session_id,
185
+ NightonaToolboxApiClient::SessionExecuteRequest.new(command: req.command, run_async: req.run_async,
186
+ suppress_input_echo: req.suppress_input_echo)
187
+ )
188
+
189
+ SessionExecuteResponse.new(
190
+ cmd_id: response.cmd_id,
191
+ output: response.output,
192
+ stdout: response.stdout || '',
193
+ stderr: response.stderr || '',
194
+ exit_code: response.exit_code,
195
+ additional_properties: {}
196
+ )
197
+ end
198
+
199
+ # Get the logs for a command executed in a session
200
+ #
201
+ # @param session_id [String] Unique identifier of the session
202
+ # @param command_id [String] Unique identifier of the command
203
+ # @return [Nightona::SessionCommandLogsResponse] Command logs including output, stdout, and stderr
204
+ #
205
+ # @example
206
+ # logs = sandbox.process.get_session_command_logs(session_id: "my-session", command_id: "cmd-123")
207
+ # puts "Command stdout: #{logs.stdout}"
208
+ # puts "Command stderr: #{logs.stderr}"
209
+ def get_session_command_logs(session_id:, command_id:)
210
+ response = toolbox_api.get_session_command_logs(session_id, command_id)
211
+ SessionCommandLogsResponse.new(output: response.output, stdout: response.stdout, stderr: response.stderr)
212
+ end
213
+
214
+ # Asynchronously retrieves and processes the logs for a command executed in a session as they become available
215
+ #
216
+ # @param session_id [String] Unique identifier of the session
217
+ # @param command_id [String] Unique identifier of the command
218
+ # @param on_stdout [Proc] Callback function to handle stdout log chunks as they arrive
219
+ # @param on_stderr [Proc] Callback function to handle stderr log chunks as they arrive
220
+ # @return [WebSocket::Client::Simple::Client]
221
+ #
222
+ # @example
223
+ # sandbox.process.get_session_command_logs_async(
224
+ # session_id: "my-session",
225
+ # command_id: "cmd-123",
226
+ # on_stdout: ->(log) { puts "[STDOUT]: #{log}" },
227
+ # on_stderr: ->(log) { puts "[STDERR]: #{log}" }
228
+ # )
229
+ def get_session_command_logs_async(session_id:, command_id:, on_stdout:, on_stderr:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
230
+ preview_link = get_preview_link.call(WS_PORT)
231
+ url = URI.parse(preview_link.url)
232
+ url.scheme = url.scheme == 'https' ? 'wss' : 'ws'
233
+ url.path = "/process/session/#{session_id}/command/#{command_id}/logs"
234
+ url.query = 'follow=true'
235
+
236
+ completion_queue = Queue.new
237
+
238
+ WebSocket::Client::Simple.connect(
239
+ url.to_s,
240
+ headers: toolbox_api.api_client.default_headers.dup.merge(
241
+ 'X-Nightona-Preview-Token' => preview_link.token,
242
+ 'Content-Type' => 'text/plain',
243
+ 'Accept' => 'text/plain'
244
+ )
245
+ ) do |client|
246
+ client.on(:message) do |message|
247
+ if message.type == :close
248
+ client.close
249
+ completion_queue.push(:close)
250
+ else
251
+ stdout, stderr = Util.demux(message.data.to_s)
252
+
253
+ on_stdout.call(stdout) unless stdout.empty?
254
+ on_stderr.call(stderr) unless stderr.empty?
255
+ end
256
+ end
257
+
258
+ client.on(:close) do
259
+ completion_queue.push(:close)
260
+ end
261
+
262
+ client.on(:error) do |e|
263
+ completion_queue.push(:error)
264
+ raise Sdk::Error, "WebSocket error: #{e.message}"
265
+ end
266
+ end
267
+
268
+ # Wait for completion
269
+ completion_queue.pop
270
+ end
271
+
272
+ # Get the sandbox entrypoint logs
273
+ #
274
+ # @return [Nightona::SessionCommandLogsResponse] Entrypoint logs including output, stdout, and stderr
275
+ #
276
+ # @example
277
+ # logs = sandbox.process.get_entrypoint_logs()
278
+ # puts "Command stdout: #{logs.stdout}"
279
+ # puts "Command stderr: #{logs.stderr}"
280
+ def get_entrypoint_logs
281
+ response = toolbox_api.get_entrypoint_logs
282
+ SessionCommandLogsResponse.new(output: response.output, stdout: response.stdout, stderr: response.stderr)
283
+ end
284
+
285
+ # Asynchronously retrieves and processes the sandbox entrypoint logs as they become available
286
+ #
287
+ # @param on_stdout [Proc] Callback function to handle stdout log chunks as they arrive
288
+ # @param on_stderr [Proc] Callback function to handle stderr log chunks as they arrive
289
+ # @return [WebSocket::Client::Simple::Client]
290
+ #
291
+ # @example
292
+ # sandbox.process.get_entrypoint_logs_async(
293
+ # on_stdout: ->(log) { puts "[STDOUT]: #{log}" },
294
+ # on_stderr: ->(log) { puts "[STDERR]: #{log}" }
295
+ # )
296
+ def get_entrypoint_logs_async(on_stdout:, on_stderr:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
297
+ preview_link = get_preview_link.call(WS_PORT)
298
+ url = URI.parse(preview_link.url)
299
+ url.scheme = url.scheme == 'https' ? 'wss' : 'ws'
300
+ url.path = '/process/session/entrypoint/logs'
301
+ url.query = 'follow=true'
302
+
303
+ completion_queue = Queue.new
304
+
305
+ WebSocket::Client::Simple.connect(
306
+ url.to_s,
307
+ headers: toolbox_api.api_client.default_headers.dup.merge(
308
+ 'X-Nightona-Preview-Token' => preview_link.token,
309
+ 'Content-Type' => 'text/plain',
310
+ 'Accept' => 'text/plain'
311
+ )
312
+ ) do |client|
313
+ client.on(:message) do |message|
314
+ if message.type == :close
315
+ client.close
316
+ completion_queue.push(:close)
317
+ else
318
+ stdout, stderr = Util.demux(message.data.to_s)
319
+
320
+ on_stdout.call(stdout) unless stdout.empty?
321
+ on_stderr.call(stderr) unless stderr.empty?
322
+ end
323
+ end
324
+
325
+ client.on(:close) do
326
+ completion_queue.push(:close)
327
+ end
328
+
329
+ client.on(:error) do |e|
330
+ completion_queue.push(:error)
331
+ raise Sdk::Error, "WebSocket error: #{e.message}"
332
+ end
333
+ end
334
+
335
+ # Wait for completion
336
+ completion_queue.pop
337
+ end
338
+
339
+ # Sends input data to a command executed in a session
340
+ #
341
+ # This method allows you to send input to an interactive command running in a session,
342
+ # such as responding to prompts or providing data to stdin.
343
+ #
344
+ # @param session_id [String] Unique identifier of the session
345
+ # @param command_id [String] Unique identifier of the command
346
+ # @param data [String] Input data to send to the command
347
+ # @return [void]
348
+ def send_session_command_input(session_id:, command_id:, data:)
349
+ toolbox_api.send_input(
350
+ session_id,
351
+ command_id,
352
+ NightonaToolboxApiClient::SessionSendInputRequest.new(data:)
353
+ )
354
+ end
355
+
356
+ #
357
+ # @return [Array<NightonaApiClient::Session>] List of all sessions in the Sandbox
358
+ #
359
+ # @example
360
+ # sessions = sandbox.process.list_sessions
361
+ # sessions.each do |session|
362
+ # puts "Session #{session.session_id}:"
363
+ # puts " Commands: #{session.commands.length}"
364
+ # end
365
+ def list_sessions = toolbox_api.list_sessions
366
+
367
+ # Terminates and removes a session from the Sandbox, cleaning up any resources associated with it
368
+ #
369
+ # @param session_id [String] Unique identifier of the session to delete
370
+ #
371
+ # @example
372
+ # # Create and use a session
373
+ # sandbox.process.create_session("temp-session")
374
+ # # ... use the session ...
375
+ #
376
+ # # Clean up when done
377
+ # sandbox.process.delete_session("temp-session")
378
+ def delete_session(session_id) = toolbox_api.delete_session(session_id)
379
+
380
+ # Creates a new PTY (pseudo-terminal) session in the Sandbox.
381
+ #
382
+ # Creates an interactive terminal session that can execute commands and handle user input.
383
+ # The PTY session behaves like a real terminal, supporting features like command history.
384
+ #
385
+ # @param id [String] Unique identifier for the PTY session. Must be unique within the Sandbox.
386
+ # @param cwd [String, nil] Working directory for the PTY session. Defaults to the sandbox's working directory.
387
+ # @param envs [Hash<String, String>, nil] Environment variables to set in the PTY session. These will be merged with
388
+ # the Sandbox's default environment variables.
389
+ # @param pty_size [PtySize, nil] Terminal size configuration. Defaults to 80x24 if not specified.
390
+ # @return [PtyHandle] Handle for managing the created PTY session. Use this to send input,
391
+ # receive output, resize the terminal, and manage the session lifecycle.
392
+ #
393
+ # @example
394
+ # # Create a basic PTY session
395
+ # pty_handle = sandbox.process.create_pty_session(id: "my-pty")
396
+ #
397
+ # # Create a PTY session with specific size and environment
398
+ # pty_size = Nightona::PtySize.new(rows: 30, cols: 120)
399
+ # pty_handle = sandbox.process.create_pty_session(
400
+ # id: "my-pty",
401
+ # cwd: "/workspace",
402
+ # envs: {"NODE_ENV" => "development"},
403
+ # pty_size: pty_size
404
+ # )
405
+ #
406
+ # # Use the PTY session
407
+ # pty_handle.wait_for_connection
408
+ # pty_handle.send_input("ls -la\n")
409
+ # result = pty_handle.wait
410
+ # pty_handle.disconnect
411
+ #
412
+ # @raise [Nightona::Sdk::Error] If the PTY session creation fails or the session ID is already in use.
413
+ def create_pty_session(id:, cwd: nil, envs: nil, pty_size: nil) # rubocop:disable Metrics/MethodLength
414
+ response = toolbox_api.create_pty_session(
415
+ NightonaToolboxApiClient::PtyCreateRequest.new(
416
+ id:,
417
+ cwd:,
418
+ envs:,
419
+ cols: pty_size&.cols,
420
+ rows: pty_size&.rows,
421
+ lazy_start: true
422
+ )
423
+ )
424
+
425
+ connect_pty_session(response.session_id)
426
+ end
427
+
428
+ # Connects to an existing PTY session in the Sandbox.
429
+ #
430
+ # Establishes a WebSocket connection to an existing PTY session, allowing you to
431
+ # interact with a previously created terminal session.
432
+ #
433
+ # @param session_id [String] Unique identifier of the PTY session to connect to.
434
+ # @return [PtyHandle] Handle for managing the connected PTY session.
435
+ #
436
+ # @example
437
+ # # Connect to an existing PTY session
438
+ # pty_handle = sandbox.process.connect_pty_session("my-pty-session")
439
+ # pty_handle.wait_for_connection
440
+ # pty_handle.send_input("echo 'Hello World'\n")
441
+ # result = pty_handle.wait
442
+ # pty_handle.disconnect
443
+ #
444
+ # @raise [Nightona::Sdk::Error] If the PTY session doesn't exist or connection fails.
445
+ def connect_pty_session(session_id) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
446
+ preview_link = get_preview_link.call(WS_PORT)
447
+ url = URI.parse(preview_link.url)
448
+ url.scheme = url.scheme == 'https' ? 'wss' : 'ws'
449
+ url.path = "/process/pty/#{session_id}/connect"
450
+
451
+ handle = nil
452
+ WebSocket::Client::Simple.connect(
453
+ url.to_s,
454
+ headers: toolbox_api.api_client.default_headers.dup.merge(
455
+ 'X-Nightona-Preview-Token' => preview_link.token
456
+ )
457
+ ) do |client|
458
+ handle = PtyHandle.new(
459
+ client,
460
+ session_id:,
461
+ handle_resize: ->(pty_size) { resize_pty_session(session_id, pty_size) },
462
+ handle_kill: -> { delete_pty_session(session_id) }
463
+ )
464
+ end
465
+ handle.tap(&:wait_for_connection)
466
+ end
467
+
468
+ # Resizes a PTY session to the specified dimensions
469
+ #
470
+ # @param session_id [String] Unique identifier of the PTY session
471
+ # @param pty_size [PtySize] New terminal size
472
+ # @return [NightonaApiClient::PtySessionInfo] Updated PTY session information
473
+ #
474
+ # @example
475
+ # pty_size = Nightona::PtySize.new(rows: 30, cols: 120)
476
+ # session_info = sandbox.process.resize_pty_session("my-pty", pty_size)
477
+ # puts "PTY resized to #{session_info.cols}x#{session_info.rows}"
478
+ def resize_pty_session(session_id, pty_size)
479
+ toolbox_api.resize_pty_session(
480
+ session_id,
481
+ NightonaToolboxApiClient::PtyResizeRequest.new(
482
+ cols: pty_size.cols,
483
+ rows: pty_size.rows
484
+ )
485
+ )
486
+ end
487
+
488
+ # Deletes a PTY session, terminating the associated process
489
+ #
490
+ # @param session_id [String] Unique identifier of the PTY session to delete
491
+ # @return [void]
492
+ #
493
+ # @example
494
+ # sandbox.process.delete_pty_session("my-pty")
495
+ def delete_pty_session(session_id)
496
+ toolbox_api.delete_pty_session(session_id)
497
+ end
498
+
499
+ # Lists all PTY sessions in the Sandbox
500
+ #
501
+ # @return [Array<NightonaApiClient::PtySessionInfo>] List of PTY session information
502
+ #
503
+ # @example
504
+ # sessions = sandbox.process.list_pty_sessions
505
+ # sessions.each do |session|
506
+ # puts "PTY Session #{session.id}: #{session.cols}x#{session.rows}"
507
+ # end
508
+ def list_pty_sessions
509
+ response = toolbox_api.list_pty_sessions
510
+ return [] if response.nil?
511
+
512
+ response.respond_to?(:sessions) ? (response.sessions || []) : response
513
+ end
514
+
515
+ # Gets detailed information about a specific PTY session
516
+ #
517
+ # Retrieves comprehensive information about a PTY session including its current state,
518
+ # configuration, and metadata.
519
+ #
520
+ # @param session_id [String] Unique identifier of the PTY session to retrieve information for
521
+ # @return [NightonaApiClient::PtySessionInfo] Detailed information about the PTY session including ID, state,
522
+ # creation time, working directory, environment variables, and more
523
+ #
524
+ # @example
525
+ # # Get details about a specific PTY session
526
+ # session_info = sandbox.process.get_pty_session_info("my-session")
527
+ # puts "Session ID: #{session_info.id}"
528
+ # puts "Active: #{session_info.active}"
529
+ # puts "Working Directory: #{session_info.cwd}"
530
+ # puts "Terminal Size: #{session_info.cols}x#{session_info.rows}"
531
+ def get_pty_session_info(session_id)
532
+ toolbox_api.get_pty_session(session_id)
533
+ end
534
+
535
+ instrument :exec, :code_run, :create_session, :get_session, :get_session_command,
536
+ :execute_session_command, :get_session_command_logs, :get_session_command_logs_async,
537
+ :send_session_command_input, :list_sessions, :delete_session,
538
+ :create_pty_session, :connect_pty_session, :resize_pty_session,
539
+ :delete_pty_session, :list_pty_sessions, :get_pty_session_info,
540
+ component: 'Process'
541
+
542
+ private
543
+
544
+ # @return [Nightona::OtelState, nil]
545
+ attr_reader :otel_state
546
+
547
+ WS_PORT = 2280
548
+ private_constant :WS_PORT
549
+ end
550
+ end