daytona 0.210.0 → 0.214.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d09317f70b4044909d07eb2a0bd5c6cc348184c1ef87682ce9a5b350479058e
4
- data.tar.gz: 5e754158fb8be188c98e79b02c7bd4ea642915215d1fd9aec9a4f30c91ed28e1
3
+ metadata.gz: 868afc4f077432e39b074d0249d2a20277206a1e430f64954b42663b74496118
4
+ data.tar.gz: 5e8dc41efd1f588f325f8fd117d8ad985382d5c7a43a73740d58ded6d5def73e
5
5
  SHA512:
6
- metadata.gz: e6804bf3e066f70ae9ce511bcad3b2d4b4d0f14c1b9805ae5d1fc5e5b53159f320ba7ebb7c09e575e1acc72093a6f15352d3b80f5b3c6479bf8739e176985be5
7
- data.tar.gz: 4ed7deeac3e6570d7469ea5cec4a08088e7fa696b67974e3d3aec405b3334b671266d3c3f655dfa04402beef15a67e3018f697d73ccc65ca6414016574ed922b
6
+ metadata.gz: c5fca0242291118c5000ffba802fb450aeab89c8f4b05c2f01cde1192e9c898dff8480edf6d9c12332a317c6282656a77b8b3fc9d75b0172a6bb0a16f1bb3dbc
7
+ data.tar.gz: 11fb9e1f412128cdb6651852bd5af7a7441a49ba1dcd3dab1ae3ce2479ac3769ba5968c3a85e10218b6e6f2ee45bd818adaebd9f3481fdb989e884294def4899
@@ -108,7 +108,7 @@ module Daytona
108
108
 
109
109
  puts "[DEBUG] Connecting to WebSocket: #{ws_url}" if ENV['DEBUG']
110
110
 
111
- ws = WebSocket::Client::Simple.connect(ws_url, headers:) do |client|
111
+ ws = Common::WebSocketDialer.connect(ws_url, headers:) do |client|
112
112
  client.on :open do
113
113
  puts '[DEBUG] WebSocket opened, sending request' if ENV['DEBUG']
114
114
  client.send(JSON.dump(request))
@@ -5,6 +5,7 @@
5
5
 
6
6
  require 'digest'
7
7
  require 'fileutils'
8
+ require 'json'
8
9
  require 'pathname'
9
10
  require 'shellwords'
10
11
 
@@ -362,7 +363,7 @@ module Daytona
362
363
  # @return [Array<Array<String>>] The list of the actual file path and its corresponding COPY-command source path
363
364
  def extract_copy_sources(dockerfile_content, path_prefix = '') # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
364
365
  sources = []
365
- lines = dockerfile_content.split("\n")
366
+ lines = dockerfile_logical_lines(dockerfile_content)
366
367
 
367
368
  lines.each do |line|
368
369
  # Skip empty lines and comments
@@ -404,47 +405,65 @@ module Daytona
404
405
  sources
405
406
  end
406
407
 
408
+ # Joins backslash-continued physical lines into logical Dockerfile instruction lines
409
+ #
410
+ # @param dockerfile_content [String] The content of the Dockerfile
411
+ # @return [Array<String>] The logical instruction lines
412
+ def dockerfile_logical_lines(dockerfile_content) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
413
+ logical_lines = []
414
+ current = nil
415
+
416
+ dockerfile_content.each_line(chomp: true) do |physical_line|
417
+ is_comment = physical_line.lstrip.start_with?('#')
418
+ # Docker drops empty and comment lines that appear inside a continued instruction
419
+ next if current && (physical_line.strip.empty? || is_comment)
420
+
421
+ stripped = physical_line.rstrip
422
+ # A trailing backslash on a comment line is literal; comments never continue onto the next line
423
+ continued = !is_comment && stripped.end_with?('\\')
424
+ segment = continued ? stripped[0..-2] : physical_line
425
+ current = current ? current + segment : segment
426
+ next if continued
427
+
428
+ logical_lines << current
429
+ current = nil
430
+ end
431
+
432
+ logical_lines << current if current
433
+ logical_lines
434
+ end
435
+
407
436
  # Parses a COPY command to extract sources and destination
