exa-ai 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5cfcc57c0090503ea4b27748c7ade968fea3768f0793bd4939fd4debfb8bd7e0
4
- data.tar.gz: 2cff698538f70030cfccdcd3a91305e4696e8f5d78c7c0d4b110e44625109a6e
3
+ metadata.gz: ae8ad5762410325124acdcada89aed1a88b18dcbecc8b85431b99b0c05e0f354
4
+ data.tar.gz: b1d8ec27baf9bdf324af7183a6088e22e2437ef5d0bb0c97edee47eadeeed7de
5
5
  SHA512:
6
- metadata.gz: 4bd5ff3554ae0b7b8159411e516531a7b3bdb5ae1249c95a88a8522cfb7c0c51d904e41e2fb5d3b1e3f19c9f61ab3f154c53e76e05aa7220971ec4e160edf141
7
- data.tar.gz: 56233afd37573951715d5747585e3fb3ae59c3df38236d75a9f75a31459a38fa459386702487fbf01217fae72aa833b61a2a0968b903f19ab7f88e5d45a41cbd
6
+ metadata.gz: 1803b1811bc368be4d83bd9b02aa095c4f77f0dd97e05f3bd96ab9bc380d54b9a9bb7e17f5e3fca78c7a2f7348000f61bc55ec4875273eafa6dd0762fb09f7aa
7
+ data.tar.gz: 0ec026a9d5b4d05c8779aa3e10df8c25c48e5a728f92608941e28f2b18a46238908af53837dfa234b22f2ad595922f03d1ce25f98f6b9f932ecce8570914a8aa
data/README.md CHANGED
@@ -220,7 +220,6 @@ exa-ai search "AI" --output-format pretty
220
220
  - `--type TYPE` - Search type: keyword, neural, or auto (default: auto)
221
221
  - `--include-domains DOMAINS` - Comma-separated domains to include
222
222
  - `--exclude-domains DOMAINS` - Comma-separated domains to exclude
223
- - `--use-autoprompt` - Use Exa's autoprompt feature
224
223
  - `--output-format FORMAT` - json or pretty (default: json)
225
224
  - `--api-key KEY` - API key (or set EXA_API_KEY env var)
226
225
 
@@ -619,8 +618,7 @@ client = Exa::Client.new
619
618
  results = client.search("kubernetes tutorial",
620
619
  num_results: 20,
621
620
  type: "neural",
622
- include_domains: ["kubernetes.io", "github.com"],
623
- use_autoprompt: true
621
+ include_domains: ["kubernetes.io", "github.com"]
624
622
  )
625
623
 
626
624
  results.results.each do |item|
data/exe/exa-ai-answer CHANGED
@@ -12,7 +12,8 @@ def parse_args(argv)
12
12
  args = {
13
13
  output_format: "json",
14
14
  api_key: nil,
15
- text: false
15
+ text: false,
16
+ stream: false
16
17
  }
17
18
 
18
19
  # Extract query (first non-flag argument)
@@ -24,9 +25,15 @@ def parse_args(argv)
24
25
  when "--text"
25
26
  args[:text] = true
26
27
  i += 1
28
+ when "--stream"
29
+ args[:stream] = true
30
+ i += 1
27
31
  when "--output-schema"
28
32
  args[:output_schema] = argv[i + 1]
29
33
  i += 2
34
+ when "--system-prompt"
35
+ args[:system_prompt] = argv[i + 1]
36
+ i += 2
30
37
  when "--api-key"
31
38
  args[:api_key] = argv[i + 1]
32
39
  i += 2
@@ -43,17 +50,21 @@ def parse_args(argv)
43
50
  QUERY Question or query to answer (required)
44
51
 
45
52
  Options:
53
+ --stream Stream answer chunks in real-time
46
54
  --text Include full text content from sources
47
55
  --output-schema JSON JSON schema for structured output
56
+ --system-prompt TEXT System prompt to guide answer generation
48
57
  --api-key KEY Exa API key (or set EXA_API_KEY env var)
49
58
  --output-format FMT Output format: json, pretty, or text (default: json)
