daytona 0.197.0 → 0.199.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 +4 -4
- data/lib/daytona/common/daytona.rb +9 -0
- data/lib/daytona/common/event_dispatcher.rb +326 -0
- data/lib/daytona/common/event_subscription_manager.rb +144 -0
- data/lib/daytona/common/socketio_client.rb +289 -0
- data/lib/daytona/config.rb +21 -0
- data/lib/daytona/daytona.rb +39 -6
- data/lib/daytona/file_system.rb +1 -1
- data/lib/daytona/lsp_server.rb +1 -1
- data/lib/daytona/sandbox.rb +404 -111
- data/lib/daytona/sdk/version.rb +1 -1
- data/lib/daytona/sdk.rb +5 -2
- data/project.json +4 -3
- metadata +22 -5
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Daytona Platforms Inc.
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
require 'timeout'
|
|
7
|
+
|
|
8
|
+
require 'websocket-client-simple'
|
|
9
|
+
require 'json'
|
|
10
|
+
require 'uri'
|
|
11
|
+
|
|
12
|
+
module Daytona
|
|
13
|
+
# Minimal Engine.IO/Socket.IO v4 client over raw WebSocket.
|
|
14
|
+
# Supports connect with auth, heartbeat, and event reception.
|
|
15
|
+
#
|
|
16
|
+
# Engine.IO v4 heartbeat protocol (WebSocket transport):
|
|
17
|
+
# - Server sends PING (type 2) every pingInterval ms
|
|
18
|
+
# - Client must respond with PONG (type 3) within pingTimeout ms
|
|
19
|
+
# - Client monitors for missing server PINGs to detect dead connections
|
|
20
|
+
class SocketIOClient
|
|
21
|
+
# Engine.IO v4 packet types
|
|
22
|
+
EIO_OPEN = '0'
|
|
23
|
+
EIO_CLOSE = '1'
|
|
24
|
+
EIO_PING = '2'
|
|
25
|
+
EIO_PONG = '3'
|
|
26
|
+
EIO_MESSAGE = '4'
|
|
27
|
+
|
|
28
|
+
# Socket.IO v4 packet types (inside Engine.IO messages)
|
|
29
|
+
SIO_CONNECT = '0'
|
|
30
|
+
SIO_DISCONNECT = '1'
|
|
31
|
+
SIO_EVENT = '2'
|
|
32
|
+
SIO_CONNECT_ERROR = '4'
|
|
33
|
+
|
|
34
|
+
attr_reader :connected
|
|
35
|
+
|
|
36
|
+
# @param api_url [String] The API URL (e.g., "https://app.daytona.io/api")
|
|
37
|
+
# @param token [String] Auth token (API key or JWT)
|
|
38
|
+
# @param organization_id [String, nil] Organization ID for room joining
|
|
39
|
+
# @param on_event [Proc] Called with (event_name, data_hash) for each Socket.IO event
|
|
40
|
+
# @param on_disconnect [Proc] Called when the connection is lost
|
|
41
|
+
# @param connect_timeout [Numeric] Connection timeout in seconds
|
|
42
|
+
def initialize(api_url:, token:, organization_id: nil, source: nil, sdk_version: nil, on_event: nil,
|
|
43
|
+
on_disconnect: nil, connect_timeout: 5)
|
|
44
|
+
@api_url = api_url
|
|
45
|
+
@token = token
|
|
46
|
+
@organization_id = organization_id
|
|
47
|
+
@source = source
|
|
48
|
+
@sdk_version = sdk_version
|
|
49
|
+
@on_event = on_event
|
|
50
|
+
@on_disconnect = on_disconnect
|
|
51
|
+
@connect_timeout = connect_timeout
|
|
52
|
+
@connected = false
|
|
53
|
+
@mutex = Mutex.new
|
|
54
|
+
@write_mutex = Mutex.new
|
|
55
|
+
@health_thread = nil
|
|
56
|
+
@ping_interval = 25
|
|
57
|
+
@ping_timeout = 20
|
|
58
|
+
@last_server_activity = Time.now
|
|
59
|
+
@ws = nil
|
|
60
|
+
@close_requested = false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Establish the WebSocket connection and perform Socket.IO handshake.
|
|
64
|
+
# @return [Boolean] true if connection succeeded
|
|
65
|
+
# @raise [StandardError] on connection failure
|
|
66
|
+
def connect
|
|
67
|
+
ws_url = build_ws_url
|
|
68
|
+
connected_queue = Queue.new
|
|
69
|
+
|
|
70
|
+
# Capture self because websocket-client-simple uses instance_exec for callbacks
|
|
71
|
+
client = self
|
|
72
|
+
|
|
73
|
+
# Register callbacks inside the connect block so they are installed before the
|
|
74
|
+
# socket dials — otherwise the Engine.IO handshake can arrive before :message
|
|
75
|
+
# is bound, stalling the connection into a polling fallback/timeout.
|
|
76
|
+
@ws = WebSocket::Client::Simple.connect(ws_url) do |ws|
|
|
77
|
+
ws.on :message do |msg|
|
|
78
|
+
client.send(:handle_raw_message, msg.data.to_s, connected_queue)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
ws.on :error do |_e|
|
|
82
|
+
was_connected = client.instance_variable_get(:@mutex).synchronize do
|
|
83
|
+
prev = client.instance_variable_get(:@connected)
|
|
84
|
+
client.instance_variable_set(:@connected, false)
|
|
85
|
+
prev
|
|
86
|
+
end
|
|
87
|
+
connected_queue.push(:error) unless was_connected
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
ws.on :close do
|
|
91
|
+
mutex = client.instance_variable_get(:@mutex)
|
|
92
|
+
was_connected = mutex.synchronize do
|
|
93
|
+
prev = client.instance_variable_get(:@connected)
|
|
94
|
+
client.instance_variable_set(:@connected, false)
|
|
95
|
+
prev
|
|
96
|
+
end
|
|
97
|
+
on_disconnect = client.instance_variable_get(:@on_disconnect)
|
|
98
|
+
close_requested = client.instance_variable_get(:@close_requested)
|
|
99
|
+
begin
|
|
100
|
+
on_disconnect&.call if was_connected && !close_requested
|
|
101
|
+
rescue StandardError
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Wait for connection with timeout
|
|
108
|
+
result = nil
|
|
109
|
+
begin
|
|
110
|
+
Timeout.timeout(@connect_timeout) { result = connected_queue.pop }
|
|
111
|
+
rescue Timeout::Error
|
|
112
|
+
close
|
|
113
|
+
raise "WebSocket connection timed out after #{@connect_timeout}s"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
if result != :connected
|
|
117
|
+
close
|
|
118
|
+
raise "WebSocket connection failed: #{result}"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
@mutex.synchronize { @connected }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @return [Boolean]
|
|
125
|
+
def connected?
|
|
126
|
+
@mutex.synchronize { @connected }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Gracefully close the connection.
|
|
130
|
+
def close
|
|
131
|
+
@close_requested = true
|
|
132
|
+
@health_thread&.kill
|
|
133
|
+
@health_thread = nil
|
|
134
|
+
|
|
135
|
+
send_raw(EIO_CLOSE) if @ws
|
|
136
|
+
@ws&.close
|
|
137
|
+
@mutex.synchronize { @connected = false }
|
|
138
|
+
rescue StandardError
|
|
139
|
+
# Ignore errors during close
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def build_ws_url
|
|
145
|
+
parsed = URI.parse(@api_url)
|
|
146
|
+
ws_scheme = parsed.scheme == 'https' ? 'wss' : 'ws'
|
|
147
|
+
host = parsed.host
|
|
148
|
+
port = parsed.port
|
|
149
|
+
|
|
150
|
+
query_parts = ['EIO=4', 'transport=websocket']
|
|
151
|
+
query_parts << "organizationId=#{URI.encode_www_form_component(@organization_id)}" if @organization_id
|
|
152
|
+
query_parts << "source=#{URI.encode_www_form_component(@source)}" if @source
|
|
153
|
+
query_parts << "sdkVersion=#{URI.encode_www_form_component(@sdk_version)}" if @sdk_version
|
|
154
|
+
|
|
155
|
+
port_str = (parsed.scheme == 'https' && port == 443) || (parsed.scheme == 'http' && port == 80) ? '' : ":#{port}"
|
|
156
|
+
base_path = parsed.path.to_s.chomp('/')
|
|
157
|
+
"#{ws_scheme}://#{host}#{port_str}#{base_path}/socket.io/?#{query_parts.join('&')}"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def handle_raw_message(raw, connected_queue)
|
|
161
|
+
return if raw.nil? || raw.empty?
|
|
162
|
+
|
|
163
|
+
# Track all server activity for health monitoring.
|
|
164
|
+
# If the server stops sending ANY data (pings, events, etc.)
|
|
165
|
+
# the health monitor will detect the dead connection.
|
|
166
|
+
@last_server_activity = Time.now
|
|
167
|
+
|
|
168
|
+
case raw[0]
|
|
169
|
+
when EIO_OPEN
|
|
170
|
+
# Parse open payload for ping interval
|
|
171
|
+
begin
|
|
172
|
+
payload = JSON.parse(raw[1..])
|
|
173
|
+
@ping_interval = (payload['pingInterval'] || 25_000) / 1000.0
|
|
174
|
+
@ping_timeout = (payload['pingTimeout'] || 20_000) / 1000.0
|
|
175
|
+
rescue JSON::ParserError
|
|
176
|
+
# Use default ping interval
|
|
177
|
+
end
|
|
178
|
+
# Send Socket.IO CONNECT with auth
|
|
179
|
+
auth = JSON.generate({ token: @token })
|
|
180
|
+
send_raw("#{EIO_MESSAGE}#{SIO_CONNECT}#{auth}")
|
|
181
|
+
|
|
182
|
+
when EIO_PING
|
|
183
|
+
# Server heartbeat — respond immediately with PONG
|
|
184
|
+
send_raw(EIO_PONG)
|
|
185
|
+
|
|
186
|
+
when EIO_PONG
|
|
187
|
+
# Unexpected in EIO v4 (server doesn't respond to client pings),
|
|
188
|
+
# but handle gracefully — activity already tracked above.
|
|
189
|
+
nil
|
|
190
|
+
|
|
191
|
+
when EIO_MESSAGE
|
|
192
|
+
handle_socketio_packet(raw[1..], connected_queue)
|
|
193
|
+
|
|
194
|
+
when EIO_CLOSE
|
|
195
|
+
@mutex.synchronize { @connected = false }
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def handle_socketio_packet(data, connected_queue)
|
|
200
|
+
return if data.nil? || data.empty?
|
|
201
|
+
|
|
202
|
+
case data[0]
|
|
203
|
+
when SIO_CONNECT
|
|
204
|
+
# Connection acknowledged
|
|
205
|
+
@mutex.synchronize { @connected = true }
|
|
206
|
+
start_health_monitor
|
|
207
|
+
connected_queue&.push(:connected)
|
|
208
|
+
|
|
209
|
+
when SIO_CONNECT_ERROR
|
|
210
|
+
# Connection rejected
|
|
211
|
+
error_msg = begin
|
|
212
|
+
payload = JSON.parse(data[1..])
|
|
213
|
+
payload['message'] || 'Unknown error'
|
|
214
|
+
rescue JSON::ParserError
|
|
215
|
+
data[1..]
|
|
216
|
+
end
|
|
217
|
+
@mutex.synchronize { @connected = false }
|
|
218
|
+
connected_queue&.push("Auth rejected: #{error_msg}")
|
|
219
|
+
|
|
220
|
+
when SIO_EVENT
|
|
221
|
+
handle_event(data[1..])
|
|
222
|
+
|
|
223
|
+
when SIO_DISCONNECT
|
|
224
|
+
was_connected = @mutex.synchronize do
|
|
225
|
+
prev = @connected
|
|
226
|
+
@connected = false
|
|
227
|
+
prev
|
|
228
|
+
end
|
|
229
|
+
@on_disconnect&.call if was_connected && !@close_requested
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def handle_event(json_str)
|
|
234
|
+
return unless @on_event
|
|
235
|
+
|
|
236
|
+
# Skip namespace prefix if present (e.g., "/ns,")
|
|
237
|
+
if json_str&.start_with?('/')
|
|
238
|
+
comma_idx = json_str.index(',')
|
|
239
|
+
json_str = json_str[(comma_idx + 1)..] if comma_idx
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
event_array = JSON.parse(json_str)
|
|
243
|
+
return unless event_array.is_a?(Array) && event_array.length >= 1
|
|
244
|
+
|
|
245
|
+
event_name = event_array[0]
|
|
246
|
+
event_data = event_array[1]
|
|
247
|
+
|
|
248
|
+
@on_event.call(event_name, event_data)
|
|
249
|
+
rescue JSON::ParserError
|
|
250
|
+
# Malformed event, ignore
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Monitors connection health by checking for server activity.
|
|
254
|
+
# In Engine.IO v4, the server sends PING every pingInterval ms.
|
|
255
|
+
# If no server activity (pings, events, any data) is seen within
|
|
256
|
+
# pingInterval + pingTimeout, the connection is considered dead.
|
|
257
|
+
HEALTH_CHECK_INTERVAL = 5 # seconds — check frequently for fast detection
|
|
258
|
+
|
|
259
|
+
def start_health_monitor
|
|
260
|
+
@health_thread&.kill
|
|
261
|
+
@last_server_activity = Time.now
|
|
262
|
+
@health_thread = Thread.new do
|
|
263
|
+
loop do
|
|
264
|
+
sleep(HEALTH_CHECK_INTERVAL)
|
|
265
|
+
break unless connected?
|
|
266
|
+
|
|
267
|
+
if Time.now - @last_server_activity > @ping_interval + @ping_timeout
|
|
268
|
+
# No server activity within expected window — connection is dead
|
|
269
|
+
@mutex.synchronize { @connected = false }
|
|
270
|
+
@on_disconnect&.call unless @close_requested
|
|
271
|
+
# Force-close the dead WebSocket to ensure cleanup
|
|
272
|
+
@ws&.close rescue nil # rubocop:disable Style/RescueModifier
|
|
273
|
+
break
|
|
274
|
+
end
|
|
275
|
+
rescue StandardError
|
|
276
|
+
break
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def send_raw(msg)
|
|
282
|
+
@write_mutex.synchronize do
|
|
283
|
+
@ws&.send(msg)
|
|
284
|
+
end
|
|
285
|
+
rescue StandardError
|
|
286
|
+
# Ignore write errors
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
data/lib/daytona/config.rb
CHANGED
|
@@ -39,6 +39,16 @@ module Daytona
|
|
|
39
39
|
# @return [Boolean, nil]
|
|
40
40
|
attr_accessor :otel_enabled
|
|
41
41
|
|
|
42
|
+
# Observe sandbox state by legacy polling instead of WebSocket event streaming.
|
|
43
|
+
# Defaults to false (event streaming). Can also be enabled via the
|
|
44
|
+
# DAYTONA_USE_DEPRECATED_POLLING environment variable.
|
|
45
|
+
#
|
|
46
|
+
# @deprecated Polling-only mode will be removed in a future release;
|
|
47
|
+
# event streaming is the default and falls back to polling automatically
|
|
48
|
+
# when WebSockets are unavailable.
|
|
49
|
+
# @return [Boolean]
|
|
50
|
+
attr_accessor :use_deprecated_polling
|
|
51
|
+
|
|
42
52
|
# Experimental configuration options
|
|
43
53
|
#
|
|
44
54
|
# @return [Hash, nil] Experimental configuration hash
|
|
@@ -52,6 +62,9 @@ module Daytona
|
|
|
52
62
|
# @param organization_id [String, nil] Daytona organization ID. Defaults to ENV['DAYTONA_ORGANIZATION_ID'].
|
|
53
63
|
# @param target [String, nil] Daytona target. Defaults to ENV['DAYTONA_TARGET'].
|
|
54
64
|
# @param otel_enabled [Boolean, nil] Enable OpenTelemetry tracing for SDK operations.
|
|
65
|
+
# @param use_deprecated_polling [Boolean, nil] Observe sandbox state by legacy polling instead of
|
|
66
|
+
# WebSocket event streaming. Defaults to false (event streaming). Can also be enabled via the
|
|
67
|
+
# DAYTONA_USE_DEPRECATED_POLLING environment variable.
|
|
55
68
|
# @param _experimental [Hash, nil] Experimental configuration options.
|
|
56
69
|
def initialize( # rubocop:disable Metrics/ParameterLists
|
|
57
70
|
api_key: nil,
|
|
@@ -60,6 +73,7 @@ module Daytona
|
|
|
60
73
|
organization_id: nil,
|
|
61
74
|
target: nil,
|
|
62
75
|
otel_enabled: nil,
|
|
76
|
+
use_deprecated_polling: nil,
|
|
63
77
|
_experimental: nil
|
|
64
78
|
)
|
|
65
79
|
@env_reader = daytona_env_reader
|
|
@@ -71,6 +85,7 @@ module Daytona
|
|
|
71
85
|
@organization_id = organization_id || @env_reader.call('DAYTONA_ORGANIZATION_ID')
|
|
72
86
|
@otel_enabled = otel_enabled
|
|
73
87
|
@_experimental = _experimental
|
|
88
|
+
@use_deprecated_polling = resolve_use_deprecated_polling(use_deprecated_polling)
|
|
74
89
|
end
|
|
75
90
|
|
|
76
91
|
# Reads a DAYTONA_-prefixed environment variable using the same precedence
|
|
@@ -105,5 +120,11 @@ module Daytona
|
|
|
105
120
|
def daytona_filter(env_hash)
|
|
106
121
|
env_hash.select { |k, _| k.start_with?('DAYTONA_') }
|
|
107
122
|
end
|
|
123
|
+
|
|
124
|
+
def resolve_use_deprecated_polling(use_deprecated_polling)
|
|
125
|
+
return use_deprecated_polling unless use_deprecated_polling.nil?
|
|
126
|
+
|
|
127
|
+
@env_reader.call('DAYTONA_USE_DEPRECATED_POLLING') == 'true'
|
|
128
|
+
end
|
|
108
129
|
end
|
|
109
130
|
end
|
data/lib/daytona/daytona.rb
CHANGED
|
@@ -55,14 +55,42 @@ module Daytona
|
|
|
55
55
|
@secret = SecretService.new(DaytonaApiClient::SecretApi.new(api_client), otel_state:)
|
|
56
56
|
@object_storage_api = DaytonaApiClient::ObjectStorageApi.new(api_client)
|
|
57
57
|
@snapshots_api = DaytonaApiClient::SnapshotsApi.new(api_client)
|
|
58
|
-
@snapshot = SnapshotService.new(
|
|
59
|
-
|
|
58
|
+
@snapshot = SnapshotService.new(
|
|
59
|
+
snapshots_api:,
|
|
60
|
+
object_storage_api:,
|
|
61
|
+
default_region_id: config.target,
|
|
62
|
+
otel_state:
|
|
63
|
+
)
|
|
64
|
+
token = config.api_key || config.jwt_token
|
|
65
|
+
|
|
66
|
+
if config.use_deprecated_polling
|
|
67
|
+
warn(
|
|
68
|
+
'[DEPRECATION] Polling-only mode (use_deprecated_polling / DAYTONA_USE_DEPRECATED_POLLING) ' \
|
|
69
|
+
'is deprecated and will be removed in a future release.',
|
|
70
|
+
uplevel: 1
|
|
71
|
+
)
|
|
72
|
+
else
|
|
73
|
+
@event_dispatcher = EventDispatcher.new(
|
|
74
|
+
api_url: config.api_url,
|
|
75
|
+
token: token,
|
|
76
|
+
organization_id: config.organization_id,
|
|
77
|
+
source: SOURCE_RUBY,
|
|
78
|
+
sdk_version: Sdk::VERSION
|
|
79
|
+
)
|
|
80
|
+
@event_dispatcher.ensure_connected
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
@subscription_manager = EventSubscriptionManager.new(@event_dispatcher)
|
|
60
84
|
end
|
|
61
85
|
|
|
62
86
|
# Shuts down OTel providers, flushing any pending telemetry data.
|
|
63
87
|
#
|
|
64
88
|
# @return [void]
|
|
65
89
|
def close
|
|
90
|
+
@subscription_manager&.shutdown
|
|
91
|
+
@subscription_manager = nil
|
|
92
|
+
@event_dispatcher&.disconnect
|
|
93
|
+
@event_dispatcher = nil
|
|
66
94
|
::Daytona.shutdown_otel(@otel_state)
|
|
67
95
|
@otel_state = nil
|
|
68
96
|
end
|
|
@@ -71,7 +99,7 @@ module Daytona
|
|
|
71
99
|
#
|
|
72
100
|
# @param params [Daytona::CreateSandboxFromSnapshotParams, Daytona::CreateSandboxFromImageParams, Nil] Sandbox creation parameters
|
|
73
101
|
# @return [Daytona::Sandbox] The created sandbox
|
|
74
|
-
# @raise [Daytona::Sdk::Error] If auto_stop_interval, auto_pause_interval, or
|
|
102
|
+
# @raise [Daytona::Sdk::Error] If auto_stop_interval, auto_pause_interval, auto_archive_interval, or ttl_minutes is negative,
|
|
75
103
|
# or if auto_stop_interval and auto_pause_interval are both non-zero
|
|
76
104
|
def create(params = nil, on_snapshot_create_logs: nil)
|
|
77
105
|
if params.nil?
|
|
@@ -91,8 +119,9 @@ module Daytona
|
|
|
91
119
|
# Deletes a Sandbox.
|
|
92
120
|
#
|
|
93
121
|
# @param sandbox [Daytona::Sandbox]
|
|
122
|
+
# @param wait [Boolean] When +true+, block until the Sandbox is destroyed.
|
|
94
123
|
# @return [void]
|
|
95
|
-
def delete(sandbox) = sandbox.delete
|
|
124
|
+
def delete(sandbox, wait: false) = sandbox.delete(wait:)
|
|
96
125
|
|
|
97
126
|
# Gets a Sandbox by its ID.
|
|
98
127
|
#
|
|
@@ -208,7 +237,7 @@ module Daytona
|
|
|
208
237
|
# @param timeout [Numeric] Maximum wait time in seconds (defaults to 60 s).
|
|
209
238
|
# @param on_snapshot_create_logs [Proc]
|
|
210
239
|
# @return [Daytona::Sandbox] The created sandbox
|
|
211
|
-
# @raise [Daytona::Sdk::Error] If auto_stop_interval, auto_pause_interval, or
|
|
240
|
+
# @raise [Daytona::Sdk::Error] If auto_stop_interval, auto_pause_interval, auto_archive_interval, or ttl_minutes is negative,
|
|
212
241
|
# or if auto_stop_interval and auto_pause_interval are both non-zero
|
|
213
242
|
def _create(params, timeout: 60, on_snapshot_create_logs: nil)
|
|
214
243
|
raise Sdk::Error, 'Timeout must be a non-negative number' if timeout.negative?
|
|
@@ -232,6 +261,8 @@ module Daytona
|
|
|
232
261
|
raise Sdk::Error, 'auto_archive_interval must be a non-negative integer'
|
|
233
262
|
end
|
|
234
263
|
|
|
264
|
+
raise Sdk::Error, 'ttl_minutes must be a non-negative integer' if params.ttl_minutes&.negative?
|
|
265
|
+
|
|
235
266
|
labels = params.labels&.dup || {}
|
|
236
267
|
labels[CODE_TOOLBOX_LANGUAGE_LABEL] = params.language.to_s if params.language
|
|
237
268
|
|
|
@@ -245,6 +276,7 @@ module Daytona
|
|
|
245
276
|
auto_pause_interval: params.auto_pause_interval,
|
|
246
277
|
auto_archive_interval: params.auto_archive_interval,
|
|
247
278
|
auto_delete_interval: params.auto_delete_interval,
|
|
279
|
+
ttl_minutes: params.ttl_minutes,
|
|
248
280
|
volumes: params.volumes,
|
|
249
281
|
secrets: params.secrets&.map { |env_var, secret_name| { env_var.to_s => secret_name.to_s } },
|
|
250
282
|
network_block_all: params.network_block_all,
|
|
@@ -352,7 +384,8 @@ module Daytona
|
|
|
352
384
|
config:,
|
|
353
385
|
sandbox_api:,
|
|
354
386
|
otel_state: @otel_state,
|
|
355
|
-
analytics_api_url_provider: method(:analytics_api_url)
|
|
387
|
+
analytics_api_url_provider: method(:analytics_api_url),
|
|
388
|
+
subscription_manager: @subscription_manager
|
|
356
389
|
)
|
|
357
390
|
end
|
|
358
391
|
|
data/lib/daytona/file_system.rb
CHANGED
|
@@ -402,7 +402,7 @@ module Daytona
|
|
|
402
402
|
# end
|
|
403
403
|
# end
|
|
404
404
|
def replace_in_files(files:, pattern:, new_value:)
|
|
405
|
-
replace_request =
|
|
405
|
+
replace_request = DaytonaToolboxApiClient::ReplaceRequest.new(
|
|
406
406
|
files: files,
|
|
407
407
|
pattern: pattern,
|
|
408
408
|
new_value: new_value
|
data/lib/daytona/lsp_server.rb
CHANGED
|
@@ -56,7 +56,7 @@ module Daytona
|
|
|
56
56
|
language_id:,
|
|
57
57
|
path_to_project:,
|
|
58
58
|
uri: uri(path),
|
|
59
|
-
position:
|
|
59
|
+
position: DaytonaToolboxApiClient::LspPosition.new(line: position.line, character: position.character)
|
|
60
60
|
)
|
|
61
61
|
)
|
|
62
62
|
end
|