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.
31 32 33 34 35 36 |
# File 'lib/daytona/code_interpreter.rb', line 31 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.
232 233 234 235 236 237 |
# File 'lib/daytona/code_interpreter.rb', line 232 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.
272 273 274 275 276 277 |
# File 'lib/daytona/code_interpreter.rb', line 272 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.
252 253 254 255 256 257 |
# File 'lib/daytona/code_interpreter.rb', line 252 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.
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 |
# File 'lib/daytona/code_interpreter.rb', line 83 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' ) completion_queue = Queue.new interpreter = self puts "[DEBUG] Connecting to WebSocket: #{ws_url}" if ENV['DEBUG'] 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| 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 no_timeout = timeout.is_a?(Numeric) && timeout <= 0 max_wait = no_timeout ? nil : (timeout || 600) + 3 start_time = Time.now completion_reason = nil loop do if max_wait remaining = max_wait - (Time.now - start_time) if remaining <= 0 ws.close raise Sdk::TimeoutError, 'Execution timed out: operation exceeded the configured `timeout`. Provide a larger value if needed.' end end completion = completion_queue.pop(timeout: max_wait ? remaining : nil) if completion.nil? ws.close raise Sdk::TimeoutError, 'Execution timed out: operation exceeded the configured `timeout`. Provide a larger value if needed.' end puts "[DEBUG] Got completion signal: #{completion[:type]}" if ENV['DEBUG'] if completion[:type] == :completed completion_reason = :completed break elsif completion[:type] == :error_from_close error_msg = completion[:error] if error_msg.include?('timed out') || error_msg.include?('Execution timed out') raise Sdk::TimeoutError, error_msg end raise Sdk::Error, error_msg elsif completion[:type] == :close elapsed = Time.now - start_time if timeout && timeout > 0 && elapsed >= timeout && elapsed < (timeout + 2) raise Sdk::TimeoutError, 'Execution timed out: operation exceeded the configured `timeout`. Provide a larger value if needed.' end completion_reason = :close break elsif completion[:type] == :error unless completion[:error]..include?('stream closed') raise Sdk::Error, "WebSocket error: #{completion[:error].}" end completion_reason = :close break end end 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 |