50
59
  --help, -h Show this help message
51
60
 
52
61
  Examples:
53
62
  exa-api answer "What is the capital of France?"
63
+ exa-api answer "Latest AI breakthroughs" --stream
54
64
  exa-api answer "Latest AI breakthroughs" --text
55
65
  exa-api answer "Ruby best practices" --output-format pretty
56
66
  exa-api answer "What is the capital of France?" --output-schema '{"type":"object","properties":{"city":{"type":"string"},"state":{"type":"string"}}}'
67
+ exa-api answer "What is Paris?" --system-prompt "Respond in the voice of a pirate"
57
68
  HELP
58
69
  exit 0
59
70
  else
@@ -89,6 +100,7 @@ begin
89
100
  # Prepare answer parameters
90
101
  answer_params = {}
91
102
  answer_params[:text] = args[:text] if args[:text]
103
+ answer_params[:system_prompt] = args[:system_prompt] if args[:system_prompt]
92
104
 
93
105
  # Parse output_schema as JSON if provided
94
106
  if args[:output_schema]
@@ -100,12 +112,24 @@ begin
100
112
  end
101
113
  end
102
114
 
103
- # Execute answer
104
- result = client.answer(args[:query], **answer_params)
105
-
106
- # Format and output result
107
- output = Exa::CLI::Formatters::AnswerFormatter.format(result, output_format)
108
- puts output
115
+ # Execute answer or streaming answer
116
+ if args[:stream]
117
+ # Streaming mode - output chunks in real-time
118
+ client.answer_stream(args[:query], **answer_params) do |chunk|
119
+ # Extract content from the streaming response format
120
+ # API returns: {"choices":[{"delta":{"content":"..."}}]}
121
+ if chunk["choices"]&.first&.dig("delta", "content")
122
+ print chunk["choices"][0]["delta"]["content"]
123
+ $stdout.flush
124
+ end
125
+ end
126
+ puts # Add newline at the end
127
+ else
128
+ # Non-streaming mode - collect full response and format
129
+ result = client.answer(args[:query], **answer_params)
130
+ output = Exa::CLI::Formatters::AnswerFormatter.format(result, output_format)
131
+ puts output
132
+ end
109
133
 
110
134
  rescue Exa::ConfigurationError => e
111
135
  $stderr.puts "Configuration error: #{e.message}"
@@ -11,8 +11,18 @@ def parse_args(args)
11
11
  ids = []
12
12
  api_key = nil
13
13
  text = false
14
- highlights = false
14
+ text_max_characters = nil
15
+ include_html_tags = false
15
16
  summary = false
17
+ summary_query = nil
18
+ summary_schema = nil
19
+ subpages = nil
20
+ subpage_target = []
21
+ links = nil
22
+ image_links = nil
23
+ context = false
24
+ context_max_characters = nil
25
+ livecrawl_timeout = nil
16
26
  output_format = nil
17
27
 
18
28
  i = 0
@@ -25,10 +35,48 @@ def parse_args(args)
25
35
  api_key = args[i]
26
36
  when "--text"
27
37
  text = true
28
- when "--highlights"
29
- highlights = true
38
+ i += 1
39
+ when "--text-max-characters"
40
+ i += 1
41
+ text_max_characters = args[i].to_i
42
+ when "--include-html-tags"
43
+ include_html_tags = true
44
+ i += 1
30
45
  when "--summary"
31
46
  summary = true
47
+ i += 1
48
+ when "--summary-query"
49
+ i += 1
50
+ summary_query = args[i]
51
+ when "--summary-schema"
52
+ i += 1
53
+ schema_arg = args[i]
54
+ summary_schema = if schema_arg.start_with?("@")
55
+ JSON.parse(File.read(schema_arg[1..]))
56
+ else
57
+ JSON.parse(schema_arg)
58
+ end
59
+ when "--subpages"
60
+ i += 1
61
+ subpages = args[i].to_i
62
+ when "--subpage-target"
63
+ i += 1
64
+ subpage_target << args[i]
65
+ when "--links"
66
+ i += 1
67
+ links = args[i].to_i
68
+ when "--image-links"
69
+ i += 1
70
+ image_links = args[i].to_i
71
+ when "--context"
72
+ context = true
73
+ i += 1
74
+ when "--context-max-characters"
75
+ i += 1
76
+ context_max_characters = args[i].to_i
77
+ when "--livecrawl-timeout"
78
+ i += 1
79
+ livecrawl_timeout = args[i].to_i
32
80
  when "--output-format"
