e2b 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d76a27510ea8da9f113ba2efb9aecf7c06377d20a409153491f6a918ee506a72
4
+ data.tar.gz: c0b1dae2a55304bbddd9211a54dea1ba964be0dcf73557a479f936627ebd67c3
5
+ SHA512:
6
+ metadata.gz: 8777bb85409fab9f15c62c7d8d39856c7901616e90cc150914b78deaf4e71ba32540545dced53513cfee7641fa234815254ff529f5ebd41f533513696af436de
7
+ data.tar.gz: b1bd243b3741b8cba7860ffc36f0398ed5b049cb82482d65a1fea0f5da55071d3664602b3144ff7cbb06c4c90708e991ae565c9ba032ced51dac0f4e0bd6e2fb
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Voyager Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # E2B Ruby SDK
2
+
3
+ Ruby SDK for [E2B](https://e2b.dev) - secure cloud sandboxes for AI-generated code execution.
4
+
5
+ Aligned with the [official E2B SDKs](https://github.com/e2b-dev/E2B) (Python/JS).
6
+
7
+ ## Features
8
+
9
+ - Sandbox lifecycle management (create, connect, pause, resume, kill)
10
+ - Command execution with streaming output and background processes
11
+ - Filesystem operations via proper envd RPC (list, read, write, stat, watch)
12
+ - PTY (pseudo-terminal) support for interactive sessions
13
+ - Git operations (clone, push, pull, branches, status, commit, etc.)
14
+ - Directory watching with event polling
15
+ - Snapshot support
16
+ - Backward-compatible Client class
17
+
18
+ ## Installation
19
+
20
+ Add to your Gemfile:
21
+
22
+ ```ruby
23
+ gem 'e2b', git: 'https://github.com/ya-luotao/e2b-ruby.git'
24
+ ```
25
+
26
+ Then:
27
+
28
+ ```bash
29
+ bundle install
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ```ruby
35
+ require 'e2b'
36
+
37
+ # Create a sandbox (matches official SDK pattern)
38
+ sandbox = E2B::Sandbox.create(template: "base", api_key: ENV['E2B_API_KEY'])
39
+
40
+ # Run commands
41
+ result = sandbox.commands.run('echo "Hello from E2B!"')
42
+ puts result.stdout
43
+
44
+ # Work with files
45
+ sandbox.files.write('/home/user/hello.txt', 'Hello, World!')
46
+ content = sandbox.files.read('/home/user/hello.txt')
47
+
48
+ # List directory (returns EntryInfo objects)
49
+ entries = sandbox.files.list('/home/user')
50
+ entries.each { |e| puts "#{e.name} (#{e.type}, #{e.size} bytes)" }
51
+
52
+ # Background commands
53
+ handle = sandbox.commands.run("sleep 30", background: true)
54
+ handle.kill
55
+
56
+ # PTY (interactive terminal)
57
+ handle = sandbox.pty.create
58
+ handle.send_stdin("ls -la\n")
59
+ handle.kill
60
+
61
+ # Git operations
62
+ sandbox.git.clone("https://github.com/user/repo.git", path: "/home/user/repo")
63
+ status = sandbox.git.status("/home/user/repo")
64
+ puts "Branch: #{status.current_branch}, Clean: #{status.clean?}"
65
+
66
+ # Sandbox lifecycle
67
+ sandbox.set_timeout(600) # extend by 10 minutes
68
+ sandbox.pause
69
+ sandbox.resume(timeout: 300)
70
+
71
+ # Clean up
72
+ sandbox.kill
73
+ ```
74
+
75
+ ## Configuration
76
+
77
+ ```bash
78
+ # Option 1: Environment variable (recommended)
79
+ export E2B_API_KEY=your-api-key
80
+ ```
81
+
82
+ ```ruby
83
+ # Option 2: Global configuration
84
+ E2B.configure do |config|
85
+ config.api_key = 'your-api-key'
86
+ config.domain = 'e2b.app'
87
+ end
88
+
89
+ # Option 3: Per-call
90
+ sandbox = E2B::Sandbox.create(template: "base", api_key: "your-key")
91
+
92
+ # Option 4: Client class (backward compatible)
93
+ client = E2B::Client.new(api_key: "your-key")
94
+ sandbox = client.create(template: "base")
95
+ ```
96
+
97
+ ## API Reference
98
+
99
+ ### Sandbox (class methods)
100
+
101
+ | Method | Description |
102
+ |--------|-------------|
103
+ | `Sandbox.create(template:, timeout:, metadata:, envs:, api_key:)` | Create a new sandbox |
104
+ | `Sandbox.connect(sandbox_id, timeout:, api_key:)` | Connect to existing sandbox |
105
+ | `Sandbox.list(query:, limit:, api_key:)` | List running sandboxes |
106
+ | `Sandbox.kill(sandbox_id, api_key:)` | Kill a sandbox by ID |
107
+
108
+ ### Sandbox (instance)
109
+
110
+ | Method | Description |
111
+ |--------|-------------|
112
+ | `sandbox.commands` | Command execution service |
113
+ | `sandbox.files` | Filesystem service |
114
+ | `sandbox.pty` | PTY (terminal) service |
115
+ | `sandbox.git` | Git operations service |
116
+ | `sandbox.set_timeout(seconds)` | Extend sandbox lifetime |
117
+ | `sandbox.get_host(port)` | Get host string for a port |
118
+ | `sandbox.get_url(port)` | Get full URL for a port |
119
+ | `sandbox.pause` / `sandbox.resume` | Pause/resume sandbox |
120
+ | `sandbox.create_snapshot` | Create sandbox snapshot |
121
+ | `sandbox.kill` | Terminate sandbox |
122
+
123
+ ### Commands (`sandbox.commands`)
124
+
125
+ | Method | Description |
126
+ |--------|-------------|
127
+ | `run(cmd, background:, envs:, cwd:, timeout:, on_stdout:, on_stderr:)` | Run command (returns `CommandResult` or `CommandHandle`) |
128
+ | `list` | List running processes |
129
+ | `kill(pid)` | Kill a process |
130
+ | `send_stdin(pid, data)` | Send stdin to a process |
131
+ | `connect(pid)` | Connect to running process |
132
+
133
+ ### Filesystem (`sandbox.files`)
134
+
135
+ | Method | Description |
136
+ |--------|-------------|
137
+ | `read(path)` | Read file content |
138
+ | `write(path, data)` | Write file (via REST upload) |
139
+ | `write_files(files)` | Write multiple files |
140
+ | `list(path, depth:)` | List directory (returns `EntryInfo[]`) |
141
+ | `get_info(path)` | Get file/dir info (returns `EntryInfo`) |
142
+ | `exists?(path)` | Check if path exists |
143
+ | `make_dir(path)` | Create directory |
144
+ | `remove(path)` | Remove file/directory |
145
+ | `rename(old_path, new_path)` | Rename/move |
146
+ | `watch_dir(path, recursive:)` | Watch directory (returns `WatchHandle`) |
147
+
148
+ ### PTY (`sandbox.pty`)
149
+
150
+ | Method | Description |
151
+ |--------|-------------|
152
+ | `create(size:, cwd:, envs:)` | Create PTY session (returns `CommandHandle`) |
153
+ | `connect(pid)` | Connect to existing PTY |
154
+ | `send_stdin(pid, data)` | Send input to PTY |
155
+ | `kill(pid)` | Kill PTY process |
156
+ | `resize(pid, size)` | Resize terminal |
157
+ | `close_stdin(pid)` | Close PTY stdin (send EOF) |
158
+ | `list` | List running processes |
159
+
160
+ ### Git (`sandbox.git`)
161
+
162
+ | Method | Description |
163
+ |--------|-------------|
164
+ | `clone(url, path:, branch:, depth:, username:, password:)` | Clone repository |
165
+ | `init(path, bare:, initial_branch:)` | Initialize repository |
166
+ | `status(path)` | Get repo status (returns `GitStatus`) |
167
+ | `branches(path)` | List branches (returns `GitBranches`) |
168
+ | `add(path, files:, all:)` | Stage files |
169
+ | `commit(path, message, author_name:, author_email:)` | Create commit |
170
+ | `push(path, remote:, branch:, username:, password:)` | Push to remote |
171
+ | `pull(path, remote:, branch:, username:, password:)` | Pull from remote |
172
+ | `create_branch` / `checkout_branch` / `delete_branch` | Branch management |
173
+ | `remote_add(path, name, url)` / `remote_get(path, name)` | Remote management |
174
+ | `reset(path, mode:, target:)` / `restore(path, paths)` | Reset/restore changes |
175
+ | `set_config` / `get_config` | Git configuration |
176
+ | `configure_user(name, email)` | Set user name/email |
177
+ | `dangerously_authenticate(username, password)` | Store credentials globally |
178
+
179
+ ## License
180
+
181
+ MIT
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "faraday/multipart"
5
+ require "json"
6
+
7
+ module E2B
8
+ module API
9
+ # HTTP client wrapper for E2B API communication
10
+ #
11
+ # Handles authentication, request/response processing, and error handling.
12
+ class HttpClient
13
+ # Default request timeout in seconds
14
+ DEFAULT_TIMEOUT = 120
15
+
16
+ # @return [String] Base URL for API requests
17
+ attr_reader :base_url
18
+
19
+ # Initialize a new HTTP client
20
+ #
21
+ # @param base_url [String] Base URL for API requests
22
+ # @param api_key [String] API key for authentication
23
+ # @param logger [Logger, nil] Optional logger
24
+ def initialize(base_url:, api_key:, logger: nil)
25
+ @base_url = base_url.end_with?("/") ? base_url : "#{base_url}/"
26
+ @api_key = api_key
27
+ @logger = logger
28
+ @connection = build_connection
29
+ end
30
+
31
+ # Perform a GET request
32
+ #
33
+ # @param path [String] API endpoint path
34
+ # @param params [Hash] Query parameters
35
+ # @param timeout [Integer] Request timeout in seconds
36
+ # @return [Hash, Array, String] Parsed response body
37
+ def get(path, params: {}, timeout: DEFAULT_TIMEOUT)
38
+ handle_response do
39
+ @connection.get(normalize_path(path)) do |req|
40
+ req.params = params
41
+ req.options.timeout = timeout
42
+ end
43
+ end
44
+ end
45
+
46
+ # Perform a POST request
47
+ #
48
+ # @param path [String] API endpoint path
49
+ # @param body [Hash, nil] Request body
50
+ # @param timeout [Integer] Request timeout in seconds
51
+ # @return [Hash, Array, String] Parsed response body
52
+ def post(path, body: nil, timeout: DEFAULT_TIMEOUT)
53
+ handle_response do
54
+ @connection.post(normalize_path(path)) do |req|
55
+ req.body = body.to_json if body
56
+ req.options.timeout = timeout
57
+ end
58
+ end
59
+ end
60
+
61
+ # Perform a PUT request
62
+ #
63
+ # @param path [String] API endpoint path
64
+ # @param body [Hash, nil] Request body
65
+ # @param timeout [Integer] Request timeout in seconds
66
+ # @return [Hash, Array, String] Parsed response body
67
+ def put(path, body: nil, timeout: DEFAULT_TIMEOUT)
68
+ handle_response do
69
+ @connection.put(normalize_path(path)) do |req|
70
+ req.body = body.to_json if body
71
+ req.options.timeout = timeout
72
+ end
73
+ end
74
+ end
75
+
76
+ # Perform a DELETE request
77
+ #
78
+ # @param path [String] API endpoint path
79
+ # @param timeout [Integer] Request timeout in seconds
80
+ # @return [Hash, Array, String, nil] Parsed response body
81
+ def delete(path, timeout: DEFAULT_TIMEOUT)
82
+ handle_response do
83
+ @connection.delete(normalize_path(path)) do |req|
84
+ req.options.timeout = timeout
85
+ end
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def normalize_path(path)
92
+ path.to_s.sub(%r{^/+}, "")
93
+ end
94
+
95
+ def build_connection
96
+ ssl_verify = ENV.fetch("E2B_SSL_VERIFY", "true").downcase != "false"
97
+
98
+ Faraday.new(url: @base_url, ssl: { verify: ssl_verify }) do |conn|
99
+ conn.request :json
100
+ conn.response :json, content_type: /\bjson$/
101
+ conn.adapter Faraday.default_adapter
102
+
103
+ conn.headers["X-API-Key"] = @api_key
104
+ conn.headers["Content-Type"] = "application/json"
105
+ conn.headers["Accept"] = "application/json"
106
+ conn.headers["User-Agent"] = "e2b-ruby-sdk/#{E2B::VERSION}"
107
+ end
108
+ end
109
+
110
+ def handle_response
111
+ response = yield
112
+ handle_error(response) unless response.success?
113
+
114
+ body = response.body
115
+
116
+ # If body is a string but should be JSON, try to parse it
117
+ if body.is_a?(String) && !body.empty?
118
+ content_type = response.headers["content-type"] rescue "unknown"
119
+ if content_type&.include?("json") || body.start_with?("{", "[")
120
+ begin
121
+ return JSON.parse(body)
122
+ rescue JSON::ParserError
123
+ # Return as-is if can't parse
124
+ end
125
+ end
126
+ end
127
+
128
+ body
129
+ rescue Faraday::TimeoutError => e
130
+ raise E2B::TimeoutError, "Request timed out: #{e.message}"
131
+ rescue Faraday::ConnectionFailed => e
132
+ raise E2B::E2BError, "Connection failed: #{e.message}"
133
+ end
134
+
135
+ def handle_error(response)
136
+ message = extract_error_message(response)
137
+ status = response.status
138
+ headers = response.headers.to_h
139
+
140
+ case status
141
+ when 401, 403
142
+ raise E2B::AuthenticationError.new(message, status_code: status, headers: headers)
143
+ when 404
144
+ raise E2B::NotFoundError.new(message, status_code: status, headers: headers)
145
+ when 409
146
+ raise E2B::ConflictError.new(message, status_code: status, headers: headers)
147
+ when 429
148
+ raise E2B::RateLimitError.new(message, status_code: status, headers: headers)
149
+ else
150
+ raise E2B::E2BError.new(message, status_code: status, headers: headers)
151
+ end
152
+ end
153
+
154
+ def extract_error_message(response)
155
+ body = response.body
156
+ return body["message"] if body.is_a?(Hash) && body["message"]
157
+ return body["error"] if body.is_a?(Hash) && body["error"]
158
+ return body.to_s if body.is_a?(String) && !body.empty?
159
+
160
+ "HTTP #{response.status} error"
161
+ end
162
+ end
163
+ end
164
+ end
data/lib/e2b/client.rb ADDED
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ module E2B
4
+ # Client for interacting with the E2B API
5
+ #
6
+ # This class provides a convenient wrapper around {Sandbox} class methods.
7
+ # For the most direct API (matching the official SDK pattern), use
8
+ # {Sandbox.create}, {Sandbox.connect}, etc. directly.
9
+ #
10
+ # @example Using Client
11
+ # client = E2B::Client.new(api_key: "your-api-key")
12
+ # sandbox = client.create(template: "base")
13
+ #
14
+ # @example Using Sandbox directly (recommended, matches official SDK)
15
+ # sandbox = E2B::Sandbox.create(template: "base", api_key: "your-key")
16
+ class Client
17
+ # @return [Configuration] Client configuration
18
+ attr_reader :config
19
+
20
+ # Initialize a new E2B client
21
+ #
22
+ # @param config_or_options [Configuration, Hash, nil] Configuration or options hash
23
+ # @option config_or_options [String] :api_key API key for authentication
24
+ # @option config_or_options [String] :api_url API URL
25
+ # @option config_or_options [Integer] :timeout_ms Request timeout in milliseconds
26
+ def initialize(config_or_options = nil)
27
+ @config = resolve_config(config_or_options)
28
+ @config.validate!
29
+
30
+ @http_client = API::HttpClient.new(
31
+ base_url: @config.api_url,
32
+ api_key: @config.api_key,
33
+ logger: @config.logger
34
+ )
35
+
36
+ @domain = Sandbox::DEFAULT_DOMAIN
37
+ end
38
+
39
+ # Create a new sandbox
40
+ #
41
+ # @param template [String] Template ID or alias
42
+ # @param timeout [Integer, nil] Sandbox timeout in seconds (or timeout_ms in milliseconds for backward compat)
43
+ # @param timeout_ms [Integer, nil] Sandbox timeout in milliseconds (deprecated, use timeout in seconds)
44
+ # @param metadata [Hash, nil] Custom metadata
45
+ # @param envs [Hash{String => String}, nil] Environment variables
46
+ # @return [Sandbox] The created sandbox instance
47
+ def create(template: "base", timeout: nil, timeout_ms: nil, metadata: nil, envs: nil, **_opts)
48
+ # Support both seconds and milliseconds for backward compat
49
+ timeout_seconds = if timeout
50
+ timeout
51
+ elsif timeout_ms
52
+ (timeout_ms / 1000).to_i
53
+ else
54
+ (@config.sandbox_timeout_ms / 1000).to_i
55
+ end
56
+
57
+ body = {
58
+ templateID: template,
59
+ timeout: timeout_seconds
60
+ }
61
+ body[:metadata] = metadata if metadata
62
+ body[:envVars] = envs if envs
63
+
64
+ response = @http_client.post("/sandboxes", body: body, timeout: 120)
65
+
66
+ Sandbox.new(
67
+ sandbox_data: response,
68
+ http_client: @http_client,
69
+ api_key: @config.api_key,
70
+ domain: @domain
71
+ )
72
+ end
73
+
74
+ # Connect to an existing sandbox
75
+ #
76
+ # @param sandbox_id [String] The sandbox ID
77
+ # @param timeout [Integer, nil] Timeout in seconds
78
+ # @return [Sandbox]
79
+ def connect(sandbox_id, timeout: nil)
80
+ if timeout
81
+ response = @http_client.post("/sandboxes/#{sandbox_id}/connect",
82
+ body: { timeout: timeout })
83
+ else
84
+ response = @http_client.get("/sandboxes/#{sandbox_id}")
85
+ end
86
+
87
+ Sandbox.new(
88
+ sandbox_data: response,
89
+ http_client: @http_client,
90
+ api_key: @config.api_key,
91
+ domain: @domain
92
+ )
93
+ end
94
+
95
+ # Get sandbox details
96
+ #
97
+ # @param sandbox_id [String] The sandbox ID
98
+ # @return [Sandbox]
99
+ def get(sandbox_id)
100
+ response = @http_client.get("/sandboxes/#{sandbox_id}")
101
+
102
+ Sandbox.new(
103
+ sandbox_data: response,
104
+ http_client: @http_client,
105
+ api_key: @config.api_key,
106
+ domain: @domain
107
+ )
108
+ end
109
+
110
+ # List sandboxes
111
+ #
112
+ # @param metadata [Hash, nil] Filter by metadata
113
+ # @param state [String, nil] Filter by state
114
+ # @param limit [Integer] Maximum results
115
+ # @return [Array<Sandbox>]
116
+ def list(metadata: nil, state: nil, limit: 100)
117
+ params = { limit: limit }
118
+ params[:metadata] = metadata.to_json if metadata
119
+ params[:state] = state if state
120
+
121
+ response = @http_client.get("/v2/sandboxes", params: params)
122
+
123
+ sandboxes = if response.is_a?(Array)
124
+ response
125
+ elsif response.is_a?(Hash)
126
+ response["sandboxes"] || response[:sandboxes] || []
127
+ else
128
+ []
129
+ end
130
+ Array(sandboxes).map do |sandbox_data|
131
+ Sandbox.new(
132
+ sandbox_data: sandbox_data,
133
+ http_client: @http_client,
134
+ api_key: @config.api_key,
135
+ domain: @domain
136
+ )
137
+ end
138
+ end
139
+
140
+ # Kill a sandbox
141
+ #
142
+ # @param sandbox_id [String] The sandbox ID
143
+ # @return [Boolean]
144
+ def kill(sandbox_id)
145
+ @http_client.delete("/sandboxes/#{sandbox_id}")
146
+ true
147
+ rescue NotFoundError
148
+ true
149
+ end
150
+
151
+ # Set sandbox timeout
152
+ #
153
+ # @param sandbox_id [String] The sandbox ID
154
+ # @param timeout [Integer] Timeout in seconds
155
+ def set_timeout(sandbox_id, timeout)
156
+ @http_client.post("/sandboxes/#{sandbox_id}/timeout",
157
+ body: { timeout: timeout })
158
+ end
159
+
160
+ # Pause a sandbox
161
+ #
162
+ # @param sandbox_id [String] The sandbox ID
163
+ def pause(sandbox_id)
164
+ @http_client.post("/sandboxes/#{sandbox_id}/pause")
165
+ end
166
+
167
+ # Resume a sandbox
168
+ #
169
+ # @param sandbox_id [String] The sandbox ID
170
+ # @param timeout [Integer, nil] New timeout in seconds
171
+ # @return [Sandbox]
172
+ def resume(sandbox_id, timeout: nil)
173
+ body = {}
174
+ body[:timeout] = timeout if timeout
175
+
176
+ response = @http_client.post("/sandboxes/#{sandbox_id}/connect", body: body)
177
+
178
+ Sandbox.new(
179
+ sandbox_data: response,
180
+ http_client: @http_client,
181
+ api_key: @config.api_key,
182
+ domain: @domain
183
+ )
184
+ end
185
+
186
+ private
187
+
188
+ def resolve_config(config_or_options)
189
+ case config_or_options
190
+ when Configuration
191
+ config_or_options
192
+ when Hash
193
+ Configuration.new(**config_or_options)
194
+ when nil
195
+ E2B.configuration || Configuration.new
196
+ else
197
+ raise ArgumentError, "Expected Configuration, Hash, or nil"
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module E2B
4
+ # Configuration for E2B SDK
5
+ #
6
+ # Supports environment variables:
7
+ # - E2B_API_KEY: API key for authentication
8
+ # - E2B_ACCESS_TOKEN: Access token (alternative auth)
9
+ # - E2B_DOMAIN: Custom domain (default: e2b.app)
10
+ # - E2B_API_URL: Custom API URL
11
+ # - E2B_DEBUG: Enable debug logging
12
+ #
13
+ # @example
14
+ # E2B.configure do |config|
15
+ # config.api_key = "your-api-key"
16
+ # config.request_timeout = 120
17
+ # end
18
+ class Configuration
19
+ # Default API base URL
20
+ DEFAULT_API_URL = "https://api.e2b.app"
21
+
22
+ # Default domain
23
+ DEFAULT_DOMAIN = "e2b.app"
24
+
25
+ # Default request timeout in seconds
26
+ DEFAULT_REQUEST_TIMEOUT = 60
27
+
28
+ # Default sandbox timeout in seconds
29
+ DEFAULT_SANDBOX_TIMEOUT = 300
30
+
31
+ # Default timeout in milliseconds (backward compat)
32
+ DEFAULT_TIMEOUT_MS = 300_000
33
+
34
+ # Default sandbox timeout in milliseconds (backward compat)
35
+ DEFAULT_SANDBOX_TIMEOUT_MS = 3_600_000
36
+
37
+ # Maximum sandbox timeout (24 hours for Pro)
38
+ MAX_SANDBOX_TIMEOUT_MS = 86_400_000
39
+
40
+ # @return [String, nil] API key for authentication
41
+ attr_accessor :api_key
42
+
43
+ # @return [String, nil] Access token (alternative auth method)
44
+ attr_accessor :access_token
45
+
46
+ # @return [String] E2B domain
47
+ attr_accessor :domain
48
+
49
+ # @return [String] API base URL
50
+ attr_accessor :api_url
51
+
52
+ # @return [Integer] Request timeout in seconds
53
+ attr_accessor :request_timeout
54
+
55
+ # @return [Integer] Request timeout in milliseconds (backward compat)
56
+ attr_accessor :timeout_ms
57
+
58
+ # @return [Integer] Default sandbox timeout in milliseconds (backward compat)
59
+ attr_accessor :sandbox_timeout_ms
60
+
61
+ # @return [Boolean] Enable debug logging
62
+ attr_accessor :debug
63
+
64
+ # @return [String, nil] Default template ID
65
+ attr_accessor :default_template
66
+
67
+ # @return [Logger, nil] Optional logger
68
+ attr_accessor :logger
69
+
70
+ # Initialize configuration
71
+ #
72
+ # @param api_key [String, nil] API key (defaults to E2B_API_KEY env var)
73
+ # @param access_token [String, nil] Access token
74
+ # @param domain [String] E2B domain
75
+ # @param api_url [String] API base URL
76
+ # @param request_timeout [Integer] Request timeout in seconds
77
+ # @param timeout_ms [Integer] Request timeout in milliseconds (backward compat)
78
+ # @param sandbox_timeout_ms [Integer] Default sandbox timeout in milliseconds
79
+ # @param debug [Boolean] Enable debug logging
80
+ def initialize(
81
+ api_key: nil,
82
+ access_token: nil,
83
+ domain: nil,
84
+ api_url: nil,
85
+ request_timeout: DEFAULT_REQUEST_TIMEOUT,
86
+ timeout_ms: DEFAULT_TIMEOUT_MS,
87
+ sandbox_timeout_ms: DEFAULT_SANDBOX_TIMEOUT_MS,
88
+ debug: false
89
+ )
90
+ @api_key = api_key || ENV["E2B_API_KEY"]
91
+ @access_token = access_token || ENV["E2B_ACCESS_TOKEN"]
92
+ @domain = domain || ENV["E2B_DOMAIN"] || DEFAULT_DOMAIN
93
+ @api_url = api_url || ENV["E2B_API_URL"] || DEFAULT_API_URL
94
+ @request_timeout = request_timeout
95
+ @timeout_ms = timeout_ms
96
+ @sandbox_timeout_ms = sandbox_timeout_ms
97
+ @debug = debug || ENV["E2B_DEBUG"]&.downcase == "true"
98
+ @default_template = nil
99
+ @logger = nil
100
+ end
101
+
102
+ # Validate configuration
103
+ #
104
+ # @raise [ConfigurationError] If API key is missing
105
+ def validate!
106
+ if (@api_key.nil? || @api_key.empty?) && (@access_token.nil? || @access_token.empty?)
107
+ raise ConfigurationError,
108
+ "E2B API key is required. Set E2B_API_KEY environment variable or pass api_key option."
109
+ end
110
+ end
111
+
112
+ # Check if configuration is valid
113
+ #
114
+ # @return [Boolean]
115
+ def valid?
116
+ (!@api_key.nil? && !@api_key.empty?) || (!@access_token.nil? && !@access_token.empty?)
117
+ end
118
+ end
119
+ end