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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +181 -0
- data/lib/e2b/api/http_client.rb +164 -0
- data/lib/e2b/client.rb +201 -0
- data/lib/e2b/configuration.rb +119 -0
- data/lib/e2b/errors.rb +88 -0
- data/lib/e2b/models/entry_info.rb +243 -0
- data/lib/e2b/models/process_result.rb +127 -0
- data/lib/e2b/models/sandbox_info.rb +94 -0
- data/lib/e2b/sandbox.rb +407 -0
- data/lib/e2b/services/base_service.rb +485 -0
- data/lib/e2b/services/command_handle.rb +350 -0
- data/lib/e2b/services/commands.rb +229 -0
- data/lib/e2b/services/filesystem.rb +373 -0
- data/lib/e2b/services/git.rb +893 -0
- data/lib/e2b/services/pty.rb +297 -0
- data/lib/e2b/services/watch_handle.rb +110 -0
- data/lib/e2b/version.rb +5 -0
- data/lib/e2b.rb +87 -0
- metadata +142 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "openssl"
|
|
6
|
+
require "ostruct"
|
|
7
|
+
|
|
8
|
+
module E2B
|
|
9
|
+
module Services
|
|
10
|
+
# Base class for sandbox services
|
|
11
|
+
#
|
|
12
|
+
# E2B sandboxes expose services through the envd daemon on port 49983.
|
|
13
|
+
# This base class handles communication with that daemon using the
|
|
14
|
+
# Connect RPC protocol (gRPC-over-HTTP with JSON encoding).
|
|
15
|
+
class BaseService
|
|
16
|
+
# Default envd port
|
|
17
|
+
ENVD_PORT = 49983
|
|
18
|
+
|
|
19
|
+
# @param sandbox_id [String] Sandbox ID
|
|
20
|
+
# @param sandbox_domain [String] Sandbox domain (e.g., "e2b.app")
|
|
21
|
+
# @param api_key [String] API key for authentication
|
|
22
|
+
# @param access_token [String, nil] Sandbox-specific access token
|
|
23
|
+
# @param logger [Logger, nil] Optional logger
|
|
24
|
+
def initialize(sandbox_id:, sandbox_domain:, api_key:, access_token: nil, logger: nil)
|
|
25
|
+
@sandbox_id = sandbox_id
|
|
26
|
+
@sandbox_domain = sandbox_domain
|
|
27
|
+
@api_key = api_key
|
|
28
|
+
@access_token = access_token
|
|
29
|
+
@logger = logger
|
|
30
|
+
@envd_client = nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
protected
|
|
34
|
+
|
|
35
|
+
# Get the envd HTTP client for this sandbox
|
|
36
|
+
#
|
|
37
|
+
# @return [EnvdHttpClient]
|
|
38
|
+
def envd_client
|
|
39
|
+
@envd_client ||= build_envd_client
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Perform GET request to envd
|
|
43
|
+
def envd_get(path, params: {}, timeout: 120)
|
|
44
|
+
envd_client.get(path, params: params, timeout: timeout)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Perform POST request to envd
|
|
48
|
+
def envd_post(path, body: nil, timeout: 120)
|
|
49
|
+
envd_client.post(path, body: body, timeout: timeout)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Perform DELETE request to envd
|
|
53
|
+
def envd_delete(path, timeout: 120)
|
|
54
|
+
envd_client.delete(path, timeout: timeout)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Perform Connect RPC call to envd
|
|
58
|
+
#
|
|
59
|
+
# @param service [String] Service name (e.g., "process.Process")
|
|
60
|
+
# @param method [String] Method name (e.g., "Start")
|
|
61
|
+
# @param body [Hash] Request body
|
|
62
|
+
# @param timeout [Integer] Request timeout in seconds
|
|
63
|
+
# @param on_event [Proc, nil] Callback for streaming events
|
|
64
|
+
# @return [Hash] Response with :events, :stdout, :stderr, :exit_code
|
|
65
|
+
def envd_rpc(service, method, body: {}, timeout: 120, on_event: nil)
|
|
66
|
+
envd_client.rpc(service, method, body: body, timeout: timeout, on_event: on_event)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def build_envd_client
|
|
72
|
+
envd_url = "https://#{ENVD_PORT}-#{@sandbox_id}.#{@sandbox_domain}"
|
|
73
|
+
|
|
74
|
+
EnvdHttpClient.new(
|
|
75
|
+
base_url: envd_url,
|
|
76
|
+
api_key: @api_key,
|
|
77
|
+
access_token: @access_token,
|
|
78
|
+
sandbox_id: @sandbox_id,
|
|
79
|
+
logger: @logger
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# HTTP client for envd daemon communication
|
|
85
|
+
#
|
|
86
|
+
# Handles both standard HTTP requests and Connect RPC protocol calls.
|
|
87
|
+
# Connect RPC uses a binary envelope format: 1 byte flags + 4 bytes
|
|
88
|
+
# big-endian length + JSON message body.
|
|
89
|
+
class EnvdHttpClient
|
|
90
|
+
DEFAULT_TIMEOUT = 120
|
|
91
|
+
|
|
92
|
+
def initialize(base_url:, api_key:, access_token: nil, sandbox_id:, logger: nil)
|
|
93
|
+
@base_url = base_url.end_with?("/") ? base_url : "#{base_url}/"
|
|
94
|
+
@api_key = api_key
|
|
95
|
+
@access_token = access_token
|
|
96
|
+
@sandbox_id = sandbox_id
|
|
97
|
+
@logger = logger
|
|
98
|
+
@connection = build_connection
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def get(path, params: {}, timeout: DEFAULT_TIMEOUT)
|
|
102
|
+
handle_response do
|
|
103
|
+
@connection.get(normalize_path(path)) do |req|
|
|
104
|
+
req.params = params
|
|
105
|
+
req.options.timeout = timeout
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def post(path, body: nil, timeout: DEFAULT_TIMEOUT)
|
|
111
|
+
handle_response do
|
|
112
|
+
@connection.post(normalize_path(path)) do |req|
|
|
113
|
+
req.body = body.to_json if body
|
|
114
|
+
req.options.timeout = timeout
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def delete(path, timeout: DEFAULT_TIMEOUT)
|
|
120
|
+
handle_response do
|
|
121
|
+
@connection.delete(normalize_path(path)) do |req|
|
|
122
|
+
req.options.timeout = timeout
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Connect RPC call with streaming support
|
|
128
|
+
#
|
|
129
|
+
# @param service [String] Service name
|
|
130
|
+
# @param method [String] Method name
|
|
131
|
+
# @param body [Hash] Request body
|
|
132
|
+
# @param timeout [Integer] Timeout in seconds
|
|
133
|
+
# @param on_event [Proc, nil] Callback for streaming events
|
|
134
|
+
# @return [Hash] Response
|
|
135
|
+
def rpc(service, method, body: {}, timeout: DEFAULT_TIMEOUT, on_event: nil)
|
|
136
|
+
path = "/#{service}/#{method}"
|
|
137
|
+
json_body = body.to_json
|
|
138
|
+
envelope = create_connect_envelope(json_body)
|
|
139
|
+
|
|
140
|
+
log_debug("RPC #{service}/#{method}")
|
|
141
|
+
|
|
142
|
+
if on_event
|
|
143
|
+
return handle_streaming_rpc(path, envelope, timeout, on_event)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
handle_rpc_response(service, method) do
|
|
147
|
+
with_retry("RPC #{service}/#{method}") do
|
|
148
|
+
url = URI.parse("#{@base_url.chomp('/')}#{path}")
|
|
149
|
+
http = build_http(url, timeout)
|
|
150
|
+
|
|
151
|
+
request = Net::HTTP::Post.new(url.request_uri)
|
|
152
|
+
request["Content-Type"] = "application/connect+json"
|
|
153
|
+
request["X-Access-Token"] = @access_token if @access_token
|
|
154
|
+
request["Connection"] = "keep-alive"
|
|
155
|
+
request.body = envelope
|
|
156
|
+
|
|
157
|
+
response = http.request(request)
|
|
158
|
+
|
|
159
|
+
OpenStruct.new(
|
|
160
|
+
status: response.code.to_i,
|
|
161
|
+
success?: response.code.to_i >= 200 && response.code.to_i < 300,
|
|
162
|
+
body: response.body,
|
|
163
|
+
headers: response.to_hash
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Streaming RPC with chunked response processing
|
|
170
|
+
def handle_streaming_rpc(path, envelope, timeout, on_event)
|
|
171
|
+
result = { events: [], stdout: "", stderr: "", exit_code: nil }
|
|
172
|
+
buffer = "".b
|
|
173
|
+
|
|
174
|
+
url = URI.parse("#{@base_url.chomp('/')}#{path}")
|
|
175
|
+
|
|
176
|
+
with_retry("Streaming RPC #{path}") do
|
|
177
|
+
http = build_http(url, timeout)
|
|
178
|
+
|
|
179
|
+
request = Net::HTTP::Post.new(url.request_uri)
|
|
180
|
+
request["Content-Type"] = "application/connect+json"
|
|
181
|
+
request["X-Access-Token"] = @access_token if @access_token
|
|
182
|
+
request["Connection"] = "keep-alive"
|
|
183
|
+
request.body = envelope
|
|
184
|
+
|
|
185
|
+
http.start do |conn|
|
|
186
|
+
conn.request(request) do |response|
|
|
187
|
+
unless response.code.to_i.between?(200, 299)
|
|
188
|
+
body = response.body
|
|
189
|
+
handle_error(OpenStruct.new(status: response.code.to_i, success?: false, body: body, headers: response.to_hash))
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
response.read_body do |chunk|
|
|
193
|
+
next if chunk.nil? || chunk.empty?
|
|
194
|
+
buffer << chunk
|
|
195
|
+
|
|
196
|
+
while buffer.bytesize >= 5
|
|
197
|
+
flags = buffer.getbyte(0)
|
|
198
|
+
length = buffer.byteslice(1, 4).unpack1("N")
|
|
199
|
+
|
|
200
|
+
break if length.nil? || buffer.bytesize < 5 + length
|
|
201
|
+
|
|
202
|
+
message_bytes = buffer.byteslice(5, length)
|
|
203
|
+
buffer = buffer.byteslice(5 + length..-1) || "".b
|
|
204
|
+
|
|
205
|
+
next if message_bytes.nil? || message_bytes.empty?
|
|
206
|
+
|
|
207
|
+
message_str = message_bytes.force_encoding("UTF-8")
|
|
208
|
+
|
|
209
|
+
begin
|
|
210
|
+
msg = JSON.parse(message_str)
|
|
211
|
+
msg = msg["result"] if msg["result"]
|
|
212
|
+
|
|
213
|
+
result[:events] << msg
|
|
214
|
+
|
|
215
|
+
stdout_data = nil
|
|
216
|
+
stderr_data = nil
|
|
217
|
+
|
|
218
|
+
if msg["event"]
|
|
219
|
+
event = msg["event"]
|
|
220
|
+
|
|
221
|
+
data_event = event["Data"] || event["data"]
|
|
222
|
+
if data_event
|
|
223
|
+
stdout_data = decode_base64(data_event["stdout"]) if data_event["stdout"]
|
|
224
|
+
stderr_data = decode_base64(data_event["stderr"]) if data_event["stderr"]
|
|
225
|
+
result[:stdout] += stdout_data if stdout_data
|
|
226
|
+
result[:stderr] += stderr_data if stderr_data
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
end_event = event["End"] || event["end"]
|
|
230
|
+
if end_event
|
|
231
|
+
result[:exit_code] = parse_exit_code(end_event["exitCode"] || end_event["exit_code"] || end_event["status"])
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
if msg["stdout"]
|
|
236
|
+
stdout_data = decode_base64(msg["stdout"])
|
|
237
|
+
result[:stdout] += stdout_data
|
|
238
|
+
end
|
|
239
|
+
if msg["stderr"]
|
|
240
|
+
stderr_data = decode_base64(msg["stderr"])
|
|
241
|
+
result[:stderr] += stderr_data
|
|
242
|
+
end
|
|
243
|
+
if msg["exitCode"] || msg["exit_code"]
|
|
244
|
+
result[:exit_code] = parse_exit_code(msg["exitCode"] || msg["exit_code"])
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
on_event.call(
|
|
248
|
+
stdout: stdout_data,
|
|
249
|
+
stderr: stderr_data,
|
|
250
|
+
exit_code: result[:exit_code],
|
|
251
|
+
event: msg
|
|
252
|
+
)
|
|
253
|
+
rescue JSON::ParserError
|
|
254
|
+
# Skip unparseable messages
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
result
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
private
|
|
266
|
+
|
|
267
|
+
def normalize_path(path)
|
|
268
|
+
path.to_s.sub(%r{^/+}, "")
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def build_connection
|
|
272
|
+
ssl_verify = ENV.fetch("E2B_SSL_VERIFY", "true").downcase != "false"
|
|
273
|
+
|
|
274
|
+
Faraday.new(url: @base_url, ssl: { verify: ssl_verify }) do |conn|
|
|
275
|
+
conn.request :json
|
|
276
|
+
conn.response :json, content_type: /\bjson$/
|
|
277
|
+
conn.adapter Faraday.default_adapter
|
|
278
|
+
|
|
279
|
+
conn.headers["E2b-Sandbox-Id"] = @sandbox_id
|
|
280
|
+
conn.headers["E2b-Sandbox-Port"] = "#{BaseService::ENVD_PORT}"
|
|
281
|
+
conn.headers["X-API-Key"] = @api_key
|
|
282
|
+
conn.headers["X-Access-Token"] = @access_token if @access_token
|
|
283
|
+
conn.headers["Content-Type"] = "application/json"
|
|
284
|
+
conn.headers["Accept"] = "application/json"
|
|
285
|
+
conn.headers["User-Agent"] = "e2b-ruby-sdk/#{E2B::VERSION}"
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def build_http(url, timeout)
|
|
290
|
+
http = Net::HTTP.new(url.host, url.port)
|
|
291
|
+
http.use_ssl = true
|
|
292
|
+
http.open_timeout = 30
|
|
293
|
+
http.read_timeout = timeout
|
|
294
|
+
http.keep_alive_timeout = 30
|
|
295
|
+
|
|
296
|
+
ssl_verify = ENV.fetch("E2B_SSL_VERIFY", "true").downcase != "false"
|
|
297
|
+
http.verify_mode = ssl_verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
|
298
|
+
|
|
299
|
+
http
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def with_retry(operation, max_retries: 3)
|
|
303
|
+
retry_count = 0
|
|
304
|
+
|
|
305
|
+
begin
|
|
306
|
+
yield
|
|
307
|
+
rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET, EOFError, Net::OpenTimeout, Net::ReadTimeout => e
|
|
308
|
+
retry_count += 1
|
|
309
|
+
|
|
310
|
+
if retry_count <= max_retries
|
|
311
|
+
sleep_time = 2**retry_count
|
|
312
|
+
log_debug("#{operation}: retry #{retry_count}/#{max_retries} after #{e.class}: #{e.message}")
|
|
313
|
+
sleep(sleep_time)
|
|
314
|
+
retry
|
|
315
|
+
else
|
|
316
|
+
raise E2B::E2BError, "#{operation} failed after #{max_retries} retries: #{e.message}"
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def handle_response
|
|
322
|
+
response = yield
|
|
323
|
+
handle_error(response) unless response.success?
|
|
324
|
+
|
|
325
|
+
body = response.body
|
|
326
|
+
|
|
327
|
+
if body.is_a?(String) && !body.empty?
|
|
328
|
+
content_type = response.headers["content-type"] rescue "unknown"
|
|
329
|
+
if content_type&.include?("json") || body.start_with?("{", "[")
|
|
330
|
+
begin
|
|
331
|
+
return JSON.parse(body)
|
|
332
|
+
rescue JSON::ParserError
|
|
333
|
+
# Return as-is
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
body
|
|
339
|
+
rescue Faraday::TimeoutError => e
|
|
340
|
+
raise E2B::TimeoutError, "Request timed out: #{e.message}"
|
|
341
|
+
rescue Faraday::ConnectionFailed => e
|
|
342
|
+
raise E2B::E2BError, "Connection to sandbox failed: #{e.message}"
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def handle_rpc_response(service, method)
|
|
346
|
+
response = yield
|
|
347
|
+
|
|
348
|
+
handle_error(response) unless response.success?
|
|
349
|
+
|
|
350
|
+
body = response.body
|
|
351
|
+
return {} if body.nil? || body.empty?
|
|
352
|
+
|
|
353
|
+
result = { events: [], stdout: "", stderr: "", exit_code: nil }
|
|
354
|
+
|
|
355
|
+
messages = parse_connect_stream(body)
|
|
356
|
+
|
|
357
|
+
messages.each do |msg_str|
|
|
358
|
+
begin
|
|
359
|
+
msg = JSON.parse(msg_str)
|
|
360
|
+
msg = msg["result"] if msg["result"]
|
|
361
|
+
|
|
362
|
+
result[:events] << msg
|
|
363
|
+
|
|
364
|
+
if msg["event"]
|
|
365
|
+
event = msg["event"]
|
|
366
|
+
|
|
367
|
+
data_event = event["Data"] || event["data"]
|
|
368
|
+
if data_event
|
|
369
|
+
result[:stdout] += decode_base64(data_event["stdout"]) if data_event["stdout"]
|
|
370
|
+
result[:stderr] += decode_base64(data_event["stderr"]) if data_event["stderr"]
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
end_event = event["End"] || event["end"]
|
|
374
|
+
if end_event
|
|
375
|
+
exit_value = end_event["exitCode"] || end_event["exit_code"] || end_event["status"]
|
|
376
|
+
result[:exit_code] = parse_exit_code(exit_value)
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
result[:stdout] += decode_base64(msg["stdout"]) if msg["stdout"]
|
|
381
|
+
result[:stderr] += decode_base64(msg["stderr"]) if msg["stderr"]
|
|
382
|
+
if msg["exitCode"] || msg["exit_code"]
|
|
383
|
+
result[:exit_code] = parse_exit_code(msg["exitCode"] || msg["exit_code"])
|
|
384
|
+
end
|
|
385
|
+
rescue JSON::ParserError
|
|
386
|
+
# Skip unparseable messages
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
result
|
|
391
|
+
rescue Faraday::TimeoutError => e
|
|
392
|
+
raise E2B::TimeoutError, "Request timed out: #{e.message}"
|
|
393
|
+
rescue Faraday::ConnectionFailed => e
|
|
394
|
+
raise E2B::E2BError, "Connection to sandbox failed: #{e.message}"
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def parse_connect_stream(body)
|
|
398
|
+
messages = []
|
|
399
|
+
|
|
400
|
+
# Try binary Connect envelope format first
|
|
401
|
+
if body.bytes.first(1) == [0] && body.bytesize >= 5
|
|
402
|
+
offset = 0
|
|
403
|
+
while offset + 5 <= body.bytesize
|
|
404
|
+
length = body.byteslice(offset + 1, 4).unpack1("N")
|
|
405
|
+
break if length.nil? || offset + 5 + length > body.bytesize
|
|
406
|
+
|
|
407
|
+
message = body.byteslice(offset + 5, length)
|
|
408
|
+
messages << message.force_encoding("UTF-8") if message && !message.empty?
|
|
409
|
+
offset += 5 + length
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
return messages if messages.any?
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# Fall back to NDJSON
|
|
416
|
+
body.each_line do |line|
|
|
417
|
+
line = line.strip
|
|
418
|
+
messages << line unless line.empty?
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
messages << body if messages.empty? && body.start_with?("{")
|
|
422
|
+
|
|
423
|
+
messages
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def create_connect_envelope(json_message)
|
|
427
|
+
flags = "\x00".b
|
|
428
|
+
length = [json_message.bytesize].pack("N")
|
|
429
|
+
flags + length + json_message
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def parse_exit_code(value)
|
|
433
|
+
return 0 if value.nil?
|
|
434
|
+
return value if value.is_a?(Integer)
|
|
435
|
+
|
|
436
|
+
str = value.to_s
|
|
437
|
+
if str =~ /exit status (\d+)/i
|
|
438
|
+
$1.to_i
|
|
439
|
+
elsif str =~ /^(\d+)$/
|
|
440
|
+
$1.to_i
|
|
441
|
+
else
|
|
442
|
+
str.include?("0") ? 0 : 1
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def decode_base64(data)
|
|
447
|
+
return "" if data.nil? || data.empty?
|
|
448
|
+
|
|
449
|
+
Base64.decode64(data)
|
|
450
|
+
rescue StandardError
|
|
451
|
+
data.to_s
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def handle_error(response)
|
|
455
|
+
message = extract_error_message(response)
|
|
456
|
+
status = response.status
|
|
457
|
+
headers = response.headers.to_h
|
|
458
|
+
|
|
459
|
+
case status
|
|
460
|
+
when 401, 403
|
|
461
|
+
raise E2B::AuthenticationError.new(message, status_code: status, headers: headers)
|
|
462
|
+
when 404
|
|
463
|
+
raise E2B::NotFoundError.new(message, status_code: status, headers: headers)
|
|
464
|
+
when 429
|
|
465
|
+
raise E2B::RateLimitError.new(message, status_code: status, headers: headers)
|
|
466
|
+
else
|
|
467
|
+
raise E2B::E2BError.new(message, status_code: status, headers: headers)
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def extract_error_message(response)
|
|
472
|
+
body = response.body
|
|
473
|
+
return body["message"] if body.is_a?(Hash) && body["message"]
|
|
474
|
+
return body["error"] if body.is_a?(Hash) && body["error"]
|
|
475
|
+
return body.to_s if body.is_a?(String) && !body.empty?
|
|
476
|
+
|
|
477
|
+
"HTTP #{response.status} error"
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def log_debug(message)
|
|
481
|
+
@logger&.debug("[E2B] #{message}")
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
end
|