33
81
  i += 1
34
82
  output_format = args[i]
@@ -41,34 +89,130 @@ def parse_args(args)
41
89
  ids_arg = arg
42
90
  ids = ids_arg.include?(",") ? ids_arg.split(",").map(&:strip) : [ids_arg]
43
91
  end
92
+ i += 1
93
+ end
94
+ end
95
+
96
+ {
97
+ ids: ids,
98
+ api_key: api_key,
99
+ text: text,
100
+ text_max_characters: text_max_characters,
101
+ include_html_tags: include_html_tags,
102
+ summary: summary,
103
+ summary_query: summary_query,
104
+ summary_schema: summary_schema,
105
+ subpages: subpages,
106
+ subpage_target: subpage_target,
107
+ links: links,
108
+ image_links: image_links,
109
+ context: context,
110
+ context_max_characters: context_max_characters,
111
+ livecrawl_timeout: livecrawl_timeout,
112
+ output_format: output_format
113
+ }
114
+ end
115
+
116
+ # Build contents parameters from extracted flags
117
+ def build_contents_params(args)
118
+ params = {}
119
+
120
+ # Text options
121
+ if args[:text]
122
+ if args[:text_max_characters] || args[:include_html_tags]
123
+ params[:text] = {}
124
+ params[:text][:max_characters] = args[:text_max_characters] if args[:text_max_characters]
125
+ params[:text][:include_html_tags] = args[:include_html_tags] if args[:include_html_tags]
126
+ else
127
+ params[:text] = true
128
+ end
129
+ end
130
+
131
+ # Summary options
132
+ if args[:summary]
133
+ if args[:summary_query] || args[:summary_schema]
134
+ params[:summary] = {}
135
+ params[:summary][:query] = args[:summary_query] if args[:summary_query]
136
+ params[:summary][:schema] = args[:summary_schema] if args[:summary_schema]
137
+ else
138
+ params[:summary] = true
139
+ end
140
+ end
141
+
142
+ # Context options
143
+ if args[:context]
144
+ if args[:context_max_characters]
145
+ params[:context] = { max_characters: args[:context_max_characters] }
146
+ else
147
+ params[:context] = true
44
148
  end
149
+ end
150
+
151
+ # Subpages options
152
+ params[:subpages] = args[:subpages] if args[:subpages]
153
+ params[:subpage_target] = args[:subpage_target] if args[:subpage_target].any?
45
154
 
46
- i += 1
155
+ # Extras options
156
+ if args[:links] || args[:image_links]
157
+ params[:extras] = {}
158
+ params[:extras][:links] = args[:links] if args[:links]
159
+ params[:extras][:image_links] = args[:image_links] if args[:image_links]
47
160
  end
48
161
 
49
- { ids: ids, api_key: api_key, text: text, highlights: highlights, summary: summary, output_format: output_format }
162
+ # Livecrawl options
163
+ params[:livecrawl_timeout] = args[:livecrawl_timeout] if args[:livecrawl_timeout]
164
+
165
+ params.empty? ? nil : params
50
166
  end
51
167
 
52
168
  def print_help
