exaonruby 1.0.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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +614 -0
  4. data/exaonruby.gemspec +37 -0
  5. data/exe/exa +7 -0
  6. data/lib/exa/cli.rb +458 -0
  7. data/lib/exa/client.rb +210 -0
  8. data/lib/exa/configuration.rb +81 -0
  9. data/lib/exa/endpoints/answer.rb +109 -0
  10. data/lib/exa/endpoints/contents.rb +141 -0
  11. data/lib/exa/endpoints/events.rb +71 -0
  12. data/lib/exa/endpoints/find_similar.rb +154 -0
  13. data/lib/exa/endpoints/imports.rb +145 -0
  14. data/lib/exa/endpoints/monitors.rb +193 -0
  15. data/lib/exa/endpoints/research.rb +158 -0
  16. data/lib/exa/endpoints/search.rb +195 -0
  17. data/lib/exa/endpoints/webhooks.rb +161 -0
  18. data/lib/exa/endpoints/webset_enrichments.rb +162 -0
  19. data/lib/exa/endpoints/webset_items.rb +90 -0
  20. data/lib/exa/endpoints/webset_searches.rb +137 -0
  21. data/lib/exa/endpoints/websets.rb +214 -0
  22. data/lib/exa/errors.rb +180 -0
  23. data/lib/exa/resources/answer_response.rb +101 -0
  24. data/lib/exa/resources/base.rb +56 -0
  25. data/lib/exa/resources/contents_response.rb +123 -0
  26. data/lib/exa/resources/event.rb +84 -0
  27. data/lib/exa/resources/import.rb +137 -0
  28. data/lib/exa/resources/monitor.rb +205 -0
  29. data/lib/exa/resources/paginated_response.rb +87 -0
  30. data/lib/exa/resources/research_task.rb +165 -0
  31. data/lib/exa/resources/search_response.rb +111 -0
  32. data/lib/exa/resources/search_result.rb +95 -0
  33. data/lib/exa/resources/webhook.rb +152 -0
  34. data/lib/exa/resources/webset.rb +491 -0
  35. data/lib/exa/resources/webset_item.rb +256 -0
  36. data/lib/exa/utils/parameter_converter.rb +159 -0
  37. data/lib/exa/utils/webhook_handler.rb +239 -0
  38. data/lib/exa/version.rb +7 -0
  39. data/lib/exa.rb +130 -0
  40. metadata +146 -0
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ # typed: strict
4
+
5
+ module Exa
6
+ module Resources
7
+ class Event < Base
8
+ # @return [String] Unique identifier
9
+ def id
10
+ get(:id)
11
+ end
12
+
13
+ # @return [String] Object type
14
+ def object
15
+ get(:object)
16
+ end
17
+
18
+ # @return [String] Event type (e.g., "webset.item.created")
19
+ def type
20
+ get(:type)
21
+ end
22
+
23
+ # @return [Hash] Event payload data
24
+ def data
25
+ get(:data, {})
26
+ end
27
+
28
+ # @return [String, nil] Related Webset ID
29
+ def webset_id
30
+ dig(:data, :webset_id) || dig(:data, :websetId)
31
+ end
32
+
33
+ # @return [String, nil] Related Item ID
34
+ def item_id
35
+ dig(:data, :item_id) || dig(:data, :itemId)
36
+ end
37
+
38
+ # @return [Time, nil] Event timestamp
39
+ def created_at
40
+ parse_time(get(:created_at))
41
+ end
42
+
43
+ # @return [Boolean] Whether this is a webset event
44
+ def webset_event?
45
+ type&.start_with?("webset.")
46
+ end
47
+
48
+ # @return [Boolean] Whether this is an item event
49
+ def item_event?
50
+ type&.include?(".item.")
51
+ end
52
+
53
+ # @return [Boolean] Whether this is a monitor event
54
+ def monitor_event?
55
+ type&.start_with?("monitor.")
56
+ end
57
+
58
+ # @return [Boolean] Whether this is an import event
59
+ def import_event?
60
+ type&.start_with?("import.")
61
+ end
62
+
63
+ private
64
+
65
+ def parse_time(value)
66
+ return nil unless value
67
+
68
+ Time.parse(value)
69
+ rescue ArgumentError
70
+ nil
71
+ end
72
+
73
+ def inspectable_attributes
74
+ { id: id, type: type }
75
+ end
76
+ end
77
+
78
+ class EventListResponse < PaginatedResponse
79
+ def data
80
+ @data ||= (get(:data, []) || []).map { |item| Event.new(item) }
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ # typed: strict
4
+
5
+ module Exa
6
+ module Resources
7
+ class Import < Base
8
+ # @return [String] Unique identifier
9
+ def id
10
+ get(:id)
11
+ end
12
+
13
+ # @return [String] Object type
14
+ def object
15
+ get(:object)
16
+ end
17
+
18
+ # @return [String] Status: pending, processing, completed, failed
19
+ def status
20
+ get(:status)
21
+ end
22
+
23
+ # @return [String] Format: csv, webset
24
+ def format
25
+ get(:format)
26
+ end
27
+
28
+ # @return [Hash, nil] Entity configuration
29
+ def entity
30
+ get(:entity)
31
+ end
32
+
33
+ # @return [String, nil] Entity type
34
+ def entity_type
35
+ dig(:entity, :type)
36
+ end
37
+
38
+ # @return [String, nil] Title
39
+ def title
40
+ get(:title)
41
+ end
42
+
43
+ # @return [Integer, nil] Number of entities
44
+ def count
45
+ get(:count)
46
+ end
47
+
48
+ # @return [Hash] Custom metadata
49
+ def metadata
50
+ get(:metadata, {})
51
+ end
52
+
53
+ # @return [String, nil] URL to upload file to
54
+ def upload_url
55
+ get(:upload_url)
56
+ end
57
+
58
+ # @return [Time, nil] Upload URL expiration
59
+ def upload_valid_until
60
+ parse_time(get(:upload_valid_until))
61
+ end
62
+
63
+ # @return [Boolean] Whether upload URL is still valid
64
+ def upload_valid?
65
+ valid_until = upload_valid_until
66
+ return false unless valid_until
67
+
68
+ Time.now < valid_until
69
+ end
70
+
71
+ # @return [String, nil] Failure reason
72
+ def failed_reason
73
+ get(:failed_reason)
74
+ end
75
+
76
+ # @return [Time, nil] Failure timestamp
77
+ def failed_at
78
+ parse_time(get(:failed_at))
79
+ end
80
+
81
+ # @return [String, nil] Failure message
82
+ def failed_message
83
+ get(:failed_message)
84
+ end
85
+
86
+ # @return [Time, nil] Creation timestamp
87
+ def created_at
88
+ parse_time(get(:created_at))
89
+ end
90
+
91
+ # @return [Time, nil] Last update timestamp
92
+ def updated_at
93
+ parse_time(get(:updated_at))
94
+ end
95
+
96
+ # @return [Boolean] Whether import is pending
97
+ def pending?
98
+ status == "pending"
99
+ end
100
+
101
+ # @return [Boolean] Whether import is processing
102
+ def processing?
103
+ status == "processing"
104
+ end
105
+
106
+ # @return [Boolean] Whether import completed
107
+ def completed?
108
+ status == "completed"
109
+ end
110
+
111
+ # @return [Boolean] Whether import failed
112
+ def failed?
113
+ status == "failed"
114
+ end
115
+
116
+ private
117
+
118
+ def parse_time(value)
119
+ return nil unless value
120
+
121
+ Time.parse(value)
122
+ rescue ArgumentError
123
+ nil
124
+ end
125
+
126
+ def inspectable_attributes
127
+ { id: id, status: status, title: title }
128
+ end
129
+ end
130
+
131
+ class ImportListResponse < PaginatedResponse
132
+ def data
133
+ @data ||= (get(:data, []) || []).map { |item| Import.new(item) }
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ # typed: strict
4
+
5
+ module Exa
6
+ module Resources
7
+ class Monitor < Base
8
+ # @return [String] Unique identifier
9
+ def id
10
+ get(:id)
11
+ end
12
+
13
+ # @return [String] Object type
14
+ def object
15
+ get(:object)
16
+ end
17
+
18
+ # @return [String] Status: enabled, disabled
19
+ def status
20
+ get(:status)
21
+ end
22
+
23
+ # @return [String] Parent Webset ID
24
+ def webset_id
25
+ get(:webset_id)
26
+ end
27
+
28
+ # @return [Hash, nil] Cadence configuration
29
+ def cadence
30
+ get(:cadence)
31
+ end
32
+
33
+ # @return [String, nil] Cron expression
34
+ def cron
35
+ dig(:cadence, :cron)
36
+ end
37
+
38
+ # @return [String, nil] Timezone
39
+ def timezone
40
+ dig(:cadence, :timezone)
41
+ end
42
+
43
+ # @return [Hash, nil] Behavior configuration
44
+ def behavior
45
+ get(:behavior)
46
+ end
47
+
48
+ # @return [String, nil] Behavior type: search, refresh
49
+ def behavior_type
50
+ dig(:behavior, :type)
51
+ end
52
+
53
+ # @return [Hash, nil] Behavior config
54
+ def behavior_config
55
+ dig(:behavior, :config)
56
+ end
57
+
58
+ # @return [MonitorRun, nil] Last run information
59
+ def last_run
60
+ run_data = get(:last_run)
61
+ return nil unless run_data
62
+
63
+ MonitorRun.new(run_data)
64
+ end
65
+
66
+ # @return [Time, nil] Next scheduled run
67
+ def next_run_at
68
+ parse_time(get(:next_run_at))
69
+ end
70
+
71
+ # @return [Hash] Custom metadata
72
+ def metadata
73
+ get(:metadata, {})
74
+ end
75
+
76
+ # @return [Time, nil] Creation timestamp
77
+ def created_at
78
+ parse_time(get(:created_at))
79
+ end
80
+
81
+ # @return [Time, nil] Last update timestamp
82
+ def updated_at
83
+ parse_time(get(:updated_at))
84
+ end
85
+
86
+ # @return [Boolean] Whether monitor is enabled
87
+ def enabled?
88
+ status == "enabled"
89
+ end
90
+
91
+ # @return [Boolean] Whether monitor is disabled
92
+ def disabled?
93
+ status == "disabled"
94
+ end
95
+
96
+ private
97
+
98
+ def parse_time(value)
99
+ return nil unless value
100
+
101
+ Time.parse(value)
102
+ rescue ArgumentError
103
+ nil
104
+ end
105
+
106
+ def inspectable_attributes
107
+ { id: id, status: status, cron: cron }
108
+ end
109
+ end
110
+
111
+ class MonitorRun < Base
112
+ # @return [String] Unique identifier
113
+ def id
114
+ get(:id)
115
+ end
116
+
117
+ # @return [String] Object type
118
+ def object
119
+ get(:object)
120
+ end
121
+
122
+ # @return [String] Status: created, running, completed, failed, canceled
123
+ def status
124
+ get(:status)
125
+ end
126
+
127
+ # @return [String] Parent monitor ID
128
+ def monitor_id
129
+ get(:monitor_id)
130
+ end
131
+
132
+ # @return [String] Run type: search, refresh
133
+ def type
134
+ get(:type)
135
+ end
136
+
137
+ # @return [Time, nil] Completion timestamp
138
+ def completed_at
139
+ parse_time(get(:completed_at))
140
+ end
141
+
142
+ # @return [Time, nil] Failure timestamp
143
+ def failed_at
144
+ parse_time(get(:failed_at))
145
+ end
146
+
147
+ # @return [String, nil] Failure reason
148
+ def failed_reason
149
+ get(:failed_reason)
150
+ end
151
+
152
+ # @return [Time, nil] Cancellation timestamp
153
+ def canceled_at
154
+ parse_time(get(:canceled_at))
155
+ end
156
+
157
+ # @return [Time, nil] Creation timestamp
158
+ def created_at
159
+ parse_time(get(:created_at))
160
+ end
161
+
162
+ # @return [Time, nil] Last update timestamp
163
+ def updated_at
164
+ parse_time(get(:updated_at))
165
+ end
166
+
167
+ # @return [Boolean] Whether run completed successfully
168
+ def completed?
169
+ status == "completed"
170
+ end
171
+
172
+ # @return [Boolean] Whether run failed
173
+ def failed?
174
+ status == "failed"
175
+ end
176
+
177
+ # @return [Boolean] Whether run was canceled
178
+ def canceled?
179
+ status == "canceled"
180
+ end
181
+
182
+ private
183
+
184
+ def parse_time(value)
185
+ return nil unless value
186
+
187
+ Time.parse(value)
188
+ rescue ArgumentError
189
+ nil
190
+ end
191
+ end
192
+
193
+ class MonitorListResponse < PaginatedResponse
194
+ def data
195
+ @data ||= (get(:data, []) || []).map { |item| Monitor.new(item) }
196
+ end
197
+ end
198
+
199
+ class MonitorRunListResponse < PaginatedResponse
200
+ def data
201
+ @data ||= (get(:data, []) || []).map { |item| MonitorRun.new(item) }
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ # typed: strict
4
+
5
+ module Exa
6
+ module Resources
7
+ class PaginatedResponse < Base
8
+ # @return [Array] Data items
9
+ def data
10
+ get(:data, [])
11
+ end
12
+
13
+ # @return [Boolean] Whether more items exist
14
+ def has_more
15
+ get(:has_more, false)
16
+ end
17
+
18
+ alias has_more? has_more
19
+
20
+ # @return [String, nil] Cursor for next page
21
+ def next_cursor
22
+ get(:next_cursor)
23
+ end
24
+
25
+ # @return [Boolean] Whether there are more pages
26
+ def more_pages?
27
+ has_more && !next_cursor.nil?
28
+ end
29
+
30
+ # @return [Integer] Number of items in current page
31
+ def count
32
+ data.length
33
+ end
34
+
35
+ # @return [Boolean] Whether data is empty
36
+ def empty?
37
+ data.empty?
38
+ end
39
+
40
+ # Iterates over each item in data
41
+ # @yield [Object] Each item
42
+ # @return [Enumerator, self]
43
+ def each(&block)
44
+ return data.each unless block
45
+
46
+ data.each(&block)
47
+ self
48
+ end
49
+
50
+ # @param index [Integer] Index of item
51
+ # @return [Object, nil] Item at index
52
+ def [](index)
53
+ data[index]
54
+ end
55
+
56
+ # @return [Object, nil] First item
57
+ def first
58
+ data.first
59
+ end
60
+
61
+ # @return [Object, nil] Last item
62
+ def last
63
+ data.last
64
+ end
65
+
66
+ private
67
+
68
+ def inspectable_attributes
69
+ { count: count, has_more: has_more }
70
+ end
71
+ end
72
+
73
+ class WebsetsListResponse < PaginatedResponse
74
+ # @return [Array<Webset>] List of websets
75
+ def data
76
+ @data ||= (get(:data, []) || []).map { |item| Webset.new(item) }
77
+ end
78
+ end
79
+
80
+ class WebsetItemsListResponse < PaginatedResponse
81
+ # @return [Array<WebsetItem>] List of webset items
82
+ def data
83
+ @data ||= (get(:data, []) || []).map { |item| WebsetItem.new(item) }
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ # typed: strict
4
+
5
+ module Exa
6
+ module Resources
7
+ class ResearchTask < Base
8
+ # @return [String] Unique identifier for the research task
9
+ def research_id
10
+ get(:research_id)
11
+ end
12
+
13
+ # @return [String] The model used for this research
14
+ def model
15
+ get(:model)
16
+ end
17
+
18
+ # @return [String] The original research instructions
19
+ def instructions
20
+ get(:instructions)
21
+ end
22
+
23
+ # @return [String] Status: pending, running, completed, canceled, failed
24
+ def status
25
+ get(:status)
26
+ end
27
+
28
+ # @return [Integer, nil] Unix timestamp when research was created (ms)
29
+ def created_at
30
+ get(:created_at)
31
+ end
32
+
33
+ # @return [Time, nil] Creation time as Ruby Time object
34
+ def created_at_time
35
+ ts = created_at
36
+ return nil unless ts
37
+
38
+ Time.at(ts / 1000.0)
39
+ end
40
+
41
+ # @return [Integer, nil] Unix timestamp when research completed (ms)
42
+ def completed_at
43
+ get(:completed_at)
44
+ end
45
+
46
+ # @return [Time, nil] Completion time as Ruby Time object
47
+ def completed_at_time
48
+ ts = completed_at
49
+ return nil unless ts
50
+
51
+ Time.at(ts / 1000.0)
52
+ end
53
+
54
+ # @return [Hash, nil] JSON Schema used to validate output
55
+ def output_schema
56
+ get(:output_schema)
57
+ end
58
+
59
+ # @return [String, Hash, nil] Research output (markdown or structured JSON)
60
+ def output
61
+ get(:output)
62
+ end
63
+
64
+ # @return [Array<Hash>] Events/progress updates
65
+ def events
66
+ get(:events, [])
67
+ end
68
+
69
+ # @return [Integer, nil] Number of operations completed
70
+ def operations_completed
71
+ dig(:operations, :completed)
72
+ end
73
+
74
+ # @return [Integer, nil] Total number of operations
75
+ def operations_total
76
+ dig(:operations, :total)
77
+ end
78
+
79
+ # @return [Float, nil] Progress percentage (0.0 to 1.0)
80
+ def progress
81
+ completed = operations_completed
82
+ total = operations_total
83
+ return nil unless completed && total && total > 0
84
+
85
+ completed.to_f / total
86
+ end
87
+
88
+ # @return [Integer, nil] Reasoning tokens used
89
+ def reasoning_tokens
90
+ dig(:usage, :reasoning_tokens)
91
+ end
92
+
93
+ # @return [Integer, nil] Agent search operations
94
+ def agent_search_operations
95
+ dig(:usage, :agent_search_operations)
96
+ end
97
+
98
+ # @return [Integer, nil] Agent page reads
99
+ def agent_page_reads
100
+ dig(:usage, :agent_page_reads)
101
+ end
102
+
103
+ # @return [String, nil] Error message if failed
104
+ def error_message
105
+ get(:error)
106
+ end
107
+
108
+ # @return [CostInfo, nil] Cost information
109
+ def cost
110
+ cost_data = get(:cost_dollars)
111
+ return nil unless cost_data
112
+
113
+ CostInfo.new(cost_data)
114
+ end
115
+
116
+ # @return [Boolean] Whether research is pending
117
+ def pending?
118
+ status == "pending"
119
+ end
120
+
121
+ # @return [Boolean] Whether research is running
122
+ def running?
123
+ status == "running"
124
+ end
125
+
126
+ # @return [Boolean] Whether research is completed
127
+ def completed?
128
+ status == "completed"
129
+ end
130
+
131
+ # @return [Boolean] Whether research was canceled
132
+ def canceled?
133
+ status == "canceled"
134
+ end
135
+
136
+ # @return [Boolean] Whether research failed
137
+ def failed?
138
+ status == "failed"
139
+ end
140
+
141
+ # @return [Boolean] Whether research is still in progress
142
+ def in_progress?
143
+ pending? || running?
144
+ end
145
+
146
+ # @return [Boolean] Whether research is finished (success or failure)
147
+ def finished?
148
+ completed? || canceled? || failed?
149
+ end
150
+
151
+ private
152
+
153
+ def inspectable_attributes
154
+ { research_id: research_id, status: status, model: model }
155
+ end
156
+ end
157
+
158
+ class ResearchListResponse < PaginatedResponse
159
+ # @return [Array<ResearchTask>] List of research tasks
160
+ def data
161
+ @data ||= (get(:data, []) || []).map { |item| ResearchTask.new(item) }
162
+ end
163
+ end
164
+ end
165
+ end