daytona 0.200.0 → 0.201.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: de70d9e56ce97468d9ef3bcf8c7dbe0b70f74ee7e64a7ac1d0e8642857a3c257
4
- data.tar.gz: 3cec447b03a65f42bcd84c49e719cd8771a1404f496384afd5e797b429e47fb1
3
+ metadata.gz: 684e4304ffae6f21d39f795fbcd75754e0148adf43a86963e3c8303b6b9eb87e
4
+ data.tar.gz: 3d4dc419e7b759cce74db9df2cec0bb36900493c39d08b5640b04dd5c4863b11
5
5
  SHA512:
6
- metadata.gz: 91c94980be6003996bbbf4b447b170e9165cbc3a47e78c47f91886f28accd5f7b083b04903fc59dede9bbe56ccdcaf9957da73e07202e762ca928203274b2c8b
7
- data.tar.gz: db39e610c03a011d4c2468475393ca7b7bd17cc1087dbb0edd46c9fc6959da18e0ca61d861485ca0b27faa8ccb4061f631ec44c97d90d8e6bea7db75317db4e3
6
+ metadata.gz: 59a7fea3c20c801d9e3d09bad69f6a4318f5b2ae64d397e2848ee893e8f5e1117c973f626ba825fce6bb4604f2875e1f4d393391b0c7dc8b850459a7d7073bd1
7
+ data.tar.gz: ade5a786482e476b4e3baf42eea6828e0d422ec83e1dc1363130165a8ae09b9a5e0d99be687848d5c50f6e4c0ce09342244ddfa1553d9effbff882ad3bd58267
@@ -232,8 +232,8 @@ module Daytona
232
232
  def create_context(cwd: nil)
233
233
  request = DaytonaToolboxApiClient::CreateContextRequest.new(cwd:)
234
234
  @toolbox_api.create_interpreter_context(request)
235
- rescue StandardError => e
236
- raise Sdk::Error, "Failed to create interpreter context: #{e.message}"
235
+ rescue *Sdk::API_ERROR_CLASSES => e
236
+ raise Sdk.wrap_error(e, 'Failed to create interpreter context')
237
237
  end
238
238
 
239
239
  # List all user-created interpreter contexts.
@@ -252,8 +252,8 @@ module Daytona
252
252
  def list_contexts
253
253
  response = @toolbox_api.list_interpreter_contexts
254
254
  response.contexts || []
255
- rescue StandardError => e
256
- raise Sdk::Error, "Failed to list interpreter contexts: #{e.message}"
255
+ rescue *Sdk::API_ERROR_CLASSES => e
256
+ raise Sdk.wrap_error(e, 'Failed to list interpreter contexts')
257
257
  end
258
258
 
259
259
  # Delete an interpreter context and shut down all associated processes.
@@ -272,8 +272,8 @@ module Daytona
272
272
  def delete_context(context)
273
273
  @toolbox_api.delete_interpreter_context(context.id)
274
274
  nil
275
- rescue StandardError => e
276
- raise Sdk::Error, "Failed to delete interpreter context: #{e.message}"
275
+ rescue *Sdk::API_ERROR_CLASSES => e
276
+ raise Sdk.wrap_error(e, 'Failed to delete interpreter context')
277
277
  end
278
278
 
279
279
  instrument :run_code, :create_context, :list_contexts, :delete_context,
@@ -6,14 +6,29 @@
6
6
  require 'securerandom'
7
7
 
8
8
  module Daytona
9
+ # Tracks dispatcher subscriptions with TTL auto-expiry.
10
+ #
11
+ # All subscriptions share ONE lazily started expiry worker thread instead of a
12
+ # dedicated sleeping thread per subscription, so listing many sandboxes costs
13
+ # one thread total, not one thread each
14
+ # (https://github.com/daytona/clients/issues/108). #refresh only bumps the
15
+ # subscription deadline — it never creates or kills threads.
9
16
  class EventSubscriptionManager
10
17
  SUBSCRIPTION_TTL = 300
11
18
  private_constant :SUBSCRIPTION_TTL
12
19
 