53
- puts "Exa Get-Contents - Retrieve page contents"
54
- puts ""
55
- puts "Usage: exa-api get-contents <ids> [options]"
56
- puts ""
57
- puts "Arguments:"
58
- puts " ids Comma-separated list of IDs or URLs (required)"
59
- puts ""
60
- puts "Options:"
61
- puts " --text Include page text in response"
62
- puts " --highlights Include highlights in response"
63
- puts " --summary Include summary in response"
64
- puts " --api-key KEY Exa API key (or set EXA_API_KEY env var)"
65
- puts " --output-format FMT Output format: json, pretty, or text (default: json)"
66
- puts " --help, -h Show this help message"
67
- puts ""
68
- puts "Examples:"
69
- puts " exa-api get-contents 'https://example.com'"
70
- puts " exa-api get-contents 'id1,id2,id3' --text"
71
- puts " exa-api get-contents 'https://example.com' --highlights --output-format pretty"
169
+ puts <<~HELP
170
+ Usage: exa-api get-contents <urls> [options]
171
+
172
+ Retrieve full page contents from URLs
173
+
174
+ Arguments:
175
+ urls Comma-separated list of URLs (required)
176
+
177
+ Options:
178
+ Text Extraction:
179
+ --text Include page text in response
180
+ --text-max-characters N Max characters for page text
181
+ --include-html-tags Include HTML tags in text extraction
182
+
183
+ Summary:
184
+ --summary Include AI-generated summary
185
+ --summary-query PROMPT Custom prompt for summary generation
186
+ --summary-schema FILE JSON schema for summary structure (@file syntax)
187
+
188
+ Context:
189
+ --context Format results as context for LLM RAG
190
+ --context-max-characters N Max characters for context string
191
+
192
+ Subpages:
193
+ --subpages N Number of subpages to crawl
194
+ --subpage-target PHRASE Subpage target phrases (repeatable)
195
+
196
+ Extras:
197
+ --links N Number of links to extract per result
198
+ --image-links N Number of image links to extract
199
+
200
+ Livecrawl:
201
+ --livecrawl-timeout N Timeout for livecrawling in milliseconds
202
+
203
+ General:
204
+ --api-key KEY Exa API key (or set EXA_API_KEY env var)
205
+ --output-format FMT Output format: json, pretty, or text (default: json)
206
+ --help, -h Show this help message
207
+
208
+ Examples:
209
+ exa-api get-contents 'https://example.com'
210
+ exa-api get-contents 'https://example.com' --text
211
+ exa-api get-contents 'https://example.com' --text --text-max-characters 3000 --include-html-tags
212
+ exa-api get-contents 'url1,url2' --summary --summary-query "Be terse"
213
+ exa-api get-contents 'https://example.com' --subpages 1 --subpage-target about
214
+ exa-api get-contents 'https://example.com' --links 5 --image-links 10
215
+ HELP
72
216
  end
73
217
 
74
218
  begin
@@ -77,9 +221,9 @@ begin
77
221
 
78
222
  # Validate IDs
79
223
  if options[:ids].empty?
80
- puts "Error: IDs argument required"
81
- puts ""
82
- puts "Run 'exa-api get-contents --help' for usage information."
224
+ $stderr.puts "Error: URLs argument required"
225
+ $stderr.puts ""
226
+ $stderr.puts "Run 'exa-api get-contents --help' for usage information."
83
227
  exit 1
84
228
  end
85
229
 
@@ -93,10 +237,8 @@ begin
93
237
  client = Exa::CLI::Base.build_client(api_key)
94
238
 
95
239
  # Build request parameters
96
- params = {}
97
- params[:text] = true if options[:text]
98
- params[:highlights] = true if options[:highlights]
99
- params[:summary] = true if options[:summary]
240
+ params = build_contents_params(options)
241
+ params ||= {}
100
242
 
101
243
  # Call API
102
244
  result = client.get_contents(options[:ids], **params)
@@ -104,11 +246,25 @@ begin
104
246
  # Format and output
105
247
  output = Exa::CLI::Formatters::ContentsFormatter.format(result, output_format)
106
248
  puts output
249
+ rescue Exa::ConfigurationError => e
250
+ $stderr.puts "Configuration error: #{e.message}"
251
+ exit 1
252
+ rescue Exa::Unauthorized => e
253
+ $stderr.puts "Authentication error: #{e.message}"
254
+ $stderr.puts "Check your API key (set EXA_API_KEY or use --api-key)"
255
+ exit 1
256
+ rescue Exa::ClientError => e
257
+ $stderr.puts "Client error: #{e.message}"
258
+ exit 1
259
+ rescue Exa::ServerError => e
260
+ $stderr.puts "Server error: #{e.message}"
261
+ $stderr.puts "The Exa API may be experiencing issues. Please try again later."
262
+ exit 1
107
263
  rescue Exa::Error => e
