e2b 0.2.0 → 0.3.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.
@@ -31,6 +31,15 @@ module E2B
31
31
  # @return [Hash] Metadata
32
32
  attr_reader :metadata
33
33
 
34
+ # @return [String, nil] Current sandbox state
35
+ attr_reader :state
36
+
37
+ # @return [String, nil] Domain where the sandbox is hosted
38
+ attr_reader :sandbox_domain
39
+
40
+ # @return [String, nil] Envd version reported by the control plane
41
+ attr_reader :envd_version
42
+
34
43
  # Create from API response hash
35
44
  #
36
45
  # @param data [Hash] API response data
@@ -45,12 +54,16 @@ module E2B
45
54
  end_at: parse_time(data["endAt"] || data["end_at"] || data[:endAt]),
46
55
  cpu_count: data["cpuCount"] || data["cpu_count"] || data[:cpuCount],
47
56
  memory_mb: data["memoryMB"] || data["memory_mb"] || data[:memoryMB],
48
- metadata: data["metadata"] || data[:metadata] || {}
57
+ metadata: data["metadata"] || data[:metadata] || {},
58
+ state: data["state"] || data[:state],
59
+ sandbox_domain: data["domain"] || data[:domain],
60
+ envd_version: data["envdVersion"] || data["envd_version"] || data[:envdVersion]
49
61
  )
50
62
  end
51
63
 
52
64
  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: {})
65
+ started_at: nil, end_at: nil, cpu_count: nil, memory_mb: nil, metadata: {},
66
+ state: nil, sandbox_domain: nil, envd_version: nil)
54
67
  @sandbox_id = sandbox_id
55
68
  @template_id = template_id
56
69
  @alias_name = alias_name
@@ -60,12 +73,16 @@ module E2B
60
73
  @cpu_count = cpu_count
61
74
  @memory_mb = memory_mb
62
75
  @metadata = metadata || {}
76
+ @state = state
77
+ @sandbox_domain = sandbox_domain
78
+ @envd_version = envd_version
63
79
  end
64
80
 
65
81
  # Check if sandbox is still running (not past end_at)
66
82
  #
67
83
  # @return [Boolean]
68
84
  def running?
85
+ return false if @state == "paused"
69
86
  return true if @end_at.nil?
70
87
 