13
- def initialize(dispatcher = nil)
20
+ def initialize(dispatcher = nil, ttl_seconds: SUBSCRIPTION_TTL)
14
21
  @dispatcher = dispatcher
22
+ @ttl = ttl_seconds
15
23
  @subscriptions = {}
24
+ # Deadline-ordered [expires_at, sub_id] entries. Entries are lazily
25
+ # invalidated: a popped entry is discarded when its subscription is gone,
26
+ # or re-queued at the current deadline when it was refreshed meanwhile.
27
+ # Invariant: every live subscription has at least one queue entry.
28
+ @expiry_queue = []
16
29
  @mutex = Mutex.new
30
+ @cond = ConditionVariable.new
31
+ @worker = nil
17
32
  @closed = false
18
33
  end
19
34
 
@@ -24,7 +39,6 @@ module Daytona
24
39
 
25
40
  unsubscribe = @dispatcher.subscribe(resource_id, events:, &handler)
26
41
  sub_id = SecureRandom.hex(16)
27
- timer_to_stop = nil
28
42
  rollback_unsubscribe = nil
29
43
 
30
44
  @mutex.synchronize do
@@ -34,8 +48,11 @@ module Daytona
34
48
  next
35
49
  end
36
50
 
37
- @subscriptions[sub_id] = { unsubscribe:, timer: nil }
38
- timer_to_stop = start_timer_locked(sub_id)
51
+ expires_at = monotonic_now + @ttl
52
+ @subscriptions[sub_id] = { unsubscribe:, expires_at: }
53
+ push_entry_locked(expires_at, sub_id)
54
+ ensure_worker_locked
55
+ @cond.signal
39
56
  end
40
57
 
41
58
  if rollback_unsubscribe
@@ -43,44 +60,36 @@ module Daytona
43
60
  return nil
44
61
  end
45
62
 
46
- stop_thread(timer_to_stop)
47
63
  sub_id
48
64
  end
49
65
 
50
66
  def refresh(sub_id)
51
67
  @mutex.synchronize do
52
- # Reject after shutdown to prevent use-after-close
53
68
  return false if @closed
54
- end
55
-
56
- timer_to_stop = nil
57
69
 
58
- @mutex.synchronize do
59
- return false unless @subscriptions.key?(sub_id)
70
+ subscription = @subscriptions[sub_id]
71
+ return false unless subscription
60
72
 
61
- timer_to_stop = start_timer_locked(sub_id)
73
+ # No queue entry or worker wake-up needed: when the old deadline pops,
74
+ # the worker sees the newer expires_at and re-queues the subscription.
75
+ subscription[:expires_at] = monotonic_now + @ttl
76
+ true
62
77
  end
63
-
64
- stop_thread(timer_to_stop)
65
- true
66
78
  end
67
79
 
68
80
  def unsubscribe(sub_id)
69
- subscription = nil
70
- timer = nil
71
-
72
- @mutex.synchronize do
73
- subscription = @subscriptions.delete(sub_id)
74
- if subscription
75
- timer = subscription[:timer]
76
- subscription[:timer] = nil
81
+ subscription = @mutex.synchronize do
82
+ removed = @subscriptions.delete(sub_id)
83
+ # The stale queue entry is discarded when the worker pops it — except
84
+ # when it was the last subscription: drop the leftovers and wake the
85
+ # worker so it exits now instead of idling until the old deadline.
86
+ if removed && @subscriptions.empty?
87
+ @expiry_queue.clear
88
+ @cond.signal
77
89
  end
90
+ removed
78
91
  end
79
-
80
- return unless subscription
81
-
82
- stop_thread(timer)
83
- subscription[:unsubscribe].call
92
+ subscription&.dig(:unsubscribe)&.call
84
93
  end
85
94
 
86
95
  def shutdown
@@ -90,55 +99,96 @@ module Daytona
90
99
  @closed = true
91
100
  subscriptions = @subscriptions.values
92
101
  @subscriptions = {}
102
+ @expiry_queue.clear
103
+ @cond.broadcast
93
104
  end
94
105
 