108
- puts "Error: #{e.message}"
264
+ $stderr.puts "Error: #{e.message}"
109
265
  exit 1
110
266
  rescue StandardError => e
111
- puts "Unexpected error: #{e.message}"
112
- puts e.backtrace.first(5) if ENV["DEBUG"]
267
+ $stderr.puts "Unexpected error: #{e.message}"
268
+ $stderr.puts e.backtrace.first(5) if ENV["DEBUG"]
113
269
  exit 1
114
270
  end
@@ -47,7 +47,7 @@ def parse_args(argv)
47
47
 
48
48
  Options:
49
49
  --instructions TEXT Research instructions (required)
50
- --model MODEL Model to use (e.g., gpt-4, gpt-3.5-turbo)
50
+ --model MODEL Research model: exa-research (default), exa-research-pro, exa-research-fast
51
51
  --output-schema JSON JSON schema string for structured output
52
52
  --wait Wait for task to complete (polls until done)
53
53
  --events Include event log in output (only with --wait)
@@ -58,7 +58,7 @@ def parse_args(argv)
58
58
  Examples:
59
59
  exa-api research-start --instructions "Find Ruby performance tips"
60
60
  exa-api research-start --instructions "Analyze AI trends" --wait --events
61
- exa-api research-start --instructions "Summarize papers" --model gpt-4 --wait
61
+ exa-api research-start --instructions "Summarize papers" --model exa-research-pro --wait
62
62
  exa-api research-start --instructions "Find stats" --output-schema '{"type":"object"}'
63
63
  HELP
64
64
  exit 0
data/exe/exa-ai-search CHANGED
@@ -39,9 +39,6 @@ def parse_args(argv)
39
39
  when "--exclude-domains"
40
40
  args[:exclude_domains] = argv[i + 1].split(",").map(&:strip)
41
41
  i += 2
42
- when "--use-autoprompt"
43
- args[:use_autoprompt] = true
44
- i += 1
45
42
  when "--api-key"
46
43
  args[:api_key] = argv[i + 1]
47
44
  i += 2
@@ -159,7 +156,6 @@ def parse_args(argv)
159
156
  --image-links N Number of image links to extract
160
157
 
161
158
  General Options:
162
- --use-autoprompt Use Exa's autoprompt feature
163
159
  --linkedin TYPE Search LinkedIn: company, person, or all
164
160
  --api-key KEY Exa API key (or set EXA_API_KEY env var)
165
161
  --output-format FMT Output format: json, pretty, or text (default: json)
@@ -271,7 +267,6 @@ begin
271
267
  search_params[:exclude_text] = args[:exclude_text] if args[:exclude_text]
272
268
  contents = build_contents(args)
273
269
  search_params.merge!(contents) if contents
274
- search_params[:useAutoprompt] = args[:use_autoprompt] if args[:use_autoprompt]
275
270
 
276
271
  # Execute search based on LinkedIn type
277
272
  result = case args[:linkedin]
data/lib/exa/client.rb CHANGED
@@ -71,6 +71,21 @@ module Exa
71
71
  Services::Answer.new(connection, query: query, **options).call
72
72
  end
73
73
 
74
+ # Stream AI-generated answers to a query
75
+ #
76
+ # Returns partial answer chunks as they are generated by the API.
77
+ #
78
+ # @param query [String] Question or query
79
+ # @param options [Hash] Answer options
80
+ # @option options [Boolean] :text Include full text content (default: false)
81
+ # @option options [Hash] :output_schema JSON schema for structured output
82
+ # @yield [chunk] Yields each answer chunk as it arrives
83
+ # @yieldparam chunk [Hash] Partial answer data with {"answer" => "text"}
84
+ # @return [void]
85
+ def answer_stream(query, **options, &block)
86
+ Services::AnswerStream.new(connection, query: query, **options).call(&block)
87
+ end
88
+
74
89
  # Start an asynchronous research task
