Class: Daytona::CodeInterpreter
- Inherits:
-
Object
- Object
- Daytona::CodeInterpreter
- Includes:
- Instrumentation
- Defined in:
- lib/daytona/code_interpreter.rb
Overview
Handles code interpretation and execution within a Sandbox. Currently supports only Python.
This class provides methods to execute code in isolated interpreter contexts, manage contexts, and stream execution output via callbacks. If subsequent code executions are performed in the same context, the variables, imports, and functions defined in the previous execution will be available.
For other languages, use the ‘code_run` method from the `Process` interface, or execute the appropriate command directly in the sandbox terminal.
Constant Summary collapse
- WEBSOCKET_TIMEOUT_CODE =
4008
Instance Method Summary collapse
-
#create_context(cwd: nil) ⇒ DaytonaToolboxApiClient::InterpreterContext
Create a new isolated interpreter context.
-
#delete_context(context) ⇒ void
Delete an interpreter context and shut down all associated processes.
-
#initialize(sandbox_id:, toolbox_api:, get_preview_link:, otel_state: nil) ⇒ CodeInterpreter
constructor
A new instance of CodeInterpreter.
-
#list_contexts ⇒ Array<DaytonaToolboxApiClient::InterpreterContext>
List all user-created interpreter contexts.
-
#run_code(code, context: nil, on_stdout: nil, on_stderr: nil, on_error: nil, envs: nil, timeout: nil) ⇒ Daytona::ExecutionResult
Execute Python code in the sandbox.
Methods included from Instrumentation
Constructor Details
#initialize(sandbox_id:, toolbox_api:, get_preview_link:, otel_state: nil) ⇒ CodeInterpreter
Returns a new instance of CodeInterpreter.
28 29 30 31 32 33 |
# File 'lib/daytona/code_interpreter.rb', line 28 def initialize(sandbox_id:, toolbox_api:, get_preview_link:, otel_state: nil) @sandbox_id = sandbox_id @toolbox_api = toolbox_api @get_preview_link = get_preview_link @otel_state = otel_state end |
Instance Method Details
#create_context(cwd: nil) ⇒ DaytonaToolboxApiClient::InterpreterContext
Create a new isolated interpreter context.
Contexts provide isolated execution environments with their own global namespace. Variables, imports, and functions defined in one context don’t affect others.
248 249 250 251 252 253 |
# File 'lib/daytona/code_interpreter.rb', line 248 def create_context(cwd: nil) request = DaytonaToolboxApiClient::CreateContextRequest.new(cwd:) @toolbox_api.create_interpreter_context(request) rescue StandardError => e raise Sdk::Error, "Failed to create interpreter context: #{e.}" end |
#delete_context(context) ⇒ void
This method returns an undefined value.
Delete an interpreter context and shut down all associated processes.
This permanently removes the context and all its state (variables, imports, etc.). The default context cannot be deleted.
288 289 290 291 292 293 |
# File 'lib/daytona/code_interpreter.rb', line 288 def delete_context(context) @toolbox_api.delete_interpreter_context(context.id) nil rescue StandardError => e raise Sdk::Error, "Failed to delete interpreter context: #{e.}" end |
#list_contexts ⇒ Array<DaytonaToolboxApiClient::InterpreterContext>
List all user-created interpreter contexts.
The default context is not included in this list. Only contexts created via ‘create_context` are returned.
268 269 270 271 272 273 |
# File 'lib/daytona/code_interpreter.rb', line 268 def list_contexts response = @toolbox_api.list_interpreter_contexts response.contexts || [] rescue StandardError => e raise Sdk::Error, "Failed to list interpreter contexts: #{e.}" end |
#run_code(code, context: nil, on_stdout: nil, on_stderr: nil, on_error: nil, envs: nil, timeout: nil) ⇒ Daytona::ExecutionResult
Execute Python code in the sandbox.
By default, code runs in the default shared context which persists variables, imports, and functions across executions. To run in an isolated context, create a new context with ‘create_context` and pass it as the `context` argument.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/daytona/code_interpreter.rb', line 80 def run_code(code, context: nil, on_stdout: nil, on_stderr: nil, on_error: nil, envs: nil, timeout: nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists # Get WebSocket URL via preview link preview_link = @get_preview_link.call(WS_PORT) url = URI.parse(preview_link.url) url.scheme = url.scheme == 'https' ? 'wss' : 'ws' url.path = '/process/interpreter/execute' ws_url = url.to_s result = ExecutionResult.new # Create request payload request = { code: } request[:contextId] = context.id if context request[:envs] = envs if envs request[:timeout] = timeout if timeout # Build headers with preview token headers = @toolbox_api.api_client.default_headers.dup.merge( 'X-Daytona-Preview-Token' => preview_link.token, 'Content-Type' => 'application/json', 'Accept' => 'application/json' ) # Use queue for synchronization completion_queue = Queue.new interpreter = self # Capture self for use in blocks = Time.now = Mutex.new puts "[DEBUG] Connecting to WebSocket: #{ws_url}" if ENV['DEBUG'] # Connect to WebSocket and execute ws = WebSocket::Client::Simple.connect(ws_url, headers:) ws.on :open do puts '[DEBUG] WebSocket opened, sending request' if ENV['DEBUG'] ws.send(JSON.dump(request)) end ws.on :message do |msg| .synchronize { = Time.now } puts "[DEBUG] Received message (length=#{msg.data.length}): #{msg.data.inspect[0..200]}" if ENV['DEBUG'] interpreter.send(:handle_message, msg.data, result, on_stdout, on_stderr, on_error, completion_queue) end ws.on :error do |e| puts "[DEBUG] WebSocket error: #{e.}" if ENV['DEBUG'] completion_queue.push({ type: :error, error: e }) end ws.on :close do |e| if ENV['DEBUG'] code = e&.code || 'nil' reason = e&.reason || 'nil' puts "[DEBUG] WebSocket closed: code=#{code}, reason=#{reason}" end error_info = interpreter.send(:handle_close, e) if error_info completion_queue.push({ type: :error_from_close, error: error_info }) else completion_queue.push({ type: :close }) end end # Wait for completion signal with idle timeout # If timeout is specified, wait longer to detect actual timeout errors # Otherwise use short idle timeout for normal completion idle_timeout = timeout ? (timeout + 2.0) : 1.0 max_wait = (timeout || 300) + 3 # Add buffer to configured timeout start_time = Time.now completion_reason = nil # Wait for completion or close event loop do begin completion = completion_queue.pop(true) # non-blocking puts "[DEBUG] Got completion signal: #{completion[:type]}" if ENV['DEBUG'] # Control message (completed/interrupted) = normal completion if completion[:type] == :completed completion_reason = :completed break # If it's an error from close event (like timeout), raise it elsif completion[:type] == :error_from_close error_msg = completion[:error] # Raise TimeoutError for timeout cases, regular Error for others if error_msg.include?('timed out') || error_msg.include?('Execution timed out') raise Sdk::TimeoutError, error_msg end raise Sdk::Error, error_msg # Close event during execution (before control message) = likely timeout or error elsif completion[:type] == :close elapsed = Time.now - start_time # If we got close near the timeout, it's likely a timeout if timeout && elapsed >= timeout && elapsed < (timeout + 2) raise Sdk::TimeoutError, 'Execution timed out: operation exceeded the configured `timeout`. Provide a larger value if needed.' end # Otherwise normal close completion_reason = :close break # WebSocket errors elsif completion[:type] == :error && !completion[:error]..include?('stream closed') raise Sdk::Error, "WebSocket error: #{completion[:error].}" end rescue ThreadError # Queue is empty, check idle timeout end # Check idle timeout (no messages for N seconds = completion) = .synchronize { Time.now - } if > idle_timeout puts "[DEBUG] Idle timeout reached (#{idle_timeout}s), assuming completion" if ENV['DEBUG'] completion_reason = :idle_complete break end # Check for absolute timeout (safety net) if Time.now - start_time > max_wait ws.close raise Sdk::TimeoutError, 'Execution timed out: operation exceeded the configured `timeout`. Provide a larger value if needed.' end sleep 0.05 # Check every 50ms end # Close WebSocket if not already closed ws.close if completion_reason != :close sleep 0.05 result rescue Sdk::Error # Re-raise SDK errors as-is raise rescue StandardError => e # Wrap unexpected errors raise Sdk::Error, "Failed to run code: #{e.}" end |