95
- subscriptions.each do |subscription|
96
- stop_thread(subscription[:timer])
97
- subscription[:unsubscribe].call
98
- end
106
+ subscriptions.each { |subscription| subscription[:unsubscribe].call }
99
107
  end
100
108
 
101
109
  private
102
110
 
103
- def start_timer_locked(sub_id)
104
- subscription = @subscriptions[sub_id]
105
- return unless subscription
111
+ def monotonic_now
112
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
113
+ end
106
114
 
107
- previous_timer = cancel_timer_locked(sub_id)
108
- timer = Thread.new do
109
- sleep(SUBSCRIPTION_TTL)
115
+ # Caller must hold @mutex.
116
+ def push_entry_locked(expires_at, sub_id)
117
+ index = @expiry_queue.bsearch_index { |entry| entry[0] > expires_at } || @expiry_queue.length
118
+ @expiry_queue.insert(index, [expires_at, sub_id])
119
+ end
110
120
 
111
- unsubscribe = @mutex.synchronize do
112
- current_subscription = @subscriptions[sub_id]
113
- next unless current_subscription && current_subscription[:timer] == Thread.current
121
+ # Caller must hold @mutex. The alive? check is a safety net: a worker that
122
+ # crashed for any reason leaves a dead-but-truthy reference, and a plain nil
123
+ # check would then block replacements forever, silently disabling expiry.
124
+ def ensure_worker_locked
125
+ return if @worker&.alive?
114
126
 
115
- @subscriptions.delete(sub_id)&.dig(:unsubscribe)
116
- end
127
+ @worker = Thread.new { expiry_loop }
128
+ @worker.name = 'daytona-subscription-expiry'
129
+ @worker.abort_on_exception = false
130
+ end
117
131
 
118
- unsubscribe&.call
132
+ def expiry_loop
133
+ loop do
134
+ expired = collect_expired
135
+ return if expired.nil?
136
+
137
+ expired.each do |subscription|
138
+ subscription[:unsubscribe].call
139
+ rescue StandardError
140
+ # One failing dispatcher unsubscribe must not kill the shared worker
141
+ # and silently stop expiry for every other subscription.
142
+ nil
143
+ end
119
144
  end
120
- timer.abort_on_exception = false
121
- subscription[:timer] = timer
122
- previous_timer
123
145
  end
124
146
 
125
- def cancel_timer_locked(sub_id)
126
- subscription = @subscriptions[sub_id]
127
- return unless subscription
147
+ # Blocks until at least one subscription expires. Returns nil when the
148
+ # worker should exit (shutdown, or no live subscriptions remain).
149
+ def collect_expired
150
+ @mutex.synchronize do
151
+ expired = []
152
+
153
+ while expired.empty?
154
+ return nil if @closed
155
+
156
+ if @expiry_queue.empty?
157
+ # No live subscriptions remain (see queue invariant above):
158
+ # exit and let the next subscribe start a fresh worker.
159
+ @worker = nil
160
+ return nil
161
+ end
162
+
163
+ now = monotonic_now
164
+ deadline = @expiry_queue.first[0]
165
+ if deadline > now
166
+ @cond.wait(@mutex, deadline - now)
167
+ next
168
+ end
169
+
170
+ drain_due_entries_locked(now, expired)
171
+ end
128
172
 
129
- timer = subscription[:timer]
130
- subscription[:timer] = nil
131
- timer
173
+ expired
174
+ end
132
175
  end
133
176
 
134
- def stop_thread(thread)
135
- return unless thread
136
- return if thread == Thread.current
177
+ # Caller must hold @mutex.
178
+ def drain_due_entries_locked(now, expired)
179
+ while !@expiry_queue.empty? && @expiry_queue.first[0] <= now
180
+ _, sub_id = @expiry_queue.shift
181
+ subscription = @subscriptions[sub_id]
182
+ next unless subscription
137
183
 
138
- thread.kill
139
- thread.join
140
- rescue StandardError
141
- nil
184
+ if subscription[:expires_at] > now
185
+ push_entry_locked(subscription[:expires_at], sub_id)
186
+ next
187
+ end
188
+
189
+ @subscriptions.delete(sub_id)
190
+ expired << subscription
191
+ end
142
192
  end
