daytona-sdk 0.125.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.
@@ -0,0 +1,493 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'fileutils'
5
+ require 'pathname'
6
+ require 'shellwords'
7
+
8
+ module Daytona
9
+ class Context
10
+ attr_reader :source_path
11
+ attr_reader :archive_path
12
+
13
+ # @param source_path [String] The path to the source file or directory
14
+ # @param archive_path [String, nil] The path inside the archive file in object storage
15
+ def initialize(source_path:, archive_path: nil)
16
+ @source_path = source_path
17
+ @archive_path = archive_path
18
+ end
19
+ end
20
+
21
+ # Represents an image definition for a Daytona sandbox.
22
+ # Do not construct this class directly. Instead use one of its static factory methods,
23
+ # such as `Image.base()`, `Image.debian_slim()`, or `Image.from_dockerfile()`.
24
+ class Image # rubocop:disable Metrics/ClassLength
25
+ # @return [String, nil] The generated Dockerfile for the image
26
+ attr_reader :dockerfile
27
+
28
+ # @return [Array<Context>] List of context files for the image
29
+ attr_reader :context_list
30
+
31
+ # Supported Python series
32
+ SUPPORTED_PYTHON_SERIES = %w[3.9 3.10 3.11 3.12 3.13].freeze
33
+ LATEST_PYTHON_MICRO_VERSIONS = %w[3.9.22 3.10.17 3.11.12 3.12.10 3.13.3].freeze
34
+
35
+ # @param dockerfile [String, nil] The Dockerfile content
36
+ # @param context_list [Array<Context>] List of context files
37
+ def initialize(dockerfile: nil, context_list: [])
38
+ @dockerfile = dockerfile || ''
39
+ @context_list = context_list
40
+ end
41
+
42
+ # Adds commands to install packages using pip
43
+ #
44
+ # @param packages [Array<String>] The packages to install
45
+ # @param find_links [Array<String>, nil] The find-links to use
46
+ # @param index_url [String, nil] The index URL to use
47
+ # @param extra_index_urls [Array<String>, nil] The extra index URLs to use
48
+ # @param pre [Boolean] Whether to install pre-release packages
49
+ # @param extra_options [String] Additional options to pass to pip
50
+ # @return [Image] The image with the pip install commands added
51
+ #
52
+ # @example
53
+ # image = Image.debian_slim("3.12").pip_install("requests", "pandas")
54
+ def pip_install(*packages, find_links: nil, index_url: nil, extra_index_urls: nil, pre: false, extra_options: '') # rubocop:disable Metrics/ParameterLists
55
+ pkgs = flatten_str_args('pip_install', 'packages', packages)
56
+ return self if pkgs.empty?
57
+
58
+ extra_args = format_pip_install_args(find_links:, index_url:, extra_index_urls:, pre:, extra_options:)
59
+ @dockerfile += "RUN python -m pip install #{Shellwords.join(pkgs.sort)}#{extra_args}\n"
60
+
61
+ self
62
+ end
63
+
64
+ # Installs dependencies from a requirements.txt file
65
+ #
66
+ # @param requirements_txt [String] The path to the requirements.txt file
67
+ # @param find_links [Array<String>, nil] The find-links to use
68
+ # @param index_url [String, nil] The index URL to use
69
+ # @param extra_index_urls [Array<String>, nil] The extra index URLs to use
70
+ # @param pre [Boolean] Whether to install pre-release packages
71
+ # @param extra_options [String] Additional options to pass to pip
72
+ # @return [Image] The image with the pip install commands added
73
+ # @raise [Sdk::Error] If the requirements file does not exist
74
+ #
75
+ # @example
76
+ # image = Image.debian_slim("3.12").pip_install_from_requirements("requirements.txt")
77
+ def pip_install_from_requirements(requirements_txt, find_links: nil, index_url: nil, extra_index_urls: nil, # rubocop:disable Metrics/ParameterLists
78
+ pre: false, extra_options: '')
79
+ requirements_txt = File.expand_path(requirements_txt)
80
+ raise Sdk::Error, "Requirements file #{requirements_txt} does not exist" unless File.exist?(requirements_txt)
81
+
82
+ extra_args = format_pip_install_args(find_links:, index_url:, extra_index_urls:, pre:, extra_options:)
83
+
84
+ archive_path = ObjectStorage.compute_archive_base_path(requirements_txt)
85
+ @context_list << Context.new(source_path: requirements_txt, archive_path:)
86
+ @dockerfile += "COPY #{archive_path} /.requirements.txt\n"
87
+ @dockerfile += "RUN python -m pip install -r /.requirements.txt#{extra_args}\n"
88
+
89
+ self
90
+ end
91
+
92
+ # Installs dependencies from a pyproject.toml file
93
+ #
94
+ # @param pyproject_toml [String] The path to the pyproject.toml file
95
+ # @param optional_dependencies [Array<String>] The optional dependencies to install
96
+ # @param find_links [String, nil] The find-links to use
97
+ # @param index_url [String, nil] The index URL to use
98
+ # @param extra_index_url [String, nil] The extra index URL to use
99
+ # @param pre [Boolean] Whether to install pre-release packages
100
+ # @param extra_options [String] Additional options to pass to pip
101
+ # @return [Image] The image with the pip install commands added
102
+ # @raise [Sdk::Error] If pyproject.toml parsing is not supported
103
+ #
104
+ # @example
105
+ # image = Image.debian_slim("3.12").pip_install_from_pyproject("pyproject.toml", optional_dependencies: ["dev"])
106
+ def pip_install_from_pyproject(pyproject_toml, optional_dependencies: [], find_links: nil, index_url: nil, # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
107
+ extra_index_url: nil, pre: false, extra_options: '')
108
+ data = TOML.load_file(pyproject_toml)
109
+ dependencies = data.dig('project', 'dependencies')
110
+
111
+ unless dependencies
112
+ raise Sdk::Error, 'No [project.dependencies] section in pyproject.toml file. ' \
113
+ 'See https://packaging.python.org/en/latest/guides/writing-pyproject-toml ' \
114
+ 'for further file format guidelines.'
115
+ end
116
+
117
+ return unless optional_dependencies
118
+
119
+ optionals = data.dig('project', 'optional-dependencies')
120
+ optional_dependencies.each do |group|
121
+ dependencies.concat(optionals.fetch(group, []))
122
+ end
123
+
124
+ pip_install(*dependencies, find_links:, index_url:, extra_index_urls: extra_index_url, pre:, extra_options:)
125
+ end
126
+
127
+ # Adds a local file to the image
128
+ #
129
+ # @param local_path [String] The path to the local file
130
+ # @param remote_path [String] The path to the file in the image
131
+ # @return [Image] The image with the local file added
132
+ #
133
+ # @example
134
+ # image = Image.debian_slim("3.12").add_local_file("package.json", "/home/daytona/package.json")
135
+ def add_local_file(local_path, remote_path)
136
+ remote_path = "#{remote_path}/#{File.basename(local_path)}" if remote_path.end_with?('/')
137
+
138
+ local_path = File.expand_path(local_path)
139
+ archive_path = ObjectStorage.compute_archive_base_path(local_path)
140
+ @context_list << Context.new(source_path: local_path, archive_path: archive_path)
141
+ @dockerfile += "COPY #{archive_path} #{remote_path}\n"
142
+
143
+ self
144
+ end
145
+
146
+ # Adds a local directory to the image
147
+ #
148
+ # @param local_path [String] The path to the local directory
149
+ # @param remote_path [String] The path to the directory in the image
150
+ # @return [Image] The image with the local directory added
151
+ #
152
+ # @example
153
+ # image = Image.debian_slim("3.12").add_local_dir("src", "/home/daytona/src")
154
+ def add_local_dir(local_path, remote_path)
155
+ local_path = File.expand_path(local_path)
156
+ archive_path = ObjectStorage.compute_archive_base_path(local_path)
157
+ @context_list << Context.new(source_path: local_path, archive_path: archive_path)
158
+ @dockerfile += "COPY #{archive_path} #{remote_path}\n"
159
+
160
+ self
161
+ end
162
+
163
+ # Runs commands in the image
164
+ #
165
+ # @param commands [Array<String>] The commands to run
166
+ # @return [Image] The image with the commands added
167
+ #
168
+ # @example
169
+ # image = Image.debian_slim("3.12").run_commands('echo "Hello, world!"', 'echo "Hello again!"')
170
+ def run_commands(*commands)
171
+ commands.each do |command|
172
+ if command.is_a?(Array)
173
+ escaped = command.map { |c| c.gsub('"', '\\"').gsub("'", "\\'") }
174
+ @dockerfile += "RUN #{escaped.map { |c| "\"#{c}\"" }.join(' ')}\n"
175
+ else
176
+ @dockerfile += "RUN #{command}\n"
177
+ end
178
+ end
179
+
180
+ self
181
+ end
182
+
183
+ # Sets environment variables in the image
184
+ #
185
+ # @param env_vars [Hash<String, String>] The environment variables to set
186
+ # @return [Image] The image with the environment variables added
187
+ #
188
+ # @example
189
+ # image = Image.debian_slim("3.12").env({"PROJECT_ROOT" => "/home/daytona"})
190
+ def env(env_vars)
191
+ non_str_keys = env_vars.reject { |_key, val| val.is_a?(String) }.keys
192
+ raise Sdk::Error, "Image ENV variables must be strings. Invalid keys: #{non_str_keys}" unless non_str_keys.empty?
193
+
194
+ env_vars.each do |key, val|
195
+ @dockerfile += "ENV #{key}=#{Shellwords.escape(val)}\n"
196
+ end
197
+
198
+ self
199
+ end
200
+
201
+ # Sets the working directory in the image
202
+ #
203
+ # @param path [String] The path to the working directory
204
+ # @return [Image] The image with the working directory added
205
+ #
206
+ # @example
207
+ # image = Image.debian_slim("3.12").workdir("/home/daytona")
208
+ def workdir(path)
209
+ @dockerfile += "WORKDIR #{Shellwords.escape(path.to_s)}\n"
210
+ self
211
+ end
212
+
213
+ # Sets the entrypoint for the image
214
+ #
215
+ # @param entrypoint_commands [Array<String>] The commands to set as the entrypoint
216
+ # @return [Image] The image with the entrypoint added
217
+ #
218
+ # @example
219
+ # image = Image.debian_slim("3.12").entrypoint(["/bin/bash"])
220
+ def entrypoint(entrypoint_commands)
221
+ unless entrypoint_commands.is_a?(Array) && entrypoint_commands.all? { |x| x.is_a?(String) }
222
+ raise Sdk::Error, 'entrypoint_commands must be a list of strings.'
223
+ end
224
+
225
+ args_str = flatten_str_args('entrypoint', 'entrypoint_commands', entrypoint_commands)
226
+ args_str = args_str.map { |arg| "\"#{arg}\"" }.join(', ') if args_str.any?
227
+ @dockerfile += "ENTRYPOINT [#{args_str}]\n"
228
+
229
+ self
230
+ end
231
+
232
+ # Sets the default command for the image
233
+ #
234
+ # @param cmd [Array<String>] The commands to set as the default command
235
+ # @return [Image] The image with the default command added
236
+ #
237
+ # @example
238
+ # image = Image.debian_slim("3.12").cmd(["/bin/bash"])
239
+ def cmd(cmd)
240
+ unless cmd.is_a?(Array) && cmd.all? { |x| x.is_a?(String) }
241
+ raise Sdk::Error, 'Image CMD must be a list of strings.'
242
+ end
243
+
244
+ cmd_str = flatten_str_args('cmd', 'cmd', cmd)
245
+ cmd_str = cmd_str.map { |arg| "\"#{arg}\"" }.join(', ') if cmd_str.any?
246
+ @dockerfile += "CMD [#{cmd_str}]\n"
247
+ self
248
+ end
249
+
250
+ # Adds arbitrary Dockerfile-like commands to the image
251
+ #
252
+ # @param dockerfile_commands [Array<String>] The commands to add to the Dockerfile
253
+ # @param context_dir [String, nil] The path to the context directory
254
+ # @return [Image] The image with the Dockerfile commands added
255
+ #
256
+ # @example
257
+ # image = Image.debian_slim("3.12").dockerfile_commands(["RUN echo 'Hello, world!'"])
258
+ def dockerfile_commands(dockerfile_commands, context_dir: nil) # rubocop:disable Metrics/MethodLength
259
+ if context_dir
260
+ context_dir = File.expand_path(context_dir)
261
+ raise Sdk::Error, "Context directory #{context_dir} does not exist" unless Dir.exist?(context_dir)
262
+ end
263
+
264
+ # Extract copy sources from dockerfile commands
265
+ extract_copy_sources(dockerfile_commands.join("\n"), context_dir || '').each do |context_path, original_path|
266
+ archive_base_path = context_path
267
+ if context_dir && !original_path.start_with?(context_dir)
268
+ archive_base_path = context_path.delete_prefix(context_dir)
269
+ end
270
+ @context_list << Context.new(source_path: context_path, archive_path: archive_base_path)
271
+ end
272
+
273
+ @dockerfile += "#{dockerfile_commands.join("\n")}\n"
274
+ self
275
+ end
276
+
277
+ class << self
278
+ # Creates an Image from an existing Dockerfile
279
+ #
280
+ # @param path [String] The path to the Dockerfile
281
+ # @return [Image] The image with the Dockerfile added
282
+ #
283
+ # @example
284
+ # image = Image.from_dockerfile("Dockerfile")
285
+ def from_dockerfile(path) # rubocop:disable Metrics/AbcSize
286
+ path = Pathname.new(File.expand_path(path))
287
+ dockerfile = path.read
288
+ img = new(dockerfile: dockerfile)
289
+
290
+ # Remove dockerfile filename from path
291
+ path_prefix = path.to_s.delete_suffix(path.basename.to_s)
292
+
293
+ extract_copy_sources(dockerfile, path_prefix).each do |context_path, original_path|
294
+ archive_base_path = context_path
295
+ archive_base_path = context_path.delete_prefix(path_prefix) unless original_path.start_with?(path_prefix)
296
+ img.context_list << Context.new(source_path: context_path, archive_path: archive_base_path)
297
+ end
298
+
299
+ img
300
+ end
301
+
302
+ # Creates an Image from an existing base image
303
+ #
304
+ # @param image [String] The base image to use
305
+ # @return [Image] The image with the base image added
306
+ #
307
+ # @example
308
+ # image = Image.base("python:3.12-slim-bookworm")
309
+ def base(image)
310
+ img = new
311
+ img.instance_variable_set(:@dockerfile, "FROM #{image}\n")
312
+ img
313
+ end
314
+
315
+ # Creates a Debian slim image based on the official Python Docker image
316
+ #
317
+ # @param python_version [String, nil] The Python version to use
318
+ # @return [Image] The image with the Debian slim image added
319
+ #
320
+ # @example
321
+ # image = Image.debian_slim("3.12")
322
+ def debian_slim(python_version = nil) # rubocop:disable Metrics/MethodLength
323
+ python_version = process_python_version(python_version)
324
+ img = new
325
+ commands = [
326
+ "FROM python:#{python_version}-slim-bookworm",
327
+ 'RUN apt-get update',
328
+ 'RUN apt-get install -y gcc gfortran build-essential',
329
+ 'RUN pip install --upgrade pip',
330
+ # Set debian front-end to non-interactive to avoid users getting stuck with input prompts.
331
+ "RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections"
332
+ ]
333
+ img.instance_variable_set(:@dockerfile, "#{commands.join("\n")}\n")
334
+ img
335
+ end
336
+
337
+ private
338
+
339
+ # Processes the Python version
340
+ #
341
+ # @param python_version [String, nil] The Python version to process
342
+ # @param allow_micro_granularity [Boolean] Whether to allow micro-level granularity
343
+ # @return [String] The processed Python version
344
+ def process_python_version(python_version = nil)
345
+ python_version ||= SUPPORTED_PYTHON_SERIES.last
346
+
347
+ unless SUPPORTED_PYTHON_SERIES.include?(python_version)
348
+ raise Sdk::Error, "Unsupported Python version: #{python_version}"
349
+ end
350
+
351
+ LATEST_PYTHON_MICRO_VERSIONS.select { |v| v.start_with?(python_version) }.last
352
+ end
353
+
354
+ # Extracts source files from COPY commands in a Dockerfile
355
+ #
356
+ # @param dockerfile_content [String] The content of the Dockerfile
357
+ # @param path_prefix [String] The path prefix to use for the sources
358
+ # @return [Array<Array<String>>] The list of the actual file path and its corresponding COPY-command source path
359
+ def extract_copy_sources(dockerfile_content, path_prefix = '') # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
360
+ sources = []
361
+ lines = dockerfile_content.split("\n")
362
+
363
+ lines.each do |line|
364
+ # Skip empty lines and comments
365
+ next if line.strip.empty? || line.strip.start_with?('#')
366
+
367
+ # Check if the line contains a COPY command (at the beginning of the line)
368
+ next unless line.match?(/^\s*COPY\s+(?!.*--from=)/i)
369
+
370
+ # Extract the sources from the COPY command
371
+ command_parts = parse_copy_command(line)
372
+ next unless command_parts
373
+
374
+ # Get source paths from the parsed command parts
375
+ command_parts['sources'].each do |source|
376
+ # Handle absolute and relative paths differently
377
+ full_path_pattern = if Pathname.new(source).absolute?
378
+ # Absolute path - use as is
379
+ source
380
+ else
381
+ # Relative path - add prefix
382
+ File.join(path_prefix, source)
383
+ end
384
+
385
+ # Handle glob patterns
386
+ matching_files = Dir.glob(full_path_pattern)
387
+
388
+ if matching_files.any?
389
+ matching_files.each { |matching_file| sources << [matching_file, source] }
390
+ else
391
+ # If no files match, include the pattern anyway
392
+ sources << [full_path_pattern, source]
393
+ end
394
+ end
395
+ end
396
+
397
+ sources
398
+ end
399
+
400
+ # Parses a COPY command to extract sources and destination
401
+ #
402
+ # @param line [String] The line to parse
403
+ # @return [Hash, nil] A hash containing the sources and destination, or nil if parsing fails
404
+ def parse_copy_command(line) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
405
+ # Remove initial "COPY" and strip whitespace
406
+ parts = line.strip[4..].strip
407
+
408
+ # Handle JSON array format: COPY ["src1", "src2", "dest"]
409
+ if parts.start_with?('[')
410
+ begin
411
+ # Parse the JSON-like array format
412
+ elements = Shellwords.split(parts.delete('[]'))
413
+ return nil if elements.length < 2
414
+
415
+ { 'sources' => elements[0..-2], 'dest' => elements[-1] }
416
+ rescue StandardError
417
+ nil
418
+ end
419
+ end
420
+
421
+ # Handle regular format with possible flags
422
+ parts = Shellwords.split(parts)
423
+
424
+ # Extract flags like --chown, --chmod, --from
425
+ sources_start_idx = 0
426
+ parts.each_with_index do |part, i|
427
+ break unless part.start_with?('--')
428
+
429
+ # Skip the flag and its value if it has one
430
+ sources_start_idx = if !part.include?('=') && i + 1 < parts.length && !parts[i + 1].start_with?('--')
431
+ i + 2
432
+ else
433
+ i + 1
434
+ end
435
+ end
436
+
437
+ # After skipping flags, we need at least one source and one destination
438
+ return nil if parts.length - sources_start_idx < 2
439
+
440
+ { 'sources' => parts[sources_start_idx..-2], 'dest' => parts[-1] }
441
+ end
442
+ end
443
+
444
+ private
445
+
446
+ # Flattens a list of strings and arrays of strings into a single array of strings
447
+ #
448
+ # @param function_name [String] The name of the function that is being called
449
+ # @param arg_name [String] The name of the argument that is being passed
450
+ # @param args [Array] The list of arguments to flatten
451
+ # @return [Array<String>] A list of strings
452
+ def flatten_str_args(function_name, arg_name, args) # rubocop:disable Metrics/MethodLength
453
+ ret = []
454
+ args.each do |x|
455
+ case x
456
+ when String
457
+ ret << x
458
+ when Array
459
+ unless x.all? { |y| y.is_a?(String) }
460
+ raise Sdk::Error, "#{function_name}: #{arg_name} must only contain strings"
461
+ end
462
+
463
+ ret.concat(x)
464
+
465
+ else
466
+ raise Sdk::Error, "#{function_name}: #{arg_name} must only contain strings"
467
+ end
468
+ end
469
+ ret
470
+ end
471
+
472
+ # Formats the arguments in a single string
473
+ #
474
+ # @param find_links [Array<String>, nil] The find-links to use
475
+ # @param index_url [String, nil] The index URL to use
476
+ # @param extra_index_urls [Array<String>, nil] The extra index URLs to use
477
+ # @param pre [Boolean] Whether to install pre-release packages
478
+ # @param extra_options [String] Additional options to pass to pip
479
+ # @return [String] The formatted arguments
480
+ def format_pip_install_args(find_links: nil, index_url: nil, extra_index_urls: nil, pre: false, extra_options: '') # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
481
+ extra_args = ''
482
+ find_links&.each { |find_link| extra_args += " --find-links #{Shellwords.escape(find_link)}" }
483
+ extra_args += " --index-url #{Shellwords.escape(index_url)}" if index_url
484
+ extra_index_urls&.each do |extra_index_url|
485
+ extra_args += " --extra-index-url #{Shellwords.escape(extra_index_url)}"
486
+ end
487
+ extra_args += ' --pre' if pre
488
+ extra_args += " #{extra_options.strip}" if extra_options && !extra_options.strip.empty?
489
+
490
+ extra_args
491
+ end
492
+ end
493
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Daytona
4
+ class ExecuteResponse
5
+ # @return [Integer] The exit code from the command execution
6
+ attr_reader :exit_code
7
+
8
+ # @return [String] The output from the command execution
9
+ attr_reader :result
10
+
11
+ # @return [ExecutionArtifacts, nil] Artifacts from the command execution
12
+ attr_reader :artifacts
13
+
14
+ # @return [Hash] Additional properties from the response
15
+ attr_reader :additional_properties
16
+
17
+ # Initialize a new ExecuteResponse
18
+ #
19
+ # @param exit_code [Integer] The exit code from the command execution
20
+ # @param result [String] The output from the command execution
21
+ # @param artifacts [ExecutionArtifacts, nil] Artifacts from the command execution
22
+ # @param additional_properties [Hash] Additional properties from the response
23
+ def initialize(exit_code:, result:, artifacts: nil, additional_properties: {})
24
+ @exit_code = exit_code
25
+ @result = result
26
+ @artifacts = artifacts
27
+ @additional_properties = additional_properties
28
+ end
29
+ end
30
+
31
+ class ExecutionArtifacts
32
+ # @return [String] Standard output from the command, same as `result` in `ExecuteResponse`
33
+ attr_accessor :stdout
34
+
35
+ # @return [Array] List of chart metadata from matplotlib
36
+ attr_accessor :charts
37
+
38
+ # Initialize a new ExecutionArtifacts
39
+ #
40
+ # @param stdout [String] Standard output from the command
41
+ # @param charts [Array] List of chart metadata from matplotlib
42
+ def initialize(stdout = '', charts = [])
43
+ @stdout = stdout
44
+ @charts = charts
45
+ end
46
+ end
47
+
48
+ class CodeRunParams
49
+ # @return [Array<String>, nil] Command line arguments
50
+ attr_accessor :argv
51
+
52
+ # @return [Hash<String, String>, nil] Environment variables
53
+ attr_accessor :env
54
+
55
+ # Initialize a new CodeRunParams
56
+ #
57
+ # @param argv [Array<String>, nil] Command line arguments
58
+ # @param env [Hash<String, String>, nil] Environment variables
59
+ def initialize(argv: nil, env: nil)
60
+ @argv = argv
61
+ @env = env
62
+ end
63
+ end
64
+
65
+ class SessionExecuteRequest
66
+ # @return [String] The command to execute
67
+ attr_accessor :command
68
+
69
+ # @return [Boolean] Whether to execute the command asynchronously
70
+ attr_accessor :run_async
71
+
72
+ # Initialize a new SessionExecuteRequest
73
+ #
74
+ # @param command [String] The command to execute
75
+ # @param run_async [Boolean] Whether to execute the command asynchronously
76
+ def initialize(command:, run_async: false)
77
+ @command = command
78
+ @run_async = run_async
79
+ end
80
+ end
81
+
82
+ class SessionExecuteResponse
83
+ # @return [String, nil] Unique identifier for the executed command
84
+ attr_reader :cmd_id
85
+
86
+ # @return [String, nil] The output from the command execution
87
+ attr_reader :output
88
+
89
+ # @return [String, nil] Standard output from the command
90
+ attr_reader :stdout
91
+
92
+ # @return [String, nil] Standard error from the command
93
+ attr_reader :stderr
94
+
95
+ # @return [Integer, nil] The exit code from the command execution
96
+ attr_reader :exit_code
97
+
98
+ # @return [Hash] Additional properties from the response
99
+ attr_reader :additional_properties
100
+
101
+ # Initialize a new SessionExecuteResponse
102
+ #
103
+ # @param opts [Hash] Options for the SessionExecuteResponse
104
+ # @param cmd_id [String, nil] Unique identifier for the executed command
105
+ # @param output [String, nil] The output from the command execution
106
+ # @param stdout [String, nil] Standard output from the command
107
+ # @param stderr [String, nil] Standard error from the command
108
+ # @param exit_code [Integer, nil] The exit code from the command execution
109
+ # @param additional_properties [Hash] Additional properties from the response
110
+ def initialize(opts = {})
111
+ @cmd_id = opts.fetch(:cmd_id, nil)
112
+ @output = opts.fetch(:output, nil)
113
+ @stdout = opts.fetch(:stdout, nil)
114
+ @stderr = opts.fetch(:stderr, nil)
115
+ @exit_code = opts.fetch(:exit_code, nil)
116
+ @additional_properties = opts.fetch(:additional_properties, {})
117
+ end
118
+ end
119
+
120
+ class SessionCommandLogsResponse
121
+ # @return [String, nil] The combined output from the command
122
+ attr_reader :output
123
+
124
+ # @return [String, nil] The stdout from the command
125
+ attr_reader :stdout
126
+
127
+ # @return [String, nil] The stderr from the command
128
+ attr_reader :stderr
129
+
130
+ # Initialize a new SessionCommandLogsResponse
131
+ #
132
+ # @param output [String, nil] The combined output from the command
133
+ # @param stdout [String, nil] The stdout from the command
134
+ # @param stderr [String, nil] The stderr from the command
135
+ def initialize(output: nil, stdout: nil, stderr: nil)
136
+ @output = output
137
+ @stdout = stdout
138
+ @stderr = stderr
139
+ end
140
+ end
141
+ end