71
88
  Time.now < @end_at
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module E2B
4
+ module Models
5
+ class SnapshotInfo
6
+ attr_reader :snapshot_id
7
+
8
+ def self.from_hash(data)
9
+ new(
10
+ snapshot_id: data["snapshotID"] || data["snapshot_id"] || data[:snapshotID]
11
+ )
12
+ end
13
+
14
+ def initialize(snapshot_id:)
15
+ @snapshot_id = snapshot_id
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module E2B
4
+ module Models
5
+ class TemplateBuildStatusResponse
6
+ attr_reader :build_id, :template_id, :status, :log_entries, :logs, :reason
7
+
8
+ def self.from_hash(data)
9
+ new(
10
+ build_id: data["buildID"] || data["build_id"] || data[:buildID],
11
+ template_id: data["templateID"] || data["template_id"] || data[:templateID],
12
+ status: data["status"] || data[:status],
13
+ log_entries: Array(data["logEntries"] || data["log_entries"] || data[:logEntries]).map do |entry|
14
+ TemplateLogEntry.from_hash(entry)
15
+ end,
16
+ logs: data["logs"] || data[:logs] || [],
17
+ reason: BuildStatusReason.from_hash(data["reason"] || data[:reason])
18
+ )
19
+ end
20
+
21
+ def initialize(build_id:, template_id:, status:, log_entries:, logs:, reason:)
22
+ @build_id = build_id
23
+ @template_id = template_id
24
+ @status = status
25
+ @log_entries = log_entries || []
26
+ @logs = logs || []
27
+ @reason = reason
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module E2B
6
+ module Models
7
+ class TemplateLogEntry
8
+ attr_reader :timestamp, :level, :message
9
+
10
+ def self.from_hash(data)
11
+ new(
12
+ timestamp: parse_time(data["timestamp"] || data[:timestamp]),
13
+ level: data["level"] || data[:level],
14
+ message: data["message"] || data[:message]
15
+ )
16
+ end
17
+
18
+ def initialize(timestamp:, level:, message:)
19
+ @timestamp = timestamp
20
+ @level = level
21
+ @message = self.class.strip_ansi_escape_codes(message.to_s)
22
+ end
23
+
24
+ def to_s
25
+ "[#{@timestamp&.iso8601}] [#{@level}] #{@message}"
26
+ end
27
+
28
+ def self.parse_time(value)
29
+ return nil if value.nil?
30
+ return value if value.is_a?(Time)
31
+
32
+ Time.parse(value)
33
+ rescue ArgumentError
34
+ nil
35
+ end
36
+
37
+ def self.strip_ansi_escape_codes(message)
38
+ message.gsub(/\e\[[0-9;?]*[ -\/]*[@-~]/, "")
39
+ end
40
+ end
41
+
42
+ class TemplateLogEntryStart < TemplateLogEntry
43
+ def initialize(timestamp:, message:)
44
+ super(timestamp: timestamp, level: "debug", message: message)
45
+ end
46
+ end
47
+
48
+ class TemplateLogEntryEnd < TemplateLogEntry
49
+ def initialize(timestamp:, message:)
50
+ super(timestamp: timestamp, level: "debug", message: message)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module E2B
6
+ module Models
7
+ class TemplateTag
8
+ attr_reader :tag, :build_id, :created_at
9
+
10
+ def self.from_hash(data)
11
+ new(
12
+ tag: data["tag"] || data[:tag],
13
+ build_id: data["buildID"] || data["build_id"] || data[:buildID],
14
+ created_at: parse_time(data["createdAt"] || data["created_at"] || data[:createdAt])
15
+ )
16
+ end
17
+
18
+ def initialize(tag:, build_id:, created_at:)
19
+ @tag = tag
20
+ @build_id = build_id
21
+ @created_at = created_at
22
+ end
23
+
24
+ def self.parse_time(value)
25
+ return nil if value.nil?
26
+ return value if value.is_a?(Time)
27
+
28
+ Time.parse(value)
29
+ rescue ArgumentError
30
+ nil
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module E2B
4
+ module Models
5
+ class TemplateTagInfo
6
+ attr_reader :build_id, :tags
7
+
8
+ def self.from_hash(data)
9
+ new(
10
+ build_id: data["buildID"] || data["build_id"] || data[:buildID],
11
+ tags: data["tags"] || data[:tags] || []
12
+ )
13
+ end
14
+
15
+ def initialize(build_id:, tags:)
16
+ @build_id = build_id
17
+ @tags = tags || []
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module E2B
6
+ class BasePaginator
7
+ attr_reader :next_token
8
+
9
+ def initialize(limit:, next_token: nil, &fetch_page)
10
+ @limit = limit
11
+ @next_token = next_token
12
+ @fetch_page = fetch_page
13
+ @has_next = true
14
+ end
15
+
16
+ def has_next?
17
+ @has_next
18
+ end
19
+
20
+ def next_items
21
+ raise E2BError, "No more items to fetch" unless has_next?
22
+
23
+ items, token = @fetch_page.call(limit: @limit, next_token: @next_token)
24
+ @next_token = token
25
+ @has_next = !@next_token.nil? && !@next_token.empty?
26
+ items
27
+ end
28
+ end
29
+
30
+ class SandboxPaginator < BasePaginator
31
+ def initialize(http_client:, query: nil, limit: 100, next_token: nil)
32
+ normalized_query = normalize_query(query)
33
+
34
+ super(limit: limit, next_token: next_token) do |limit:, next_token:|
35
+ params = { limit: limit }
36
+ params[:nextToken] = next_token if next_token
37
+ if normalized_query[:metadata]
38
+ params[:metadata] = self.class.encode_metadata(normalized_query[:metadata])
39
+ end
40
+ params[:state] = normalized_query[:state] if normalized_query[:state]
41
+
42
+ response = http_client.get("/v2/sandboxes", params: params, detailed: true)
43
+ sandboxes = extract_sandboxes(response.body)
44
+
45
+ [
46
+ Array(sandboxes).map { |sandbox_data| Models::SandboxInfo.from_hash(sandbox_data) },
47
+ response.headers["x-next-token"]
48
+ ]
49
+ end
50
+ end
51
+
52
+ def self.encode_metadata(metadata)
53
+ encoded_pairs = metadata.to_h.each_with_object({}) do |(key, value), result|
54
+ result[URI.encode_www_form_component(key.to_s)] = URI.encode_www_form_component(value.to_s)
55
+ end
56
+
57
+ URI.encode_www_form(encoded_pairs)
58
+ end
59
+
60
+ private
61
+
62
+ def normalize_query(query)
63
+ return {} unless query
64
+
65
+ state = query[:state] || query["state"]
66
+ {
67
+ metadata: query[:metadata] || query["metadata"],
68
+ state: state ? Array(state).map(&:to_s) : nil
69
+ }
70
+ end
71
+
72
+ def extract_sandboxes(body)
73
+ return body if body.is_a?(Array)
74
+ return body["sandboxes"] || body[:sandboxes] || [] if body.is_a?(Hash)
75
+
76
+ []
77
+ end
78
+ end
79
+
80
+ class SnapshotPaginator < BasePaginator
81
+ def initialize(http_client:, sandbox_id: nil, limit: 100, next_token: nil)
82
+ super(limit: limit, next_token: next_token) do |limit:, next_token:|
83
+ params = { limit: limit }
84
+ params[:sandboxID] = sandbox_id if sandbox_id
85
+ params[:nextToken] = next_token if next_token
86
+
87
+ response = http_client.get("/snapshots", params: params, detailed: true)
88
+ snapshots = response.body.is_a?(Array) ? response.body : []
89
+
90
+ [
91
+ snapshots.map { |snapshot_data| Models::SnapshotInfo.from_hash(snapshot_data) },
92
+ response.headers["x-next-token"]
93
+ ]
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module E2B
4
+ class ReadyCmd
5
+ def initialize(cmd)
6
+ @cmd = cmd
7
+ end
8
+
9
+ def get_cmd
10
+ @cmd
11
+ end
12
+ end
13
+
14
+ class << self
15
+ def wait_for_port(port)
16
+ ReadyCmd.new("ss -tuln | grep :#{port}")
17
+ end
18
+
19
+ def wait_for_url(url, status_code = 200)
20
+ ReadyCmd.new(%(curl -s -o /dev/null -w "%{http_code}" #{url} | grep -q "#{status_code}"))
21
+ end
22
+
23
+ def wait_for_process(process_name)
24
+ ReadyCmd.new("pgrep #{process_name} > /dev/null")
25
+ end
26
+
27
+ def wait_for_file(filename)
28
+ ReadyCmd.new("[ -f #{filename} ]")
29
+ end
30
+
31
+ def wait_for_timeout(timeout)
32
+ seconds = [1, timeout.to_i / 1000].max
33
+ ReadyCmd.new("sleep #{seconds}")
34
+ end
35
+ end
36
+ end