143
193
  end
144
194
  end
@@ -89,6 +89,8 @@ module Daytona
89
89
  :created_at_before,
90
90
  :last_activity_after,
91
91
  :last_activity_before,
92
+ :auto_destroy_at_after,
93
+ :auto_destroy_at_before,
92
94
  :sort,
93
95
  :order
94
96
  ) do
@@ -33,8 +33,8 @@ module Daytona
33
33
  # puts "Mouse is at: #{position.x}, #{position.y}"
34
34
  def position
35
35
  toolbox_api.get_mouse_position
36
- rescue StandardError => e
37
- raise Sdk::Error, "Failed to get mouse position: #{e.message}"
36
+ rescue *Sdk::API_ERROR_CLASSES => e
37
+ raise Sdk.wrap_error(e, 'Failed to get mouse position')
38
38
  end
39
39
 
40
40
  # Moves the mouse cursor to the specified coordinates.
@@ -50,8 +50,8 @@ module Daytona
50
50
  def move(x:, y:)
51
51
  request = DaytonaToolboxApiClient::MouseMoveRequest.new(x:, y:)
52
52
  toolbox_api.move_mouse(request)
53
- rescue StandardError => e
54
- raise Sdk::Error, "Failed to move mouse: #{e.message}"
53
+ rescue *Sdk::API_ERROR_CLASSES => e
54
+ raise Sdk.wrap_error(e, 'Failed to move mouse')
55
55
  end
56
56
 
57
57
  # Clicks the mouse at the specified coordinates.
@@ -75,8 +75,8 @@ module Daytona
75
75
  def click(x:, y:, button: 'left', double: false)
76
76
  request = DaytonaToolboxApiClient::MouseClickRequest.new(x:, y:, button:, double:)
77
77
  toolbox_api.click(request)
78
- rescue StandardError => e
79
- raise Sdk::Error, "Failed to click mouse: #{e.message}"
78
+ rescue *Sdk::API_ERROR_CLASSES => e
79
+ raise Sdk.wrap_error(e, 'Failed to click mouse')
80
80
  end
81
81
 
82
82
  # Drags the mouse from start coordinates to end coordinates.
@@ -95,8 +95,8 @@ module Daytona
95
95
  def drag(start_x:, start_y:, end_x:, end_y:, button: 'left')
96
96
  request = DaytonaToolboxApiClient::MouseDragRequest.new(start_x:, start_y:, end_x:, end_y:, button:)
97
97
  toolbox_api.drag(request)
98
- rescue StandardError => e
99
- raise Sdk::Error, "Failed to drag mouse: #{e.message}"
98
+ rescue *Sdk::API_ERROR_CLASSES => e
99
+ raise Sdk.wrap_error(e, 'Failed to drag mouse')
100
100
  end
101
101
 
102
102
  # Scrolls the mouse wheel at the specified coordinates.
@@ -118,8 +118,8 @@ module Daytona
118
118
  request = DaytonaToolboxApiClient::MouseScrollRequest.new(x:, y:, direction:, amount:)
119
119
  toolbox_api.scroll(request)
120
120
  true
121
- rescue StandardError => e
122
- raise Sdk::Error, "Failed to scroll mouse: #{e.message}"
121
+ rescue *Sdk::API_ERROR_CLASSES => e
122
+ raise Sdk.wrap_error(e, 'Failed to scroll mouse')
123
123
  end
124
124
 
125
125
  instrument :position, :move, :click, :drag, :scroll, component: 'Mouse'
@@ -164,8 +164,8 @@ module Daytona
164
164
  def type(text:, delay: nil)
165
165
  request = DaytonaToolboxApiClient::KeyboardTypeRequest.new(text:, delay:)
166
166
  toolbox_api.type_text(request)
167
- rescue StandardError => e
168
- raise Sdk::Error, "Failed to type text: #{e.message}"
167
+ rescue *Sdk::API_ERROR_CLASSES => e
168
+ raise Sdk.wrap_error(e, 'Failed to type text')
169
169
  end