408
437
  #
409
438
  # @param line [String] The line to parse
410
439
  # @return [Hash, nil] A hash containing the sources and destination, or nil if parsing fails
411
- def parse_copy_command(line) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
440
+ def parse_copy_command(line)
412
441
  # Remove initial "COPY" and strip whitespace
413
442
  parts = line.strip[4..].strip
414
443
 
415
- # Handle JSON array format: COPY ["src1", "src2", "dest"]
416
- if parts.start_with?('[')
417
- begin
418
- # Parse the JSON-like array format
419
- elements = Shellwords.split(parts.delete('[]'))
420
- return nil if elements.length < 2
421
-
422
- { 'sources' => elements[0..-2], 'dest' => elements[-1] }
423
- rescue StandardError
424
- nil
425
- end
426
- end
444
+ # Skip leading flags. Value-taking flags use the --flag=value form (--chown=..., --chmod=...)
445
+ # and boolean flags stand alone (--link), so a flag never consumes the token that follows it.
446
+ parts = parts.sub(/\A\S+\s*/, '') while parts.start_with?('--')
427
447
 
428
- # Handle regular format with possible flags
429
- parts = Shellwords.split(parts)
448
+ # Handle JSON array format: COPY ["src1", "src2", "dest"]
449
+ return parse_json_copy_command(parts) if parts.start_with?('[')
430
450
 
431
- # Extract flags like --chown, --chmod, --from
432
- sources_start_idx = 0
433
- parts.each_with_index do |part, i|
434
- break unless part.start_with?('--')
451
+ # Handle the whitespace-separated format
452
+ elements = Shellwords.split(parts)
453
+ return nil if elements.length < 2
435
454
 
436
- # Skip the flag and its value if it has one
437
- sources_start_idx = if !part.include?('=') && i + 1 < parts.length && !parts[i + 1].start_with?('--')
438
- i + 2
439
- else
440
- i + 1
441
- end
442
- end
455
+ { 'sources' => elements[0..-2], 'dest' => elements[-1] }
456
+ rescue ArgumentError
457
+ nil
458
+ end
443
459
 
444
- # After skipping flags, we need at least one source and one destination
445
- return nil if parts.length - sources_start_idx < 2
460
+ def parse_json_copy_command(parts)
461
+ elements = JSON.parse(parts)
462
+ return nil unless elements.is_a?(Array) && elements.all?(String) && elements.length >= 2
446
463
 
447
- { 'sources' => parts[sources_start_idx..-2], 'dest' => parts[-1] }
464
+ { 'sources' => elements[0..-2], 'dest' => elements[-1] }
465
+ rescue JSON::ParserError
466
+ nil
448
467
  end
449
468
  end
