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
data/lib/e2b/errors.rb
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module E2B
|
|
4
|
+
# Base error class for all E2B SDK errors
|
|
5
|
+
#
|
|
6
|
+
# @attr_reader [Integer, nil] status_code HTTP status code if available
|
|
7
|
+
# @attr_reader [Hash] headers Response headers if available
|
|
8
|
+
class E2BError < StandardError
|
|
9
|
+
attr_reader :status_code, :headers
|
|
10
|
+
|
|
11
|
+
# @param message [String] Error message
|
|
12
|
+
# @param status_code [Integer, nil] HTTP status code if available
|
|
13
|
+
# @param headers [Hash] Response headers if available
|
|
14
|
+
def initialize(message = nil, status_code: nil, headers: {})
|
|
15
|
+
@status_code = status_code
|
|
16
|
+
@headers = headers || {}
|
|
17
|
+
super(message)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Alias matching official SDK naming
|
|
22
|
+
SandboxError = E2BError
|
|
23
|
+
|
|
24
|
+
# Error raised when a requested resource is not found (HTTP 404)
|
|
25
|
+
class NotFoundError < E2BError; end
|
|
26
|
+
|
|
27
|
+
# Error raised when rate limit is exceeded (HTTP 429)
|
|
28
|
+
class RateLimitError < E2BError; end
|
|
29
|
+
|
|
30
|
+
# Error raised when an operation times out
|
|
31
|
+
class TimeoutError < E2BError; end
|
|
32
|
+
|
|
33
|
+
# Error raised when authentication fails (HTTP 401/403)
|
|
34
|
+
class AuthenticationError < E2BError; end
|
|
35
|
+
|
|
36
|
+
# Error raised when configuration is invalid
|
|
37
|
+
class ConfigurationError < E2BError; end
|
|
38
|
+
|
|
39
|
+
# Error raised when sandbox is not in expected state
|
|
40
|
+
class SandboxStateError < E2BError; end
|
|
41
|
+
|
|
42
|
+
# Error raised for conflict errors (HTTP 409)
|
|
43
|
+
class ConflictError < E2BError; end
|
|
44
|
+
|
|
45
|
+
# Error raised for invalid arguments
|
|
46
|
+
class InvalidArgumentError < E2BError; end
|
|
47
|
+
|
|
48
|
+
# Error raised when there is not enough disk space
|
|
49
|
+
class NotEnoughSpaceError < E2BError; end
|
|
50
|
+
|
|
51
|
+
# Error raised for template-related failures
|
|
52
|
+
class TemplateError < E2BError; end
|
|
53
|
+
|
|
54
|
+
# Error raised when a command exits with non-zero exit code
|
|
55
|
+
#
|
|
56
|
+
# @attr_reader [String] stdout Command stdout output
|
|
57
|
+
# @attr_reader [String] stderr Command stderr output
|
|
58
|
+
# @attr_reader [Integer] exit_code Process exit code
|
|
59
|
+
# @attr_reader [String, nil] command_error Error message from the process
|
|
60
|
+
class CommandExitError < E2BError
|
|
61
|
+
attr_reader :stdout, :stderr, :exit_code, :command_error
|
|
62
|
+
|
|
63
|
+
# @param stdout [String] Command stdout
|
|
64
|
+
# @param stderr [String] Command stderr
|
|
65
|
+
# @param exit_code [Integer] Process exit code
|
|
66
|
+
# @param error [String, nil] Error message from the process
|
|
67
|
+
def initialize(stdout: "", stderr: "", exit_code: 1, error: nil)
|
|
68
|
+
@stdout = stdout
|
|
69
|
+
@stderr = stderr
|
|
70
|
+
@exit_code = exit_code
|
|
71
|
+
@command_error = error
|
|
72
|
+
message = "Command exited with code #{exit_code}"
|
|
73
|
+
message += ": #{error}" if error
|
|
74
|
+
message += "\nStderr: #{stderr}" if stderr && !stderr.empty?
|
|
75
|
+
super(message)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def success?
|
|
79
|
+
false
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Error raised when git authentication fails
|
|
84
|
+
class GitAuthError < AuthenticationError; end
|
|
85
|
+
|
|
86
|
+
# Error raised when git upstream is missing
|
|
87
|
+
class GitUpstreamError < E2BError; end
|
|
88
|
+
end
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module E2B
|
|
6
|
+
module Models
|
|
7
|
+
# File types in the sandbox filesystem
|
|
8
|
+
#
|
|
9
|
+
# Maps to the protobuf FileType enum used by the filesystem service.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# entry.type == E2B::Models::FileType::FILE
|
|
13
|
+
# entry.type == E2B::Models::FileType::DIRECTORY
|
|
14
|
+
module FileType
|
|
15
|
+
# Regular file (FILE_TYPE_FILE = 1)
|
|
16
|
+
FILE = "FILE"
|
|
17
|
+
|
|
18
|
+
# Directory (FILE_TYPE_DIRECTORY = 2)
|
|
19
|
+
DIRECTORY = "DIRECTORY"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Filesystem event types from directory watching
|
|
23
|
+
#
|
|
24
|
+
# Maps to the protobuf EventType enum used by the filesystem watcher service.
|
|
25
|
+
#
|
|
26
|
+
# @example
|
|
27
|
+
# event.type == E2B::Models::FilesystemEventType::CREATE
|
|
28
|
+
module FilesystemEventType
|
|
29
|
+
# A new file or directory was created (EVENT_TYPE_CREATE = 1)
|
|
30
|
+
CREATE = "CREATE"
|
|
31
|
+
|
|
32
|
+
# A file was written to (EVENT_TYPE_WRITE = 2)
|
|
33
|
+
WRITE = "WRITE"
|
|
34
|
+
|
|
35
|
+
# A file or directory was removed (EVENT_TYPE_REMOVE = 3)
|
|
36
|
+
REMOVE = "REMOVE"
|
|
37
|
+
|
|
38
|
+
# A file or directory was renamed (EVENT_TYPE_RENAME = 4)
|
|
39
|
+
RENAME = "RENAME"
|
|
40
|
+
|
|
41
|
+
# File permissions were changed (EVENT_TYPE_CHMOD = 5)
|
|
42
|
+
CHMOD = "CHMOD"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Represents a filesystem event from directory watching
|
|
46
|
+
#
|
|
47
|
+
# Filesystem events are emitted when files or directories change within
|
|
48
|
+
# a watched directory. Events are retrieved by polling via {WatchHandle#get_new_events}.
|
|
49
|
+
#
|
|
50
|
+
# @example
|
|
51
|
+
# events = watch_handle.get_new_events
|
|
52
|
+
# events.each do |event|
|
|
53
|
+
# puts "#{event.name} was #{event.type}"
|
|
54
|
+
# end
|
|
55
|
+
class FilesystemEvent
|
|
56
|
+
# @return [String] Name of the file or directory that changed
|
|
57
|
+
attr_reader :name
|
|
58
|
+
|
|
59
|
+
# @return [String] Type of event (one of {FilesystemEventType} constants)
|
|
60
|
+
attr_reader :type
|
|
61
|
+
|
|
62
|
+
# @param name [String] Name of the file or directory that changed
|
|
63
|
+
# @param type [String] Type of event (one of {FilesystemEventType} constants)
|
|
64
|
+
def initialize(name:, type:)
|
|
65
|
+
@name = name
|
|
66
|
+
@type = type
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Create from RPC response hash
|
|
70
|
+
#
|
|
71
|
+
# Handles both numeric protobuf enum values and string enum names.
|
|
72
|
+
#
|
|
73
|
+
# @param data [Hash] Raw event data from the RPC response
|
|
74
|
+
# @return [FilesystemEvent]
|
|
75
|
+
def self.from_hash(data)
|
|
76
|
+
type_value = data["type"] || data[:type]
|
|
77
|
+
type_name = case type_value
|
|
78
|
+
when 1, "EVENT_TYPE_CREATE" then FilesystemEventType::CREATE
|
|
79
|
+
when 2, "EVENT_TYPE_WRITE" then FilesystemEventType::WRITE
|
|
80
|
+
when 3, "EVENT_TYPE_REMOVE" then FilesystemEventType::REMOVE
|
|
81
|
+
when 4, "EVENT_TYPE_RENAME" then FilesystemEventType::RENAME
|
|
82
|
+
when 5, "EVENT_TYPE_CHMOD" then FilesystemEventType::CHMOD
|
|
83
|
+
else type_value.to_s
|
|
84
|
+
end
|
|
85
|
+
new(name: data["name"] || data[:name], type: type_name)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Information about a filesystem entry (file or directory) in the sandbox
|
|
90
|
+
#
|
|
91
|
+
# Represents the protobuf EntryInfo message returned by filesystem RPCs
|
|
92
|
+
# such as Stat, ListDir, and others. Contains metadata including name, type,
|
|
93
|
+
# path, size, permissions, ownership, and modification time.
|
|
94
|
+
#
|
|
95
|
+
# @example
|
|
96
|
+
# entry = sandbox.files.stat("/home/user/hello.txt")
|
|
97
|
+
# puts entry.name # => "hello.txt"
|
|
98
|
+
# puts entry.size # => 13
|
|
99
|
+
# puts entry.file? # => true
|
|
100
|
+
# puts entry.directory? # => false
|
|
101
|
+
# puts entry.permissions # => "0644"
|
|
102
|
+
class EntryInfo
|
|
103
|
+
# @return [String] Name of the file or directory
|
|
104
|
+
attr_reader :name
|
|
105
|
+
|
|
106
|
+
# @return [String] Type of entry (one of {FileType} constants)
|
|
107
|
+
attr_reader :type
|
|
108
|
+
|
|
109
|
+
# @return [String] Full path to the entry in the sandbox filesystem
|
|
110
|
+
attr_reader :path
|
|
111
|
+
|
|
112
|
+
# @return [Integer] Size in bytes
|
|
113
|
+
attr_reader :size
|
|
114
|
+
|
|
115
|
+
# @return [Integer] Unix file mode (e.g., 0o644)
|
|
116
|
+
attr_reader :mode
|
|
117
|
+
|
|
118
|
+
# @return [String] Permissions string (e.g., "0644")
|
|
119
|
+
attr_reader :permissions
|
|
120
|
+
|
|
121
|
+
# @return [String] Owner username
|
|
122
|
+
attr_reader :owner
|
|
123
|
+
|
|
124
|
+
# @return [String] Group name
|
|
125
|
+
attr_reader :group
|
|
126
|
+
|
|
127
|
+
# @return [Time, nil] Last modification time
|
|
128
|
+
attr_reader :modified_time
|
|
129
|
+
|
|
130
|
+
# @return [String, nil] Symlink target path, if the entry is a symlink
|
|
131
|
+
attr_reader :symlink_target
|
|
132
|
+
|
|
133
|
+
# @param name [String] Name of the file or directory
|
|
134
|
+
# @param type [String] Type of entry (one of {FileType} constants)
|
|
135
|
+
# @param path [String] Full path in the sandbox filesystem
|
|
136
|
+
# @param size [Integer] Size in bytes
|
|
137
|
+
# @param mode [Integer] Unix file mode
|
|
138
|
+
# @param permissions [String] Permissions string
|
|
139
|
+
# @param owner [String] Owner username
|
|
140
|
+
# @param group [String] Group name
|
|
141
|
+
# @param modified_time [Time, nil] Last modification time
|
|
142
|
+
# @param symlink_target [String, nil] Symlink target path
|
|
143
|
+
def initialize(name:, type:, path:, size: 0, mode: 0, permissions: "",
|
|
144
|
+
owner: "", group: "", modified_time: nil, symlink_target: nil)
|
|
145
|
+
@name = name
|
|
146
|
+
@type = type
|
|
147
|
+
@path = path
|
|
148
|
+
@size = size
|
|
149
|
+
@mode = mode
|
|
150
|
+
@permissions = permissions
|
|
151
|
+
@owner = owner
|
|
152
|
+
@group = group
|
|
153
|
+
@modified_time = modified_time
|
|
154
|
+
@symlink_target = symlink_target
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Check if this entry is a regular file
|
|
158
|
+
#
|
|
159
|
+
# @return [Boolean]
|
|
160
|
+
def file?
|
|
161
|
+
@type == FileType::FILE
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Check if this entry is a directory
|
|
165
|
+
#
|
|
166
|
+
# @return [Boolean]
|
|
167
|
+
def directory?
|
|
168
|
+
@type == FileType::DIRECTORY
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Create from RPC response hash
|
|
172
|
+
#
|
|
173
|
+
# Handles both numeric protobuf enum values and string enum names,
|
|
174
|
+
# as well as camelCase and snake_case key formats.
|
|
175
|
+
#
|
|
176
|
+
# @param data [Hash] Raw entry data from the RPC response
|
|
177
|
+
# @return [EntryInfo, nil] The parsed entry, or nil if data is not a Hash
|
|
178
|
+
def self.from_hash(data)
|
|
179
|
+
return nil unless data.is_a?(Hash)
|
|
180
|
+
|
|
181
|
+
type_value = data["type"] || data[:type]
|
|
182
|
+
type_name = case type_value
|
|
183
|
+
when 1, "FILE_TYPE_FILE" then FileType::FILE
|
|
184
|
+
when 2, "FILE_TYPE_DIRECTORY" then FileType::DIRECTORY
|
|
185
|
+
else type_value.to_s
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
modified = data["modifiedTime"] || data["modified_time"] || data[:modifiedTime] || data[:modified_time]
|
|
189
|
+
modified_time = parse_modified_time(modified)
|
|
190
|
+
|
|
191
|
+
new(
|
|
192
|
+
name: data["name"] || data[:name] || "",
|
|
193
|
+
type: type_name,
|
|
194
|
+
path: data["path"] || data[:path] || "",
|
|
195
|
+
size: (data["size"] || data[:size] || 0).to_i,
|
|
196
|
+
mode: (data["mode"] || data[:mode] || 0).to_i,
|
|
197
|
+
permissions: data["permissions"] || data[:permissions] || "",
|
|
198
|
+
owner: data["owner"] || data[:owner] || "",
|
|
199
|
+
group: data["group"] || data[:group] || "",
|
|
200
|
+
modified_time: modified_time,
|
|
201
|
+
symlink_target: data["symlinkTarget"] || data["symlink_target"] || data[:symlinkTarget] || data[:symlink_target]
|
|
202
|
+
)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Parse a modified time value from various formats
|
|
206
|
+
#
|
|
207
|
+
# @param value [Hash, String, Time, nil] The raw modified time value
|
|
208
|
+
# @return [Time, nil] Parsed time, or nil if unparseable
|
|
209
|
+
def self.parse_modified_time(value)
|
|
210
|
+
if value.is_a?(Hash)
|
|
211
|
+
# Protobuf Timestamp format: { "seconds" => ..., "nanos" => ... }
|
|
212
|
+
seconds = value["seconds"] || value[:seconds] || 0
|
|
213
|
+
Time.at(seconds.to_i)
|
|
214
|
+
elsif value.is_a?(String)
|
|
215
|
+
Time.parse(value)
|
|
216
|
+
elsif value.is_a?(Time)
|
|
217
|
+
value
|
|
218
|
+
end
|
|
219
|
+
rescue ArgumentError
|
|
220
|
+
nil
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
private_class_method :parse_modified_time
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Information about a file write operation
|
|
227
|
+
#
|
|
228
|
+
# Returned after writing a file to confirm the path that was written.
|
|
229
|
+
#
|
|
230
|
+
# @example
|
|
231
|
+
# info = sandbox.files.write("/home/user/output.txt", "content")
|
|
232
|
+
# puts info.path # => "/home/user/output.txt"
|
|
233
|
+
class WriteInfo
|
|
234
|
+
# @return [String] Path of the written file
|
|
235
|
+
attr_reader :path
|
|
236
|
+
|
|
237
|
+
# @param path [String] Path of the written file
|
|
238
|
+
def initialize(path:)
|
|
239
|
+
@path = path
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module E2B
|
|
4
|
+
module Models
|
|
5
|
+
# Result of a process execution
|
|
6
|
+
class ProcessResult
|
|
7
|
+
# @return [String] Standard output
|
|
8
|
+
attr_reader :stdout
|
|
9
|
+
|
|
10
|
+
# @return [String] Standard error
|
|
11
|
+
attr_reader :stderr
|
|
12
|
+
|
|
13
|
+
# @return [Integer] Exit code
|
|
14
|
+
attr_reader :exit_code
|
|
15
|
+
|
|
16
|
+
# @return [String, nil] Error message if any
|
|
17
|
+
attr_reader :error
|
|
18
|
+
|
|
19
|
+
# Create from API response hash
|
|
20
|
+
#
|
|
21
|
+
# @param data [Hash] API response data
|
|
22
|
+
# @return [ProcessResult]
|
|
23
|
+
def self.from_hash(data)
|
|
24
|
+
new(
|
|
25
|
+
stdout: data["stdout"] || data[:stdout] || "",
|
|
26
|
+
stderr: data["stderr"] || data[:stderr] || "",
|
|
27
|
+
exit_code: data["exitCode"] || data["exit_code"] || data[:exitCode] || 0,
|
|
28
|
+
error: data["error"] || data[:error]
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Create from Connect RPC response
|
|
33
|
+
#
|
|
34
|
+
# Connect RPC responses for process.Process/Start contain:
|
|
35
|
+
# - events: Array of streaming events
|
|
36
|
+
# - stdout: Accumulated stdout
|
|
37
|
+
# - stderr: Accumulated stderr
|
|
38
|
+
# - exit_code: Process exit code
|
|
39
|
+
#
|
|
40
|
+
# @param data [Hash] Connect RPC response data
|
|
41
|
+
# @return [ProcessResult]
|
|
42
|
+
def self.from_connect_response(data)
|
|
43
|
+
return from_hash(data) unless data.is_a?(Hash)
|
|
44
|
+
|
|
45
|
+
stdout = data[:stdout] || data["stdout"] || ""
|
|
46
|
+
stderr = data[:stderr] || data["stderr"] || ""
|
|
47
|
+
exit_code = data[:exit_code] || data["exit_code"] || data["exitCode"] || 0
|
|
48
|
+
error = data[:error] || data["error"]
|
|
49
|
+
|
|
50
|
+
# If no stdout but we have events, try to extract from events
|
|
51
|
+
if stdout.empty? && data[:events].is_a?(Array)
|
|
52
|
+
data[:events].each do |event|
|
|
53
|
+
next unless event.is_a?(Hash)
|
|
54
|
+
|
|
55
|
+
# Handle nested event structure
|
|
56
|
+
if event["event"]
|
|
57
|
+
ev = event["event"]
|
|
58
|
+
if ev["Stdout"]
|
|
59
|
+
stdout += decode_base64_safe(ev["Stdout"]["data"])
|
|
60
|
+
elsif ev["stdout"]
|
|
61
|
+
stdout += decode_base64_safe(ev["stdout"]["data"])
|
|
62
|
+
elsif ev["Stderr"]
|
|
63
|
+
stderr += decode_base64_safe(ev["Stderr"]["data"])
|
|
64
|
+
elsif ev["stderr"]
|
|
65
|
+
stderr += decode_base64_safe(ev["stderr"]["data"])
|
|
66
|
+
elsif ev["Exit"]
|
|
67
|
+
exit_code = ev["Exit"]["exitCode"] || ev["Exit"]["exit_code"] || exit_code
|
|
68
|
+
elsif ev["exit"]
|
|
69
|
+
exit_code = ev["exit"]["exitCode"] || ev["exit"]["exit_code"] || exit_code
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
new(stdout: stdout, stderr: stderr, exit_code: parse_exit_code(exit_code), error: error)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.decode_base64_safe(data)
|
|
79
|
+
return "" if data.nil? || data.empty?
|
|
80
|
+
|
|
81
|
+
Base64.decode64(data)
|
|
82
|
+
rescue
|
|
83
|
+
data.to_s
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Parse exit code from various formats
|
|
87
|
+
# Handles: integer 0, string "0", string "exit status 0"
|
|
88
|
+
def self.parse_exit_code(value)
|
|
89
|
+
return 0 if value.nil?
|
|
90
|
+
return value if value.is_a?(Integer)
|
|
91
|
+
|
|
92
|
+
str = value.to_s
|
|
93
|
+
if str =~ /exit status (\d+)/i
|
|
94
|
+
$1.to_i
|
|
95
|
+
elsif str =~ /(\d+)/
|
|
96
|
+
$1.to_i
|
|
97
|
+
else
|
|
98
|
+
str.include?("0") ? 0 : 1
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def initialize(stdout: "", stderr: "", exit_code: 0, error: nil)
|
|
103
|
+
@stdout = stdout
|
|
104
|
+
@stderr = stderr
|
|
105
|
+
@exit_code = exit_code
|
|
106
|
+
@error = error
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Check if the process succeeded
|
|
110
|
+
#
|
|
111
|
+
# @return [Boolean]
|
|
112
|
+
def success?
|
|
113
|
+
@exit_code.zero? && @error.nil?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Combined output (stdout + stderr)
|
|
117
|
+
#
|
|
118
|
+
# @return [String]
|
|
119
|
+
def output
|
|
120
|
+
"#{@stdout}#{@stderr}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Alias for compatibility with Daytona
|
|
124
|
+
alias result stdout
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module E2B
|
|
4
|
+
module Models
|
|
5
|
+
# Information about a sandbox
|
|
6
|
+
class SandboxInfo
|
|
7
|
+
# @return [String] Sandbox ID
|
|
8
|
+
attr_reader :sandbox_id
|
|
9
|
+
|
|
10
|
+
# @return [String] Template ID used to create the sandbox
|
|
11
|
+
attr_reader :template_id
|
|
12
|
+
|
|
13
|
+
# @return [String, nil] Alias/name of the sandbox
|
|
14
|
+
attr_reader :alias_name
|
|
15
|
+
|
|
16
|
+
# @return [String] Client ID
|
|
17
|
+
attr_reader :client_id
|
|
18
|
+
|
|
19
|
+
# @return [Time] When the sandbox was started
|
|
20
|
+
attr_reader :started_at
|
|
21
|
+
|
|
22
|
+
# @return [Time] When the sandbox will end (timeout)
|
|
23
|
+
attr_reader :end_at
|
|
24
|
+
|
|
25
|
+
# @return [Integer] CPU count
|
|
26
|
+
attr_reader :cpu_count
|
|
27
|
+
|
|
28
|
+
# @return [Integer] Memory in MB
|
|
29
|
+
attr_reader :memory_mb
|
|
30
|
+
|
|
31
|
+
# @return [Hash] Metadata
|
|
32
|
+
attr_reader :metadata
|
|
33
|
+
|
|
34
|
+
# Create from API response hash
|
|
35
|
+
#
|
|
36
|
+
# @param data [Hash] API response data
|
|
37
|
+
# @return [SandboxInfo]
|
|
38
|
+
def self.from_hash(data)
|
|
39
|
+
new(
|
|
40
|
+
sandbox_id: data["sandboxID"] || data["sandbox_id"] || data[:sandboxID],
|
|
41
|
+
template_id: data["templateID"] || data["template_id"] || data[:templateID],
|
|
42
|
+
alias_name: data["alias"] || data[:alias],
|
|
43
|
+
client_id: data["clientID"] || data["client_id"] || data[:clientID],
|
|
44
|
+
started_at: parse_time(data["startedAt"] || data["started_at"] || data[:startedAt]),
|
|
45
|
+
end_at: parse_time(data["endAt"] || data["end_at"] || data[:endAt]),
|
|
46
|
+
cpu_count: data["cpuCount"] || data["cpu_count"] || data[:cpuCount],
|
|
47
|
+
memory_mb: data["memoryMB"] || data["memory_mb"] || data[:memoryMB],
|
|
48
|
+
metadata: data["metadata"] || data[:metadata] || {}
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def initialize(sandbox_id:, template_id:, alias_name: nil, client_id: nil,
|
|
53
|
+
started_at: nil, end_at: nil, cpu_count: nil, memory_mb: nil, metadata: {})
|
|
54
|
+
@sandbox_id = sandbox_id
|
|
55
|
+
@template_id = template_id
|
|
56
|
+
@alias_name = alias_name
|
|
57
|
+
@client_id = client_id
|
|
58
|
+
@started_at = started_at
|
|
59
|
+
@end_at = end_at
|
|
60
|
+
@cpu_count = cpu_count
|
|
61
|
+
@memory_mb = memory_mb
|
|
62
|
+
@metadata = metadata || {}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Check if sandbox is still running (not past end_at)
|
|
66
|
+
#
|
|
67
|
+
# @return [Boolean]
|
|
68
|
+
def running?
|
|
69
|
+
return true if @end_at.nil?
|
|
70
|
+
|
|
71
|
+
Time.now < @end_at
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Time remaining until timeout
|
|
75
|
+
#
|
|
76
|
+
# @return [Integer] Seconds remaining, 0 if expired
|
|
77
|
+
def time_remaining
|
|
78
|
+
return 0 if @end_at.nil?
|
|
79
|
+
|
|
80
|
+
remaining = (@end_at - Time.now).to_i
|
|
81
|
+
remaining.positive? ? remaining : 0
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.parse_time(value)
|
|
85
|
+
return nil if value.nil?
|
|
86
|
+
return value if value.is_a?(Time)
|
|
87
|
+
|
|
88
|
+
Time.parse(value)
|
|
89
|
+
rescue ArgumentError
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|