170
170
 
171
171
  # Presses a key with optional modifiers.
@@ -187,8 +187,8 @@ module Daytona
187
187
  def press(key:, modifiers: nil)
188
188
  request = DaytonaToolboxApiClient::KeyboardPressRequest.new(key:, modifiers: modifiers || [])
189
189
  toolbox_api.press_key(request)
190
- rescue StandardError => e
191
- raise Sdk::Error, "Failed to press key: #{e.message}"
190
+ rescue *Sdk::API_ERROR_CLASSES => e
191
+ raise Sdk.wrap_error(e, 'Failed to press key')
192
192
  end
193
193
 
194
194
  # Presses a hotkey combination.
@@ -209,8 +209,8 @@ module Daytona
209
209
  def hotkey(keys:)
210
210
  request = DaytonaToolboxApiClient::KeyboardHotkeyRequest.new(keys:)
211
211
  toolbox_api.press_hotkey(request)
212
- rescue StandardError => e
213
- raise Sdk::Error, "Failed to press hotkey: #{e.message}"
212
+ rescue *Sdk::API_ERROR_CLASSES => e
213
+ raise Sdk.wrap_error(e, 'Failed to press hotkey')
214
214
  end
215
215
 
216
216
  instrument :type, :press, :hotkey, component: 'Keyboard'
@@ -254,8 +254,8 @@ module Daytona
254
254
  # with_cursor = sandbox.computer_use.screenshot.take_full_screen(show_cursor: true)
255
255
  def take_full_screen(show_cursor: false)
256
256
  toolbox_api.take_screenshot(show_cursor:)
257
- rescue StandardError => e
258
- raise Sdk::Error, "Failed to take screenshot: #{e.message}"
257
+ rescue *Sdk::API_ERROR_CLASSES => e
258
+ raise Sdk.wrap_error(e, 'Failed to take screenshot')
259
259
  end
260
260
 
261
261
  # Takes a screenshot of a specific region.
@@ -271,8 +271,8 @@ module Daytona
271
271
  # puts "Captured region: #{screenshot.region.width}x#{screenshot.region.height}"
272
272
  def take_region(region:, show_cursor: false)
273
273
  toolbox_api.take_region_screenshot(region.height, region.width, region.y, region.x, show_cursor:)
274
- rescue StandardError => e
275
- raise Sdk::Error, "Failed to take region screenshot: #{e.message}"
274
+ rescue *Sdk::API_ERROR_CLASSES => e
275
+ raise Sdk.wrap_error(e, 'Failed to take region screenshot')
276
276
  end
277
277
 
278
278
  # Takes a compressed screenshot of the entire screen.
@@ -303,8 +303,8 @@ module Daytona
303
303
  format: options.fmt,
304
304
  show_cursor: options.show_cursor
305
305
  )
306
- rescue StandardError => e
307
- raise Sdk::Error, "Failed to take compressed screenshot: #{e.message}"
306
+ rescue *Sdk::API_ERROR_CLASSES => e
307
+ raise Sdk.wrap_error(e, 'Failed to take compressed screenshot')
308
308
  end
309
309
 
310
310
  # Takes a compressed screenshot of a specific region.
@@ -334,8 +334,8 @@ module Daytona
334
334
  format: options.fmt,
335
335
  show_cursor: options.show_cursor
336
336
  )
337
- rescue StandardError => e
338
- raise Sdk::Error, "Failed to take compressed region screenshot: #{e.message}"
337
+ rescue *Sdk::API_ERROR_CLASSES => e
338
+ raise Sdk.wrap_error(e, 'Failed to take compressed region screenshot')
339
339
  end
340
340
 
341
341
  instrument :take_full_screen, :take_region, :take_compressed, :take_compressed_region,
@@ -380,8 +380,8 @@ module Daytona
380
380
  # end
381
381
  def info
382
382
  toolbox_api.get_display_info
383
- rescue StandardError => e
384
- raise Sdk::Error, "Failed to get display info: #{e.message}"
383
+ rescue *Sdk::API_ERROR_CLASSES => e
384
+ raise Sdk.wrap_error(e, 'Failed to get display info')
385
385
  end
