daytona 0.126.0.pre.alpha.5 → 0.134.0.alpha.1
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/.rubocop.yml +1 -1
- data/README.md +2 -1
- data/lib/daytona/code_interpreter.rb +365 -0
- data/lib/daytona/code_toolbox/sandbox_js_code_toolbox.rb +19 -0
- data/lib/daytona/common/code_interpreter.rb +53 -0
- data/lib/daytona/common/snapshot.rb +7 -1
- data/lib/daytona/computer_use.rb +3 -3
- data/lib/daytona/config.rb +46 -10
- data/lib/daytona/daytona.rb +30 -9
- data/lib/daytona/git.rb +10 -10
- data/lib/daytona/lsp_server.rb +7 -7
- data/lib/daytona/object_storage.rb +2 -2
- data/lib/daytona/process.rb +26 -12
- data/lib/daytona/sandbox.rb +115 -1
- data/lib/daytona/sdk/version.rb +1 -1
- data/lib/daytona/sdk.rb +4 -2
- data/lib/daytona/snapshot_service.rb +33 -10
- data/lib/daytona.rb +0 -1
- data/project.json +13 -2
- data/scripts/generate-docs.rb +395 -0
- metadata +9 -5
data/lib/daytona/git.rb
CHANGED
|
@@ -37,7 +37,7 @@ module Daytona
|
|
|
37
37
|
# "README.md"
|
|
38
38
|
# ])
|
|
39
39
|
def add(path, files)
|
|
40
|
-
toolbox_api.
|
|
40
|
+
toolbox_api.add_files(DaytonaToolboxApiClient::GitAddRequest.new(path:, files:))
|
|
41
41
|
rescue StandardError => e
|
|
42
42
|
raise Sdk::Error, "Failed to add files: #{e.message}"
|
|
43
43
|
end
|
|
@@ -53,7 +53,7 @@ module Daytona
|
|
|
53
53
|
# response = sandbox.git.branches("workspace/repo")
|
|
54
54
|
# puts "Branches: #{response.branches}"
|
|
55
55
|
def branches(path)
|
|
56
|
-
toolbox_api.
|
|
56
|
+
toolbox_api.list_branches(path)
|
|
57
57
|
rescue StandardError => e
|
|
58
58
|
raise Sdk::Error, "Failed to list branches: #{e.message}"
|
|
59
59
|
end
|
|
@@ -97,7 +97,7 @@ module Daytona
|
|
|
97
97
|
# commit_id: "abc123"
|
|
98
98
|
# )
|
|
99
99
|
def clone(url:, path:, branch: nil, commit_id: nil, username: nil, password: nil) # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
|
|
100
|
-
toolbox_api.
|
|
100
|
+
toolbox_api.clone_repository(
|
|
101
101
|
DaytonaToolboxApiClient::GitCloneRequest.new(
|
|
102
102
|
url: url,
|
|
103
103
|
branch: branch,
|
|
@@ -135,7 +135,7 @@ module Daytona
|
|
|
135
135
|
# )
|
|
136
136
|
# puts "Commit SHA: #{commit_response.sha}"
|
|
137
137
|
def commit(path:, message:, author:, email:, allow_empty: false)
|
|
138
|
-
response = toolbox_api.
|
|
138
|
+
response = toolbox_api.commit_changes(
|
|
139
139
|
DaytonaToolboxApiClient::GitCommitRequest.new(path:, message:, author:, email:, allow_empty:)
|
|
140
140
|
)
|
|
141
141
|
GitCommitResponse.new(sha: response.hash)
|
|
@@ -165,7 +165,7 @@ module Daytona
|
|
|
165
165
|
# password: "github_token"
|
|
166
166
|
# )
|
|
167
167
|
def push(path:, username: nil, password: nil)
|
|
168
|
-
toolbox_api.
|
|
168
|
+
toolbox_api.push_changes(
|
|
169
169
|
DaytonaToolboxApiClient::GitRepoRequest.new(path:, username:, password:)
|
|
170
170
|
)
|
|
171
171
|
rescue StandardError => e
|
|
@@ -194,7 +194,7 @@ module Daytona
|
|
|
194
194
|
# )
|
|
195
195
|
#
|
|
196
196
|
def pull(path:, username: nil, password: nil)
|
|
197
|
-
toolbox_api.
|
|
197
|
+
toolbox_api.pull_changes(
|
|
198
198
|
DaytonaToolboxApiClient::GitRepoRequest.new(path:, username:, password:)
|
|
199
199
|
)
|
|
200
200
|
rescue StandardError => e
|
|
@@ -214,7 +214,7 @@ module Daytona
|
|
|
214
214
|
# puts "Commits ahead: #{status.ahead}"
|
|
215
215
|
# puts "Commits behind: #{status.behind}"
|
|
216
216
|
def status(path)
|
|
217
|
-
toolbox_api.
|
|
217
|
+
toolbox_api.get_status(path)
|
|
218
218
|
rescue StandardError => e
|
|
219
219
|
raise Sdk::Error, "Failed to get status: #{e.message}"
|
|
220
220
|
end
|
|
@@ -231,7 +231,7 @@ module Daytona
|
|
|
231
231
|
# # Checkout a branch
|
|
232
232
|
# sandbox.git.checkout_branch("workspace/repo", "feature-branch")
|
|
233
233
|
def checkout_branch(path, branch)
|
|
234
|
-
toolbox_api.
|
|
234
|
+
toolbox_api.checkout_branch(
|
|
235
235
|
DaytonaToolboxApiClient::GitCheckoutRequest.new(path:, branch:)
|
|
236
236
|
)
|
|
237
237
|
rescue StandardError => e
|
|
@@ -251,7 +251,7 @@ module Daytona
|
|
|
251
251
|
# sandbox.git.create_branch("workspace/repo", "new-feature")
|
|
252
252
|
#
|
|
253
253
|
def create_branch(path, name)
|
|
254
|
-
toolbox_api.
|
|
254
|
+
toolbox_api.create_branch(
|
|
255
255
|
DaytonaToolboxApiClient::GitBranchRequest.new(path:, name:)
|
|
256
256
|
)
|
|
257
257
|
rescue StandardError => e
|
|
@@ -270,7 +270,7 @@ module Daytona
|
|
|
270
270
|
# # Delete a branch
|
|
271
271
|
# sandbox.git.delete_branch("workspace/repo", "old-feature")
|
|
272
272
|
def delete_branch(path, name)
|
|
273
|
-
toolbox_api.
|
|
273
|
+
toolbox_api.delete_branch(
|
|
274
274
|
DaytonaToolboxApiClient::GitDeleteBranchRequest.new(path:, name:)
|
|
275
275
|
)
|
|
276
276
|
rescue StandardError => e
|
data/lib/daytona/lsp_server.rb
CHANGED
|
@@ -44,7 +44,7 @@ module Daytona
|
|
|
44
44
|
# @param position [Daytona::LspServer::Position]
|
|
45
45
|
# @return [DaytonaApiClient::CompletionList]
|
|
46
46
|
def completions(path:, position:)
|
|
47
|
-
toolbox_api.
|
|
47
|
+
toolbox_api.completions(
|
|
48
48
|
DaytonaToolboxApiClient::LspCompletionParams.new(
|
|
49
49
|
language_id:,
|
|
50
50
|
path_to_project:,
|
|
@@ -61,7 +61,7 @@ module Daytona
|
|
|
61
61
|
# @param path [String]
|
|
62
62
|
# @return [void]
|
|
63
63
|
def did_close(path)
|
|
64
|
-
toolbox_api.
|
|
64
|
+
toolbox_api.did_close(
|
|
65
65
|
DaytonaToolboxApiClient::LspDocumentRequest.new(language_id:, path_to_project:, uri: uri(path))
|
|
66
66
|
)
|
|
67
67
|
end
|
|
@@ -74,7 +74,7 @@ module Daytona
|
|
|
74
74
|
# @param path [String]
|
|
75
75
|
# @return [void]
|
|
76
76
|
def did_open(path)
|
|
77
|
-
toolbox_api.
|
|
77
|
+
toolbox_api.did_open(
|
|
78
78
|
DaytonaToolboxApiClient::LspDocumentRequest.new(language_id:, path_to_project:, uri: uri(path))
|
|
79
79
|
)
|
|
80
80
|
end
|
|
@@ -83,14 +83,14 @@ module Daytona
|
|
|
83
83
|
#
|
|
84
84
|
# @param path [String]
|
|
85
85
|
# @return [Array<DaytonaToolboxApiClient::LspSymbol]
|
|
86
|
-
def document_symbols(path) = toolbox_api.
|
|
86
|
+
def document_symbols(path) = toolbox_api.document_symbols(language_id, path_to_project, uri(path))
|
|
87
87
|
|
|
88
88
|
# Searches for symbols matching the query string across all files
|
|
89
89
|
# in the Sandbox.
|
|
90
90
|
#
|
|
91
91
|
# @param query [String]
|
|
92
92
|
# @return [Array<DaytonaToolboxApiClient::LspSymbol]
|
|
93
|
-
def sandbox_symbols(query) = toolbox_api.
|
|
93
|
+
def sandbox_symbols(query) = toolbox_api.workspace_symbols(query, language_id, path_to_project)
|
|
94
94
|
|
|
95
95
|
# Starts the language server.
|
|
96
96
|
# This method must be called before using any other LSP functionality.
|
|
@@ -98,7 +98,7 @@ module Daytona
|
|
|
98
98
|
#
|
|
99
99
|
# @return [void]
|
|
100
100
|
def start
|
|
101
|
-
toolbox_api.
|
|
101
|
+
toolbox_api.start(
|
|
102
102
|
DaytonaToolboxApiClient::LspServerRequest.new(language_id:, path_to_project:)
|
|
103
103
|
)
|
|
104
104
|
end
|
|
@@ -109,7 +109,7 @@ module Daytona
|
|
|
109
109
|
#
|
|
110
110
|
# @return [void]
|
|
111
111
|
def stop
|
|
112
|
-
toolbox_api.
|
|
112
|
+
toolbox_api.stop(
|
|
113
113
|
DaytonaToolboxApiClient::LspServerRequest.new(language_id:, path_to_project:)
|
|
114
114
|
)
|
|
115
115
|
end
|
|
@@ -110,8 +110,8 @@ module Daytona
|
|
|
110
110
|
.glob(File.join(abs_path_str, '**', '*'))
|
|
111
111
|
.select { |path| File.directory?(path) && Dir.empty?(path) }
|
|
112
112
|
.each do |empty_dir|
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
rel_dir = Pathname.new(empty_dir).relative_path_from(Pathname.new(abs_path_str)).to_s
|
|
114
|
+
md5_hasher.update(rel_dir)
|
|
115
115
|
end
|
|
116
116
|
end
|
|
117
117
|
|
data/lib/daytona/process.rb
CHANGED
|
@@ -222,26 +222,40 @@ module Daytona
|
|
|
222
222
|
url.path = "/process/session/#{session_id}/command/#{command_id}/logs"
|
|
223
223
|
url.query = 'follow=true'
|
|
224
224
|
|
|
225
|
-
|
|
225
|
+
completion_queue = Queue.new
|
|
226
|
+
|
|
227
|
+
ws = WebSocket::Client::Simple.connect(
|
|
226
228
|
url.to_s,
|
|
227
229
|
headers: toolbox_api.api_client.default_headers.dup.merge(
|
|
228
230
|
'X-Daytona-Preview-Token' => preview_link.token,
|
|
229
231
|
'Content-Type' => 'text/plain',
|
|
230
232
|
'Accept' => 'text/plain'
|
|
231
233
|
)
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
ws.on(:message) do |message|
|
|
237
|
+
if message.type == :close
|
|
238
|
+
ws.close
|
|
239
|
+
completion_queue.push(:close)
|
|
240
|
+
else
|
|
241
|
+
stdout, stderr = Util.demux(message.data.to_s)
|
|
242
|
+
|
|
243
|
+
on_stdout.call(stdout) unless stdout.empty?
|
|
244
|
+
on_stderr.call(stderr) unless stderr.empty?
|
|
243
245
|
end
|
|
244
246
|
end
|
|
247
|
+
|
|
248
|
+
ws.on(:close) do
|
|
249
|
+
completion_queue.push(:close)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
ws.on(:error) do |e|
|
|
253
|
+
completion_queue.push(:error)
|
|
254
|
+
raise Sdk::Error, "WebSocket error: #{e.message}"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Wait for completion
|
|
258
|
+
completion_queue.pop
|
|
245
259
|
end
|
|
246
260
|
|
|
247
261
|
#
|
data/lib/daytona/sandbox.rb
CHANGED
|
@@ -109,6 +109,9 @@ module Daytona
|
|
|
109
109
|
# @return [Daytona::ComputerUse]
|
|
110
110
|
attr_reader :computer_use
|
|
111
111
|
|
|
112
|
+
# @return [Daytona::CodeInterpreter]
|
|
113
|
+
attr_reader :code_interpreter
|
|
114
|
+
|
|
112
115
|
# @params code_toolbox [Daytona::SandboxPythonCodeToolbox, Daytona::SandboxTsCodeToolbox]
|
|
113
116
|
# @params config [Daytona::Config]
|
|
114
117
|
# @params sandbox_api [DaytonaApiClient::SandboxApi]
|
|
@@ -127,6 +130,9 @@ module Daytona
|
|
|
127
130
|
create_authenticated_client = lambda do
|
|
128
131
|
client = DaytonaToolboxApiClient::ApiClient.new(toolbox_api_config)
|
|
129
132
|
client.default_headers['Authorization'] = "Bearer #{config.api_key || config.jwt_token}"
|
|
133
|
+
client.default_headers['X-Daytona-Source'] = 'ruby-sdk'
|
|
134
|
+
client.default_headers['X-Daytona-SDK-Version'] = Sdk::VERSION
|
|
135
|
+
client.default_headers['X-Daytona-Organization-ID'] = config.organization_id if config.jwt_token
|
|
130
136
|
client
|
|
131
137
|
end
|
|
132
138
|
|
|
@@ -135,6 +141,8 @@ module Daytona
|
|
|
135
141
|
git_api = DaytonaToolboxApiClient::GitApi.new(create_authenticated_client.call)
|
|
136
142
|
lsp_api = DaytonaToolboxApiClient::LspApi.new(create_authenticated_client.call)
|
|
137
143
|
computer_use_api = DaytonaToolboxApiClient::ComputerUseApi.new(create_authenticated_client.call)
|
|
144
|
+
interpreter_api = DaytonaToolboxApiClient::InterpreterApi.new(create_authenticated_client.call)
|
|
145
|
+
info_api = DaytonaToolboxApiClient::InfoApi.new(create_authenticated_client.call)
|
|
138
146
|
|
|
139
147
|
@process = Process.new(
|
|
140
148
|
sandbox_id: id,
|
|
@@ -145,7 +153,13 @@ module Daytona
|
|
|
145
153
|
@fs = FileSystem.new(sandbox_id: id, toolbox_api: fs_api)
|
|
146
154
|
@git = Git.new(sandbox_id: id, toolbox_api: git_api)
|
|
147
155
|
@computer_use = ComputerUse.new(sandbox_id: id, toolbox_api: computer_use_api)
|
|
156
|
+
@code_interpreter = CodeInterpreter.new(
|
|
157
|
+
sandbox_id: id,
|
|
158
|
+
toolbox_api: interpreter_api,
|
|
159
|
+
get_preview_link: proc { |port| preview_url(port) }
|
|
160
|
+
)
|
|
148
161
|
@lsp_api = lsp_api
|
|
162
|
+
@info_api = info_api
|
|
149
163
|
end
|
|
150
164
|
|
|
151
165
|
# Archives the sandbox, making it inactive and preserving its state. When sandboxes are
|
|
@@ -211,6 +225,33 @@ module Daytona
|
|
|
211
225
|
refresh
|
|
212
226
|
end
|
|
213
227
|
|
|
228
|
+
# Gets the user's home directory path for the logged in user inside the Sandbox.
|
|
229
|
+
#
|
|
230
|
+
# @return [String] The absolute path to the Sandbox user's home directory for the logged in user
|
|
231
|
+
#
|
|
232
|
+
# @example
|
|
233
|
+
# user_home_dir = sandbox.get_user_home_dir
|
|
234
|
+
# puts "Sandbox user home: #{user_home_dir}"
|
|
235
|
+
def get_user_home_dir
|
|
236
|
+
@info_api.get_user_home_dir.dir
|
|
237
|
+
rescue StandardError => e
|
|
238
|
+
raise Sdk::Error, "Failed to get user home directory: #{e.message}"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Gets the working directory path inside the Sandbox.
|
|
242
|
+
#
|
|
243
|
+
# @return [String] The absolute path to the Sandbox working directory. Uses the WORKDIR specified
|
|
244
|
+
# in the Dockerfile if present, or falling back to the user's home directory if not.
|
|
245
|
+
#
|
|
246
|
+
# @example
|
|
247
|
+
# work_dir = sandbox.get_work_dir
|
|
248
|
+
# puts "Sandbox working directory: #{work_dir}"
|
|
249
|
+
def get_work_dir
|
|
250
|
+
@info_api.get_work_dir.dir
|
|
251
|
+
rescue StandardError => e
|
|
252
|
+
raise Sdk::Error, "Failed to get working directory path: #{e.message}"
|
|
253
|
+
end
|
|
254
|
+
|
|
214
255
|
# Sets labels for the Sandbox.
|
|
215
256
|
#
|
|
216
257
|
# @param labels [Hash<String, String>]
|
|
@@ -227,11 +268,54 @@ module Daytona
|
|
|
227
268
|
# @return [DaytonaApiClient::PortPreviewUrl]
|
|
228
269
|
def preview_url(port) = sandbox_api.get_port_preview_url(id, port)
|
|
229
270
|
|
|
271
|
+
# Creates a signed preview URL for the sandbox at the specified port.
|
|
272
|
+
#
|
|
273
|
+
# @param port [Integer] The port to open the preview link on
|
|
274
|
+
# @param expires_in_seconds [Integer, nil] The number of seconds the signed preview URL
|
|
275
|
+
# will be valid for. Defaults to 60 seconds.
|
|
276
|
+
# @return [DaytonaApiClient::SignedPortPreviewUrl] The signed preview URL response object
|
|
277
|
+
#
|
|
278
|
+
# @example
|
|
279
|
+
# signed_url = sandbox.create_signed_preview_url(3000, 120)
|
|
280
|
+
# puts "Signed URL: #{signed_url.url}"
|
|
281
|
+
# puts "Token: #{signed_url.token}"
|
|
282
|
+
def create_signed_preview_url(port, expires_in_seconds = nil)
|
|
283
|
+
sandbox_api.get_signed_port_preview_url(id, port, { expires_in_seconds: })
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Expires a signed preview URL for the sandbox at the specified port.
|
|
287
|
+
#
|
|
288
|
+
# @param port [Integer] The port to expire the signed preview URL on
|
|
289
|
+
# @param token [String] The token to expire
|
|
290
|
+
# @return [void]
|
|
291
|
+
#
|
|
292
|
+
# @example
|
|
293
|
+
# sandbox.expire_signed_preview_url(3000, "token-value")
|
|
294
|
+
def expire_signed_preview_url(port, token)
|
|
295
|
+
sandbox_api.expire_signed_port_preview_url(id, port, token)
|
|
296
|
+
end
|
|
297
|
+
|
|
230
298
|
# Refresh the Sandbox data from the API.
|
|
231
299
|
#
|
|
232
300
|
# @return [void]
|
|
233
301
|
def refresh = process_response(sandbox_api.get_sandbox(id))
|
|
234
302
|
|
|
303
|
+
# Refreshes the sandbox activity to reset the timer for automated lifecycle management actions.
|
|
304
|
+
#
|
|
305
|
+
# This method updates the sandbox's last activity timestamp without changing its state.
|
|
306
|
+
# It is useful for keeping long-running sessions alive while there is still user activity.
|
|
307
|
+
#
|
|
308
|
+
# @return [void]
|
|
309
|
+
#
|
|
310
|
+
# @example
|
|
311
|
+
# sandbox.refresh_activity
|
|
312
|
+
def refresh_activity
|
|
313
|
+
sandbox_api.update_last_activity(id)
|
|
314
|
+
nil
|
|
315
|
+
rescue StandardError => e
|
|
316
|
+
raise Sdk::Error, "Failed to refresh sandbox activity: #{e.message}"
|
|
317
|
+
end
|
|
318
|
+
|
|
235
319
|
# Revokes an SSH access token for the sandbox.
|
|
236
320
|
#
|
|
237
321
|
# @param token [String]
|
|
@@ -250,6 +334,25 @@ module Daytona
|
|
|
250
334
|
) { wait_for_states(operation: OPERATION_START, target_states: [DaytonaApiClient::SandboxState::STARTED]) }
|
|
251
335
|
end
|
|
252
336
|
|
|
337
|
+
# Recovers the Sandbox from a recoverable error and waits for it to be ready.
|
|
338
|
+
#
|
|
339
|
+
# @param timeout [Numeric] Maximum wait time in seconds (defaults to 60 s).
|
|
340
|
+
# @return [void]
|
|
341
|
+
#
|
|
342
|
+
# @example
|
|
343
|
+
# sandbox = daytona.get('my-sandbox-id')
|
|
344
|
+
# sandbox.recover(timeout: 40) # Wait up to 40 seconds
|
|
345
|
+
# puts 'Sandbox recovered successfully'
|
|
346
|
+
def recover(timeout = DEFAULT_TIMEOUT)
|
|
347
|
+
with_timeout(
|
|
348
|
+
timeout:,
|
|
349
|
+
message: "Sandbox #{id} failed to recover within the #{timeout} seconds timeout period",
|
|
350
|
+
setup: proc { process_response(sandbox_api.recover_sandbox(id)) }
|
|
351
|
+
) { wait_for_states(operation: OPERATION_START, target_states: [DaytonaApiClient::SandboxState::STARTED]) }
|
|
352
|
+
rescue StandardError => e
|
|
353
|
+
raise Sdk::Error, "Failed to recover sandbox: #{e.message}"
|
|
354
|
+
end
|
|
355
|
+
|
|
253
356
|
# Stops the Sandbox and waits for it to be stopped.
|
|
254
357
|
#
|
|
255
358
|
# @param timeout [Numeric] Maximum wait time in seconds (defaults to 60 s).
|
|
@@ -297,6 +400,17 @@ module Daytona
|
|
|
297
400
|
wait_for_states(operation: OPERATION_START, target_states: [DaytonaApiClient::SandboxState::STARTED])
|
|
298
401
|
end
|
|
299
402
|
|
|
403
|
+
# Waits for the Sandbox to reach the 'stopped' state. Polls the Sandbox status until it
|
|
404
|
+
# reaches the 'stopped' state or encounters an error.
|
|
405
|
+
# Treats destroyed as stopped to cover ephemeral sandboxes that are automatically deleted after stopping.
|
|
406
|
+
#
|
|
407
|
+
# @param timeout [Numeric] Maximum wait time in seconds (defaults to 60 s).
|
|
408
|
+
# @return [void]
|
|
409
|
+
def wait_for_sandbox_stop(_timeout = DEFAULT_TIMEOUT)
|
|
410
|
+
wait_for_states(operation: OPERATION_STOP, target_states: [DaytonaApiClient::SandboxState::STOPPED,
|
|
411
|
+
DaytonaApiClient::SandboxState::DESTROYED])
|
|
412
|
+
end
|
|
413
|
+
|
|
300
414
|
private
|
|
301
415
|
|
|
302
416
|
# Build toolbox API configuration with dynamic base URL from preview link
|
|
@@ -304,7 +418,7 @@ module Daytona
|
|
|
304
418
|
def build_toolbox_api_config
|
|
305
419
|
DaytonaToolboxApiClient::Configuration.new.configure do |cfg|
|
|
306
420
|
# Get the proxy toolbox URL and append sandbox ID
|
|
307
|
-
proxy_toolbox_url = @get_proxy_toolbox_url.call
|
|
421
|
+
proxy_toolbox_url = @get_proxy_toolbox_url.call(id, target)
|
|
308
422
|
proxy_toolbox_url += '/' unless proxy_toolbox_url.end_with?('/')
|
|
309
423
|
full_url = "#{proxy_toolbox_url}#{id}"
|
|
310
424
|
uri = URI(full_url)
|
data/lib/daytona/sdk/version.rb
CHANGED
data/lib/daytona/sdk.rb
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require 'logger'
|
|
4
4
|
|
|
5
|
-
require 'dotenv'
|
|
6
|
-
Dotenv.load('.env.local', '.env')
|
|
7
5
|
require 'daytona_api_client'
|
|
8
6
|
require 'daytona_toolbox_api_client'
|
|
9
7
|
require 'toml'
|
|
@@ -12,6 +10,7 @@ require 'websocket-client-simple'
|
|
|
12
10
|
require_relative 'sdk/version'
|
|
13
11
|
require_relative 'config'
|
|
14
12
|
require_relative 'common/charts'
|
|
13
|
+
require_relative 'common/code_interpreter'
|
|
15
14
|
require_relative 'common/code_language'
|
|
16
15
|
require_relative 'common/daytona'
|
|
17
16
|
require_relative 'common/file_system'
|
|
@@ -22,9 +21,11 @@ require_relative 'common/pty'
|
|
|
22
21
|
require_relative 'common/resources'
|
|
23
22
|
require_relative 'common/response'
|
|
24
23
|
require_relative 'common/snapshot'
|
|
24
|
+
require_relative 'code_interpreter'
|
|
25
25
|
require_relative 'computer_use'
|
|
26
26
|
require_relative 'code_toolbox/sandbox_python_code_toolbox'
|
|
27
27
|
require_relative 'code_toolbox/sandbox_ts_code_toolbox'
|
|
28
|
+
require_relative 'code_toolbox/sandbox_js_code_toolbox'
|
|
28
29
|
require_relative 'daytona'
|
|
29
30
|
require_relative 'file_system'
|
|
30
31
|
require_relative 'git'
|
|
@@ -40,6 +41,7 @@ require_relative 'process'
|
|
|
40
41
|
module Daytona
|
|
41
42
|
module Sdk
|
|
42
43
|
class Error < StandardError; end
|
|
44
|
+
class TimeoutError < Error; end
|
|
43
45
|
|
|
44
46
|
def self.logger = @logger ||= Logger.new($stdout, level: Logger::INFO)
|
|
45
47
|
end
|
|
@@ -8,9 +8,11 @@ module Daytona
|
|
|
8
8
|
|
|
9
9
|
# @param snapshots_api [DaytonaApiClient::SnapshotsApi] The snapshots API client
|
|
10
10
|
# @param object_storage_api [DaytonaApiClient::ObjectStorageApi] The object storage API client
|
|
11
|
-
|
|
11
|
+
# @param default_region_id [String, nil] Default region ID for snapshot creation
|
|
12
|
+
def initialize(snapshots_api:, object_storage_api:, default_region_id: nil)
|
|
12
13
|
@snapshots_api = snapshots_api
|
|
13
14
|
@object_storage_api = object_storage_api
|
|
15
|
+
@default_region_id = default_region_id
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
# List all Snapshots.
|
|
@@ -97,9 +99,12 @@ module Daytona
|
|
|
97
99
|
create_snapshot_req.disk = params.resources.disk
|
|
98
100
|
end
|
|
99
101
|
|
|
102
|
+
create_snapshot_req.region_id = params.region_id || @default_region_id
|
|
103
|
+
|
|
100
104
|
snapshot = snapshots_api.create_snapshot(create_snapshot_req)
|
|
101
105
|
|
|
102
|
-
snapshot
|
|
106
|
+
# Always wait for snapshot to be ready, regardless of on_logs
|
|
107
|
+
snapshot = wait_for_snapshot(snapshot, on_logs:)
|
|
103
108
|
|
|
104
109
|
if [DaytonaApiClient::SnapshotState::ERROR, DaytonaApiClient::SnapshotState::BUILD_FAILED].include?(snapshot.state)
|
|
105
110
|
raise Sdk::Error, "Failed to create snapshot #{snapshot.name}, reason: #{snapshot.error_reason}"
|
|
@@ -148,10 +153,16 @@ module Daytona
|
|
|
148
153
|
# @return [DaytonaApiClient::ObjectStorageApi, nil] The object storage API client
|
|
149
154
|
attr_reader :object_storage_api
|
|
150
155
|
|
|
156
|
+
# @return [String, nil] Default region ID for snapshot creation
|
|
157
|
+
attr_reader :default_region_id
|
|
158
|
+
|
|
159
|
+
# Wait for snapshot to reach a terminal state (ACTIVE, ERROR, or BUILD_FAILED)
|
|
160
|
+
# Optionally streams logs if on_logs callback is provided
|
|
161
|
+
#
|
|
151
162
|
# @param snapshot [DaytonaApiClient::SnapshotDto]
|
|
152
|
-
# @param on_logs [Proc]
|
|
163
|
+
# @param on_logs [Proc, nil]
|
|
153
164
|
# @return [DaytonaApiClient::SnapshotDto]
|
|
154
|
-
def
|
|
165
|
+
def wait_for_snapshot(snapshot, on_logs:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
155
166
|
terminal_states = [
|
|
156
167
|
DaytonaApiClient::SnapshotState::ACTIVE,
|
|
157
168
|
DaytonaApiClient::SnapshotState::ERROR,
|
|
@@ -160,13 +171,25 @@ module Daytona
|
|
|
160
171
|
|
|
161
172
|
thread = nil
|
|
162
173
|
previous_state = snapshot.state
|
|
174
|
+
|
|
175
|
+
# Log initial state if callback provided
|
|
176
|
+
on_logs&.call("Creating snapshot #{snapshot.name} (#{snapshot.state})")
|
|
177
|
+
|
|
163
178
|
until terminal_states.include?(snapshot.state)
|
|
164
179
|
Sdk.logger.debug("Waiting for snapshot to be created: #{snapshot.state}")
|
|
165
|
-
|
|
180
|
+
|
|
181
|
+
# Start log streaming thread if callback provided and snapshot is building
|
|
182
|
+
if on_logs && thread.nil? && snapshot.state != DaytonaApiClient::SnapshotState::PENDING
|
|
166
183
|
thread = start_log_streaming(snapshot, on_logs:)
|
|
167
184
|
end
|
|
168
185
|
|
|
169
|
-
|
|
186
|
+
# Log state changes if callback provided
|
|
187
|
+
if on_logs && previous_state != snapshot.state
|
|
188
|
+
if snapshot.state != DaytonaApiClient::SnapshotState::PENDING && thread.nil?
|
|
189
|
+
thread = start_log_streaming(snapshot, on_logs:)
|
|
190
|
+
end
|
|
191
|
+
on_logs.call("Creating snapshot #{snapshot.name} (#{snapshot.state})")
|
|
192
|
+
end
|
|
170
193
|
|
|
171
194
|
sleep(1)
|
|
172
195
|
previous_state = snapshot.state
|
|
@@ -175,7 +198,7 @@ module Daytona
|
|
|
175
198
|
|
|
176
199
|
thread&.join
|
|
177
200
|
|
|
178
|
-
if snapshot.state == DaytonaApiClient::SnapshotState::ACTIVE
|
|
201
|
+
if on_logs && snapshot.state == DaytonaApiClient::SnapshotState::ACTIVE
|
|
179
202
|
on_logs.call("Created snapshot #{snapshot.name} (#{snapshot.state})")
|
|
180
203
|
end
|
|
181
204
|
|
|
@@ -186,9 +209,9 @@ module Daytona
|
|
|
186
209
|
# @param on_logs [Proc]
|
|
187
210
|
# @return [Thread]
|
|
188
211
|
def start_log_streaming(snapshot, on_logs:)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
uri
|
|
212
|
+
# Get build logs URL from API
|
|
213
|
+
build_logs_response = snapshots_api.get_snapshot_build_logs_url(snapshot.id)
|
|
214
|
+
uri = URI.parse("#{build_logs_response.url}?follow=true")
|
|
192
215
|
|
|
193
216
|
headers = {}
|
|
194
217
|
snapshots_api.api_client.update_params_for_auth!(headers, nil, ['bearer'])
|
data/lib/daytona.rb
CHANGED
data/project.json
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"executor": "nx:run-commands",
|
|
25
25
|
"options": {
|
|
26
26
|
"cwd": "{projectRoot}",
|
|
27
|
-
"command": "if [ -n \"$RUBYGEMS_PKG_VERSION\" ]
|
|
27
|
+
"command": "if [ -n \"$RUBYGEMS_PKG_VERSION\" ]; then sed -i \"s/VERSION = '[^']*'/VERSION = '$RUBYGEMS_PKG_VERSION'/\" lib/daytona/sdk/version.rb && echo \"Changed version to $RUBYGEMS_PKG_VERSION\"; else echo \"Using version from version.rb\"; fi"
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
"lint": {
|
|
@@ -49,11 +49,22 @@
|
|
|
49
49
|
"parallel": false
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
"docs": {
|
|
53
|
+
"executor": "nx:run-commands",
|
|
54
|
+
"options": {
|
|
55
|
+
"cwd": "{projectRoot}",
|
|
56
|
+
"command": "bash -O extglob -c 'rm -rf ../../apps/docs/src/content/docs/en/ruby-sdk/!(index.mdx)' && bundle exec ruby scripts/generate-docs.rb"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
52
59
|
"publish": {
|
|
53
60
|
"executor": "nx:run-commands",
|
|
54
61
|
"options": {
|
|
55
62
|
"cwd": "{projectRoot}",
|
|
56
|
-
"
|
|
63
|
+
"commands": [
|
|
64
|
+
"mkdir -p ~/.gem && echo \":rubygems: $RUBYGEMS_API_KEY\" > ~/.gem/credentials && chmod 0600 ~/.gem/credentials",
|
|
65
|
+
"gem push daytona-*.gem --key rubygems --host https://rubygems.org"
|
|
66
|
+
],
|
|
67
|
+
"parallel": false
|
|
57
68
|
},
|
|
58
69
|
"dependsOn": [
|
|
59
70
|
{
|