450
469
 
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright Daytona Platforms Inc.
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ require 'openssl'
7
+ require 'socket'
8
+ require 'uri'
9
+ require 'websocket'
10
+ require 'websocket-client-simple'
11
+
12
+ module Daytona
13
+ module Common
14
+ # Establishes WebSocket connections with the peer certificate fully verified
15
+ # before any request bytes are written.
16
+ #
17
+ # `websocket-client-simple` builds its own `SSLContext` and never calls
18
+ # `OpenSSL::SSL::SSLContext#set_params`, so it does not pick up Ruby's own
19
+ # defaults (`verify_mode: VERIFY_PEER`, `verify_hostname: true`). It applies
20
+ # `verify_mode` only when the caller passes it, and offers no way to enable
21
+ # hostname verification at all. Because the context is frozen by
22
+ # `SSLSocket.new` and the handshake is written before `connect` returns,
23
+ # there is no caller-side hook to correct this - so the dial itself has to
24
+ # be owned here.
25
+ #
26
+ # Chain verification alone is not sufficient: without hostname verification
27
+ # a peer holding any valid certificate can terminate the connection and
28
+ # receive the request headers.
29
+ module WebSocketDialer
30
+ # Opens a verified WebSocket connection.
31
+ #
32
+ # Mirrors `WebSocket::Client::Simple.connect`: the block, if given, receives
33
+ # the client before the connection is established so handlers can be
34
+ # registered, and the client is returned.
35
+ #
36
+ # @param url [String] The `ws://` or `wss://` URL to dial.
37
+ # @param options [Hash] Passed through to the client; `:headers`, `:ssl_version` and `:cert_store` are honoured.
38
+ # @return [VerifyingClient] The connected client.
39
+ # @raise [OpenSSL::SSL::SSLError] If the peer certificate fails chain or hostname verification.
40
+ def self.connect(url, options = {})
41
+ client = VerifyingClient.new
42
+ yield client if block_given?
43
+ client.connect(url, options)
44
+ client
45
+ end
46
+
47
+ # A `websocket-client-simple` client that verifies the peer before writing.
48
+ #
49
+ # `#connect` is reimplemented rather than extended because the upstream
50
+ # method builds the socket, performs the TLS handshake and writes the
51
+ # request in one pass, and returns early when `@socket` is already set.
52
+ #
53
+ # Everything after TLS setup mirrors upstream so that framing, the reader
54
+ # thread and event semantics stay identical. That couples to the instance
55
+ # variables the inherited `#send`, `#close`, `#open?` and `#closed?` read,
56
+ # so the gemspec pins the gem to `~> 0.9.0` - a 0.x minor bump could rename
57
+ # them. `websocket_dialer_spec.rb` drives a full send/close round trip
58
+ # against a real listener, which fails if that contract breaks.
59
+ class VerifyingClient < WebSocket::Client::Simple::Client
60
+ # @param url [String] The `ws://` or `wss://` URL to dial.
61
+ # @param options [Hash] Connection options.
62
+ # @return [void]
63
+ def connect(url, options = {})
64
+ return if @socket
65
+
66
+ @url = url
67
+ uri = URI.parse(url)
68
+ @socket = TCPSocket.new(uri.host, uri.port || (uri.scheme == 'wss' ? 443 : 80))
69
+ @socket = verified_ssl_socket(@socket, uri, options) if %w[https wss].include?(uri.scheme)
70
+
71
+ start_websocket(url, options)
72
+ end
73
+
74
+ private
75
+
76
+ # Wraps a socket in TLS with the peer certificate verified.
77
+ #
78
+ # @param socket [TCPSocket] The connected plaintext socket.
79
+ # @param uri [URI] The dialed URI, whose host the certificate must match.
80
+ # @param options [Hash] Connection options.
81
+ # @return [OpenSSL::SSL::SSLSocket] The connected, verified socket.
82
+ # @raise [OpenSSL::SSL::SSLError] If chain or hostname verification fails.
83
+ def verified_ssl_socket(socket, uri, options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
84
+ ctx = OpenSSL::SSL::SSLContext.new
85
+ ctx.ssl_version = options[:ssl_version] if options[:ssl_version]
86
+
87
+ # Only seed system roots into a store we own. Adding them to a
88
+ # caller-supplied store would silently widen their trust policy.
89
+ cert_store = options[:cert_store]
90
+ unless cert_store
91
+ cert_store = OpenSSL::X509::Store.new
92
+ cert_store.set_default_paths
93
+ end
94
+ ctx.cert_store = cert_store
95
+
96
+ # Must precede SSLSocket.new, which freezes the context.
97
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
98
+ ctx.verify_hostname = true
99
+
100
+ ssl = OpenSSL::SSL::SSLSocket.new(socket, ctx)
101
+ ssl.sync_close = true
102
+ ssl.hostname = uri.host
103
+ begin
104
+ ssl.connect
105
+ # Redundant while verify_hostname holds, but keeps the check explicit
106
+ # and independent of the context surviving future changes.
107
+ ssl.post_connection_check(uri.host)
108
+ rescue StandardError
109
+ ssl.close
110
+ raise
111
+ end
112
+ ssl
113
+ end
114
+
115
+ # Performs the WebSocket handshake and starts the reader thread.
116
+ #
117
+ # Mirrors `WebSocket::Client::Simple::Client#connect` from the request
118
+ # onward. Only reached once the socket is verified.
119
+ #
120
+ # @param url [String] The dialed URL.
121
+ # @param options [Hash] Connection options.
122
+ # @return [void]
123
+ def start_websocket(url, options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
124
+ WebSocket.should_raise = true
125
+ @handshake = WebSocket::Handshake::Client.new(url: url, headers: options[:headers])
126
+ @handshaked = false
127
+ @pipe_broken = false
128
+ @closed = false
129
+ frame = WebSocket::Frame::Incoming::Client.new
130
+
131
+ once :__close do |err|
132
+ close
133
+ emit :close, err
134
+ end
135
+
136
+ @thread = Thread.new do
137
+ until @closed
138
+ begin
139
+ unless (recv_data = @socket.getc)
140
+ sleep 1
141
+ next
142
+ end
143
+ if @handshaked
144
+ frame << recv_data
145
+ while (msg = frame.next)
146
+ emit :message, msg
147
+ end
148
+ else
149
+ @handshake << recv_data
150
+ if @handshake.finished?
151
+ @handshaked = true
152
+ emit :open
153
+ end
154
+ end
155
+ rescue StandardError => e
156
+ emit :error, e
157
+ end
158
+ end
159
+ end
160
+
161
+ @socket.write @handshake.to_s
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -6,6 +6,45 @@
6
6
  require 'dotenv'
7
7
 
8
8
  module Daytona
9
+ # Dotenv runs `$(...)` in a value through the shell while parsing, and the working
10
+ # directory is frequently a cloned repository, so parsing `.env` there would execute
11
+ # whatever its author put in it.
12
+ module EnvFile
13
+ # Dotenv::Parser picks its substitutions up from `self.class.substitutions`, and Ruby
14
+ # does not inherit class-level instance variables, so declaring the list here drops
15
+ # command substitution while leaving Dotenv::Parser alone — a host application's own
16
+ # Dotenv.load keeps the behaviour its author chose. `${VAR}` is kept: it reads only
17
+ # keys already parsed or the process environment.
18
+ class Parser < Dotenv::Parser
19
+ @substitutions = [Dotenv::Substitutions::Variable].freeze
20
+ end
21
+
22
+ # Read with the mode dotenv itself uses, so the accepted format does not narrow: `bom`
23
+ # skips a byte-order mark an editor on Windows may have written, and pinning `utf-8`
24
+ # keeps the file readable under a POSIX locale, where the default external encoding
25
+ # would make one accented byte anywhere — a comment included — raise while it is scanned.
26
+ def self.parse(path)
27
+ verify_suppression!
28
+ Parser.call(File.read(path, mode: 'rb:bom|utf-8'))
29
+ end
30
+
31
+ # The subclass reaches into dotenv's internals rather than a public API, and dotenv has
32
+ # already reorganised them once inside the range the gemspec allows, so confirm the
33
+ # suppression actually holds in the process that relies on it rather than trusting the
34
+ # version pinned in CI. Checked here rather than on load: this is the operation the
35
+ # guard protects, so a lapse stops it, and a gem that never reads a .env still loads.
36
+ # A failure is not memoised, so it is raised again on the next attempt.
37
+ def self.verify_suppression!
38
+ return if @verified
39
+
40
+ probe = Parser.call('DAYTONA_PROBE=$(echo substituted)')['DAYTONA_PROBE']
41
+ raise "dotenv command substitution is not suppressed (got #{probe.inspect})" unless
42
+ probe == '$(echo substituted)'
43
+
44
+ @verified = true
45
+ end
46
+ end
47
+
9
48
  class Config
10
49
  API_URL = 'https://app.daytona.io/api'
11
50
 
@@ -80,7 +119,9 @@ module Daytona
80
119
 
81
120
  @api_key = api_key || @env_reader.call('DAYTONA_API_KEY')
82
121
  @jwt_token = jwt_token || @env_reader.call('DAYTONA_JWT_TOKEN')
83
- @api_url = api_url || @env_reader.call('DAYTONA_API_URL') || API_URL
122
+ # Resolved from the process environment only, never from .env / .env.local:
123
+ # the endpoint decides where the credential above is sent.
124
+ @api_url = resolve_api_url(api_url)
84
125
  @target = target || @env_reader.call('DAYTONA_TARGET')
85
126
  @organization_id = organization_id || @env_reader.call('DAYTONA_ORGANIZATION_ID')
86
127
  @otel_enabled = otel_enabled
@@ -101,14 +142,44 @@ module Daytona
101
142
 
102
143
  private
103
144
 
104
- # Returns a lambda that looks up DAYTONA_-prefixed env vars without writing to ENV.
105
- # Files are parsed once; lookups check runtime env first, then .env.local, then .env.
106
- def daytona_env_reader
145
+ # Resolves the API endpoint without consulting the working directory, then reports a
146
+ # dotenv value that was passed over. Staying silent when the file value matches the
147
+ # endpoint in use keeps the documented .env layout quiet, while a file that would have
148
+ # changed the destination is surfaced.
149
+ def resolve_api_url(api_url)
150
+ resolved = api_url || process_env('DAYTONA_API_URL') || API_URL
151
+ file_value = env_file_vars['DAYTONA_API_URL']
152
+ return resolved if file_value.nil? || file_value.empty? || file_value == resolved
153
+
154
+ warn(
155
+ '`DAYTONA_API_URL` set in a .env or .env.local file was ignored: the Daytona API endpoint ' \
156
+ 'is never read from dotenv files, because the working directory is not always authored ' \
157
+ "by you. Using `#{resolved}` instead. To change the endpoint, pass `api_url:` to " \
158
+ '`Daytona::Config.new` or set `DAYTONA_API_URL` in the environment of the process.'
159
+ )
160
+ resolved
161
+ end
162
+
163
+ # Parses DAYTONA_-prefixed vars out of .env and .env.local in the working directory.
164
+ # These files are not necessarily authored by whoever runs the process, so anything
165
+ # that determines where a credential is sent must not be read from them.
166
+ def env_file_vars
167
+ @env_file_vars ||= parse_dotenv_files
168
+ end
169
+
170
+ def parse_dotenv_files
107
171
  file_vars = {}
108
172
  env_file = File.join(Dir.pwd, '.env')
109
- file_vars.merge!(daytona_filter(Dotenv.parse(env_file))) if File.exist?(env_file)
173
+ file_vars.merge!(daytona_filter(EnvFile.parse(env_file))) if File.exist?(env_file)
110
174
  env_local_file = File.join(Dir.pwd, '.env.local')
111
- file_vars.merge!(daytona_filter(Dotenv.parse(env_local_file))) if File.exist?(env_local_file)
175
+ file_vars.merge!(daytona_filter(EnvFile.parse(env_local_file))) if File.exist?(env_local_file)
176
+ file_vars
177
+ end
178
+
179
+ # Returns a lambda that looks up DAYTONA_-prefixed env vars without writing to ENV.
180
+ # Files are parsed once; lookups check runtime env first, then .env.local, then .env.
181
+ def daytona_env_reader
182
+ file_vars = env_file_vars
112
183
 
113
184
  lambda do |name|
114
185
  raise ArgumentError, "Variable must start with 'DAYTONA_', got '#{name}'" unless name.start_with?('DAYTONA_')
@@ -117,6 +188,13 @@ module Daytona
117
188
  end
118
189
  end
119
190
 
191
+ # Reads a DAYTONA_-prefixed variable from the process environment only.
192
+ def process_env(name)
193
+ raise ArgumentError, "Variable must start with 'DAYTONA_', got '#{name}'" unless name.start_with?('DAYTONA_')
194
+
195
+ ENV.fetch(name, nil)
196
+ end
197
+
120
198
  def daytona_filter(env_hash)
121
199
  env_hash.select { |k, _| k.start_with?('DAYTONA_') }
122
200
  end
@@ -34,7 +34,8 @@ module Daytona
34
34
  endpoint: endpoint_url,
35
35
  access_key_id: aws_access_key_id,
36
36
  secret_access_key: aws_secret_access_key,
37
- session_token: aws_session_token
37
+ session_token: aws_session_token,
38
+ **CLIENT_OPTIONS
38
39
  )
39
40
  end
40
41
 
@@ -146,17 +147,15 @@ module Daytona
146
147
  self.class.compute_archive_base_path(source_path) if archive_base_path.nil?
147
148
 
148
149
  temp_file = Tempfile.new(['context', '.tar'])
150
+ archive_path = temp_file.path
149
151
 
150
152
  begin
151
- system('tar', '-cf', temp_file.path, '-C', File.dirname(source_path), File.basename(source_path))
152
-
153
- File.open(temp_file.path, 'rb') do |file|
154
- s3_client.put_object(
155
- bucket: bucket_name,
156
- key: s3_key,
157
- body: file
158
- )
153
+ unless system('tar', '-cf', archive_path, '-C', File.dirname(source_path), File.basename(source_path))
154
+ raise Sdk::Error, "Failed to create tar archive for #{source_path}"
159
155
  end
156
+
157
+ Aws::S3::TransferManager.new(client: s3_client)
158
+ .upload_file(archive_path, bucket: bucket_name, key: s3_key, **UPLOAD_OPTIONS)
160
159
  ensure
161
160
  temp_file.close
162
161
  temp_file.unlink
@@ -164,6 +163,18 @@ module Daytona
164
163
  end
165
164
 
166
165
  DEFAULT_BUCKET_NAME = 'daytona-volume-builds'
167
- private_constant :DEFAULT_BUCKET_NAME
166
+
167
+ # multipart_threshold is also the part size floor: S3 rejects any part but the
168
+ # last below 5 MiB, and uploading in parts means a slow or unstable connection
169
+ # only ever has to carry 5 MiB per request instead of the whole context.
170
+ UPLOAD_OPTIONS = {
171
+ multipart_threshold: 5 * 1024 * 1024,
172
+ thread_count: 4,
173
+ content_type: 'application/x-tar'
174
+ }.freeze
175
+
176
+ CLIENT_OPTIONS = { http_open_timeout: 15, http_read_timeout: 120, retry_limit: 3 }.freeze
177
+
178
+ private_constant :DEFAULT_BUCKET_NAME, :UPLOAD_OPTIONS, :CLIENT_OPTIONS
168
179
  end
169
180
  end
@@ -257,7 +257,7 @@ module Daytona
257
257
 
258
258
  completion_queue = Queue.new
259
259
 
260
- WebSocket::Client::Simple.connect(
260
+ Common::WebSocketDialer.connect(
261
261
  url.to_s,
262
262
  headers: toolbox_api.api_client.default_headers.dup.merge(
263
263
  'X-Daytona-Preview-Token' => preview_link.token,
@@ -326,7 +326,7 @@ module Daytona
326
326
 
327
327
  completion_queue = Queue.new
328
328
 
329
- WebSocket::Client::Simple.connect(
329
+ Common::WebSocketDialer.connect(
330
330
  url.to_s,
331
331
  headers: toolbox_api.api_client.default_headers.dup.merge(
332
332
  'X-Daytona-Preview-Token' => preview_link.token,
@@ -465,7 +465,7 @@ module Daytona
465
465
  headers['Sec-WebSocket-Protocol'] = protocols.join(', ')
466
466
 
467
467
  PtyHandle.new(
468
- WebSocket::Client::Simple.connect(url.to_s, headers:),
468
+ Common::WebSocketDialer.connect(url.to_s, headers:),
469
469
  session_id: id,
470
470
  handle_resize: ->(pty_size_arg) { resize_pty_session(id, pty_size_arg) },
471
471
  handle_kill: -> { delete_pty_session(id) }
@@ -502,7 +502,7 @@ module Daytona
502
502
  [headers['Sec-WebSocket-Protocol'], PTY_EXIT_CONTROL_SUBPROTOCOL].compact.join(', ')
503
503
 
504
504
  handle = nil
505
- WebSocket::Client::Simple.connect(url.to_s, headers:) do |client|
505
+ Common::WebSocketDialer.connect(url.to_s, headers:) do |client|
506
506
  handle = PtyHandle.new(
507
507
  client,
508
508
  session_id:,
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Daytona
7
7
  module Sdk
8
- VERSION = '0.210.0'
8
+ VERSION = '0.214.0'
9
9
  end
10
10
  end
data/lib/daytona/sdk.rb CHANGED
@@ -33,6 +33,7 @@ require_relative 'common/snapshot'
33
33
  require_relative 'code_interpreter'
34
34
  require_relative 'computer_use'
35
35
  require_relative 'common/socketio_client'
36
+ require_relative 'common/websocket_dialer'
36
37
  require_relative 'common/event_dispatcher'
37
38
  require_relative 'common/event_subscription_manager'
38
39
  require_relative 'daytona'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: daytona
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.210.0
4
+ version: 0.214.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daytona Platforms Inc.
@@ -69,58 +69,64 @@ dependencies:
69
69
  name: aws-sdk-s3
70
70
  requirement: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - "~>"
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 1.197.0
75
+ - - "<"
73
76
  - !ruby/object:Gem::Version
74
- version: '1.0'
77
+ version: '2'
75
78
  type: :runtime
76
79
  prerelease: false
77
80
  version_requirements: !ruby/object:Gem::Requirement
78
81
  requirements:
79
- - - "~>"
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 1.197.0
85
+ - - "<"
80
86
  - !ruby/object:Gem::Version
81
- version: '1.0'
87
+ version: '2'
82
88
  - !ruby/object:Gem::Dependency
83
89
  name: daytona_analytics_api_client
84
90
  requirement: !ruby/object:Gem::Requirement
85
91
  requirements:
86
92
  - - '='
87
93
  - !ruby/object:Gem::Version
88
- version: 0.210.0
94
+ version: 0.214.0
89
95
  type: :runtime
90
96
  prerelease: false
91
97
  version_requirements: !ruby/object:Gem::Requirement
92
98
  requirements:
93
99
  - - '='
94
100
  - !ruby/object:Gem::Version
95
- version: 0.210.0
101
+ version: 0.214.0
96
102
  - !ruby/object:Gem::Dependency
97
103
  name: daytona_api_client
98
104
  requirement: !ruby/object:Gem::Requirement
99
105
  requirements:
100
106
  - - '='
101
107
  - !ruby/object:Gem::Version
102
- version: 0.210.0
108
+ version: 0.214.0
103
109
  type: :runtime
104
110
  prerelease: false
105
111
  version_requirements: !ruby/object:Gem::Requirement
106
112
  requirements:
107
113
  - - '='
108
114
  - !ruby/object:Gem::Version
109
- version: 0.210.0
115
+ version: 0.214.0
110
116
  - !ruby/object:Gem::Dependency
111
117
  name: daytona_toolbox_api_client
112
118
  requirement: !ruby/object:Gem::Requirement
113
119
  requirements:
114
120
  - - '='
115
121
  - !ruby/object:Gem::Version
116
- version: 0.210.0
122
+ version: 0.214.0
117
123
  type: :runtime
118
124
  prerelease: false
119
125
  version_requirements: !ruby/object:Gem::Requirement
120
126
  requirements:
121
127
  - - '='
122
128
  - !ruby/object:Gem::Version
123
- version: 0.210.0
129
+ version: 0.214.0
124
130
  - !ruby/object:Gem::Dependency
125
131
  name: dotenv
126
132
  requirement: !ruby/object:Gem::Requirement
@@ -175,14 +181,14 @@ dependencies:
175
181
  requirements:
176
182
  - - "~>"
177
183
  - !ruby/object:Gem::Version
178
- version: '0.6'
184
+ version: 0.9.0
179
185
  type: :runtime
180
186
  prerelease: false
181
187
  version_requirements: !ruby/object:Gem::Requirement
182
188
  requirements:
183
189
  - - "~>"
184
190
  - !ruby/object:Gem::Version
185
- version: '0.6'
191
+ version: 0.9.0
186
192
  description: 'High-level Ruby SDK for Daytona: sandboxes, git, filesystem, LSP, process,
187
193
  and object storage. Requires Ruby >= 3.2.'
188
194
  email:
@@ -215,6 +221,7 @@ files:
215
221
  - lib/daytona/common/response.rb
216
222
  - lib/daytona/common/snapshot.rb
217
223
  - lib/daytona/common/socketio_client.rb
224
+ - lib/daytona/common/websocket_dialer.rb
218
225
  - lib/daytona/computer_use.rb
219
226
  - lib/daytona/config.rb
220
227
  - lib/daytona/daytona.rb