386
386
 
387
387
  # Gets the list of open windows.
@@ -397,8 +397,8 @@ module Daytona
397
397
  # end
398
398
  def windows
399
399
  toolbox_api.get_windows
400
- rescue StandardError => e
401
- raise Sdk::Error, "Failed to get windows: #{e.message}"
400
+ rescue *Sdk::API_ERROR_CLASSES => e
401
+ raise Sdk.wrap_error(e, 'Failed to get windows')
402
402
  end
403
403
 
404
404
  instrument :info, :windows, component: 'Display'
@@ -446,8 +446,8 @@ module Daytona
446
446
  opts[:max_depth] = max_depth unless max_depth.nil?
447
447
 
448
448
  toolbox_api.get_accessibility_tree(opts)
449
- rescue StandardError => e
450
- raise Sdk::Error, "Failed to get accessibility tree: #{e.message}"
449
+ rescue *Sdk::API_ERROR_CLASSES => e
450
+ raise Sdk.wrap_error(e, 'Failed to get accessibility tree')
451
451
  end
452
452
 
453
453
  # Finds AT-SPI accessibility nodes matching the provided filters.
@@ -482,8 +482,8 @@ module Daytona
482
482
 
483
483
  request = DaytonaToolboxApiClient::FindAccessibilityNodesRequest.new(attrs)
484
484
  toolbox_api.find_accessibility_nodes(request)
485
- rescue StandardError => e
486
- raise Sdk::Error, "Failed to find accessibility nodes: #{e.message}"
485
+ rescue *Sdk::API_ERROR_CLASSES => e
486
+ raise Sdk.wrap_error(e, 'Failed to find accessibility nodes')
487
487
  end
488
488
 
489
489
  # Focuses an AT-SPI accessibility node.
@@ -496,8 +496,8 @@ module Daytona
496
496
  def focus_node(id:)
497
497
  request = DaytonaToolboxApiClient::AccessibilityNodeRequest.new(id:)
498
498
  toolbox_api.focus_accessibility_node(request)
499
- rescue StandardError => e
500
- raise Sdk::Error, "Failed to focus accessibility node: #{e.message}"
499
+ rescue *Sdk::API_ERROR_CLASSES => e
500
+ raise Sdk.wrap_error(e, 'Failed to focus accessibility node')
501
501
  end
502
502
 
503
503
  # Invokes an AT-SPI accessibility node action.
@@ -514,8 +514,8 @@ module Daytona
514
514
 
515
515
  request = DaytonaToolboxApiClient::AccessibilityInvokeRequest.new(attrs)
516
516
  toolbox_api.invoke_accessibility_node(request)
517
- rescue StandardError => e
518
- raise Sdk::Error, "Failed to invoke accessibility node: #{e.message}"
517
+ rescue *Sdk::API_ERROR_CLASSES => e
518
+ raise Sdk.wrap_error(e, 'Failed to invoke accessibility node')
519
519
  end
520
520
 
521
521
  # Sets an AT-SPI accessibility node value.
@@ -529,8 +529,8 @@ module Daytona
529
529
  def set_node_value(id:, value:)
530
530
  request = DaytonaToolboxApiClient::AccessibilitySetValueRequest.new(id:, value:)
531
531
  toolbox_api.set_accessibility_node_value(request)
532
- rescue StandardError => e
533
- raise Sdk::Error, "Failed to set accessibility node value: #{e.message}"
532
+ rescue *Sdk::API_ERROR_CLASSES => e
533
+ raise Sdk.wrap_error(e, 'Failed to set accessibility node value')
534
534
  end
535
535
 
536
536
  instrument :get_tree, :find_nodes, :focus_node, :invoke_node, :set_node_value,
@@ -627,8 +627,8 @@ module Daytona
627
627
  def start(label: nil)
628
628
  request = DaytonaToolboxApiClient::StartRecordingRequest.new(label:)
629
629
  toolbox_api.start_recording(request: request)
