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.
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