75
90
  #
76
91
  # @param params [Hash] Research parameters
@@ -0,0 +1,90 @@
1
+ require "json"
2
+
3
+ module Exa
4
+ module Services
5
+ class AnswerStream
6
+ def initialize(connection, **params)
7
+ @connection = connection
8
+ @params = params.merge(stream: true)
9
+ end
10
+
11
+ def call(&block)
12
+ raise ArgumentError, "block required for streaming" unless block_given?
13
+
14
+ # Use instance variable to track buffer across on_data callbacks
15
+ @buffer = ""
16
+
17
+ # Configure the request to stream chunks via on_data callback
18
+ @connection.post("/answer", @params) do |req|
19
+ req.options.on_data = proc do |chunk|
20
+ # Add chunk to buffer and process complete SSE events
21
+ @buffer += chunk
22
+ process_sse_buffer(&block)
23
+ end
24
+ end
25
+
26
+ # Process any remaining data in buffer after stream ends
27
+ process_remaining_buffer(&block) if @buffer.length.positive?
28
+ end
29
+
30
+ private
31
+
32
+ def process_sse_buffer
33
+ # Extract and process complete SSE events (separated by \n\n)
34
+ # If buffer ends with \n\n, all parts are complete.
35
+ # Otherwise, keep the last part in buffer for next chunk.
36
+
37
+ return if @buffer.empty?
38
+
39
+ parts = @buffer.split("\n\n")
40
+
41
+ # When split by a delimiter, if the string ends with the delimiter,
42
+ # split doesn't add a trailing empty string. So we need to track
43
+ # whether the buffer ended with \n\n to know if the last part is incomplete.
44
+ if @buffer.end_with?("\n\n")
45
+ # All parts are complete, clear buffer
46
+ complete_parts = parts
47
+ @buffer = ""
48
+ else
49
+ # Last part is incomplete, keep it for next chunk
50
+ complete_parts = parts[0...-1]
51
+ @buffer = parts.last || ""
52
+ end
53
+
54
+ # Process all complete events
55
+ complete_parts.each do |event|
56
+ next if event.empty?
57
+
58
+ lines = event.split("\n")
59
+ lines.each do |line|
60
+ if line.start_with?("data: ")
61
+ json_str = line.sub(/^data: /, "").strip
62
+ begin
63
+ data = JSON.parse(json_str)
64
+ yield(data)
65
+ rescue JSON::ParserError
66
+ # Skip lines that aren't valid JSON
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ def process_remaining_buffer
74
+ # Process any remaining incomplete buffer
75
+ lines = @buffer.split("\n")
76
+ lines.each do |line|
77
+ if line.start_with?("data: ")
78
+ json_str = line.sub(/^data: /, "").strip
79
+ begin
80
+ data = JSON.parse(json_str)
81
+ yield(data)
82
+ rescue JSON::ParserError
83
+ # Skip lines that aren't valid JSON
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
data/lib/exa/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Exa
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/exa.rb CHANGED
@@ -18,6 +18,7 @@ require_relative "exa/services/research_get"
18
18
  require_relative "exa/services/research_start"
19
19
  require_relative "exa/services/research_list"
20
20
  require_relative "exa/services/answer"
21
+ require_relative "exa/services/answer_stream"
21
22
  require_relative "exa/services/context"
22
23
  require_relative "exa/client"
23
24
  require_relative "exa/cli/base"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exa-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Jackson
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: ld-eventsource
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: minitest
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -139,6 +153,7 @@ files:
139
153
  - lib/exa/resources/research_task.rb
140
154
  - lib/exa/resources/search_result.rb
141
155
  - lib/exa/services/answer.rb
156
+ - lib/exa/services/answer_stream.rb
142
157
  - lib/exa/services/context.rb
143
158
  - lib/exa/services/find_similar.rb
144
159
  - lib/exa/services/get_contents.rb