630
- rescue StandardError => e
631
- raise Sdk::Error, "Failed to start recording: #{e.message}"
630
+ rescue *Sdk::API_ERROR_CLASSES => e
631
+ raise Sdk.wrap_error(e, 'Failed to start recording')
632
632
  end
633
633
 
634
634
  # Stops an active screen recording session.
@@ -644,8 +644,8 @@ module Daytona
644
644
  def stop(id:)
645
645
  request = DaytonaToolboxApiClient::StopRecordingRequest.new(id: id)
646
646
  toolbox_api.stop_recording(request)
647
- rescue StandardError => e
648
- raise Sdk::Error, "Failed to stop recording: #{e.message}"
647
+ rescue *Sdk::API_ERROR_CLASSES => e
648
+ raise Sdk.wrap_error(e, 'Failed to stop recording')
649
649
  end
650
650
 
651
651
  # Lists all recordings (active and completed).
@@ -661,8 +661,8 @@ module Daytona
661
661
  # end
662
662
  def list
663
663
  toolbox_api.list_recordings
664
- rescue StandardError => e
665
- raise Sdk::Error, "Failed to list recordings: #{e.message}"
664
+ rescue *Sdk::API_ERROR_CLASSES => e
665
+ raise Sdk.wrap_error(e, 'Failed to list recordings')
666
666
  end
667
667
 
668
668
  # Gets details of a specific recording by ID.
@@ -678,8 +678,8 @@ module Daytona
678
678
  # puts "Duration: #{recording.duration_seconds} seconds"
679
679
  def get(id:)
680
680
  toolbox_api.get_recording(id)
681
- rescue StandardError => e
682
- raise Sdk::Error, "Failed to get recording: #{e.message}"
681
+ rescue *Sdk::API_ERROR_CLASSES => e
682
+ raise Sdk.wrap_error(e, 'Failed to get recording')
683
683
  end
684
684
 
685
685
  # Deletes a recording by ID.
@@ -693,8 +693,8 @@ module Daytona
693
693
  # puts "Recording deleted"
694
694
  def delete(id:)
695
695
  toolbox_api.delete_recording(id)
696
- rescue StandardError => e
697
- raise Sdk::Error, "Failed to delete recording: #{e.message}"
696
+ rescue *Sdk::API_ERROR_CLASSES => e
697
+ raise Sdk.wrap_error(e, 'Failed to delete recording')
698
698
  end
699
699
 
700
700
  # Downloads a recording file and saves it to a local path.
@@ -815,8 +815,8 @@ module Daytona
815
815
  # puts "Computer use processes started: #{result.message}"
816
816
  def start
817
817
  toolbox_api.start_computer_use
818
- rescue StandardError => e
819
- raise Sdk::Error, "Failed to start computer use: #{e.message}"
818
+ rescue *Sdk::API_ERROR_CLASSES => e
819
+ raise Sdk.wrap_error(e, 'Failed to start computer use')
820
820
  end
821
821
 
822
822
  # Stops all computer use processes.
@@ -829,8 +829,8 @@ module Daytona
829
829
  # puts "Computer use processes stopped: #{result.message}"
830
830
  def stop
831
831
  toolbox_api.stop_computer_use
832
- rescue StandardError => e
833
- raise Sdk::Error, "Failed to stop computer use: #{e.message}"
832
+ rescue *Sdk::API_ERROR_CLASSES => e
833
+ raise Sdk.wrap_error(e, 'Failed to stop computer use')
834
834
  end
835
835
 
836
836
  # Gets the status of all computer use processes.
@@ -843,8 +843,8 @@ module Daytona
843
843
  # puts "Computer use status: #{response.status}"
844
844
  def status
845
845
  toolbox_api.get_computer_use_status
846
- rescue StandardError => e
847
- raise Sdk::Error, "Failed to get computer use status: #{e.message}"
846
+ rescue *Sdk::API_ERROR_CLASSES => e
847
+ raise Sdk.wrap_error(e, 'Failed to get computer use status')
848
848
  end
849
849
 
850
850
  # Gets the status of a specific VNC process.
@@ -858,8 +858,8 @@ module Daytona
858
858
  # no_vnc_status = sandbox.computer_use.get_process_status("novnc")
859
859
  def get_process_status(process_name:)
860
860
  toolbox_api.get_process_status(process_name, sandbox_id)
861
- rescue StandardError => e
862
- raise Sdk::Error, "Failed to get process status: #{e.message}"
861
+ rescue *Sdk::API_ERROR_CLASSES => e
862
+ raise Sdk.wrap_error(e, 'Failed to get process status')
863
863
  end
864
864
 
865
865
  # Restarts a specific VNC process.
@@ -873,8 +873,8 @@ module Daytona
873
873
  # puts "XFCE4 process restarted: #{result.message}"
874
874
  def restart_process(process_name:)
875
875
  toolbox_api.restart_process(process_name, sandbox_id)
876
- rescue StandardError => e
877
- raise Sdk::Error, "Failed to restart process: #{e.message}"
876
+ rescue *Sdk::API_ERROR_CLASSES => e
877
+ raise Sdk.wrap_error(e, 'Failed to restart process')
878
878
  end
879
879
 
880
880
  # Gets logs for a specific VNC process.
@@ -888,8 +888,8 @@ module Daytona
888
888
  # puts "NoVNC logs: #{logs}"
889
889
  def get_process_logs(process_name:)
890
890
  toolbox_api.get_process_logs(process_name, sandbox_id)
891
- rescue StandardError => e
892
- raise Sdk::Error, "Failed to get process logs: #{e.message}"
891
+ rescue *Sdk::API_ERROR_CLASSES => e
892
+ raise Sdk.wrap_error(e, 'Failed to get process logs')
893
893
  end
894
894
 
895
895
  # Gets error logs for a specific VNC process.
@@ -903,8 +903,8 @@ module Daytona
903
903
  # puts "X11VNC errors: #{errors}"
904
904
  def get_process_errors(process_name:)
905
905
  toolbox_api.get_process_errors(process_name, sandbox_id)
906
- rescue StandardError => e
907
- raise Sdk::Error, "Failed to get process errors: #{e.message}"
906
+ rescue *Sdk::API_ERROR_CLASSES => e
907
+ raise Sdk.wrap_error(e, 'Failed to get process errors')
908
908
  end
909
909
 
910
910
  instrument :start, :stop, :status, :get_process_status, :restart_process,
@@ -127,9 +127,12 @@ module Daytona
127
127
  #
128
128
  # @param id [String]
129
129
  # @return [Daytona::Sandbox]
130
+ # @raise [Daytona::Sdk::Error]
130
131
  def get(id)
131
132
  sandbox_dto = sandbox_api.get_sandbox(id)
132
133
  to_sandbox(sandbox_dto:)
134
+ rescue *Sdk::API_ERROR_CLASSES => e
135
+ raise Sdk.wrap_error(e, "Failed to get sandbox #{id}")
133
136
  end
134
137
 
135
138
  # Iterates over Sandboxes matching the given query.
@@ -222,11 +225,15 @@ module Daytona
222
225
  created_at_before: q.created_at_before,
223
226
  last_event_after: q.last_activity_after,
224
227
  last_event_before: q.last_activity_before,
228
+ auto_destroy_at_after: q.auto_destroy_at_after,
229
+ auto_destroy_at_before: q.auto_destroy_at_before,
225
230
  sort: q.sort,
226
231
  order: q.order
227
232
  }.compact
228
233
 
229
234
  sandbox_api.list_sandboxes(opts)
235
+ rescue *Sdk::API_ERROR_CLASSES => e
236
+ raise Sdk.wrap_error(e, 'Failed to list sandboxes')
230
237
  end
231
238
 
232
239
  instrument :fetch_sandbox_page, component: 'Daytona.list'
@@ -334,6 +341,8 @@ module Daytona
334
341
  end
335
342
 
336
343
  sandbox
344
+ rescue *Sdk::API_ERROR_CLASSES => e
345
+ raise Sdk.wrap_error(e, 'Failed to create sandbox')
337
346
  end
338
347
 
339
348
  # @return [void]