exa-ai 0.11.1 → 0.12.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 +4 -4
- data/README.md +112 -0
- data/exe/exa-ai +28 -1
- data/exe/exa-ai-agent-run-cancel +90 -0
- data/exe/exa-ai-agent-run-create +278 -0
- data/exe/exa-ai-agent-run-delete +96 -0
- data/exe/exa-ai-agent-run-events +105 -0
- data/exe/exa-ai-agent-run-get +90 -0
- data/exe/exa-ai-agent-run-list +94 -0
- data/lib/exa/cli/formatters/agent_run_formatter.rb +149 -0
- data/lib/exa/client.rb +77 -0
- data/lib/exa/error.rb +1 -0
- data/lib/exa/middleware/raise_error.rb +2 -0
- data/lib/exa/resources/agent_run.rb +54 -0
- data/lib/exa/resources/agent_run_event_list.rb +21 -0
- data/lib/exa/resources/agent_run_list.rb +18 -0
- data/lib/exa/services/agent_run_cancel.rb +32 -0
- data/lib/exa/services/agent_run_create.rb +33 -0
- data/lib/exa/services/agent_run_delete.rb +37 -0
- data/lib/exa/services/agent_run_events.rb +28 -0
- data/lib/exa/services/agent_run_get.rb +33 -0
- data/lib/exa/services/agent_run_list.rb +41 -0
- data/lib/exa/services/agent_run_stream.rb +110 -0
- data/lib/exa/services/parameter_converter.rb +2 -0
- data/lib/exa/version.rb +1 -1
- data/lib/exa.rb +11 -0
- metadata +18 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aac702bd80757b3b6f52899e116899184df36530b6330c496c405e52a247eb95
|
|
4
|
+
data.tar.gz: 46669a7169fc6f2155b2869b7bf90740d6e28d90855a35e8064707778115b8db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c6db2aaa317b4e3b4a07c2a09a6af6b41e701961333b4dc3fa46c3c66bd535c529c82b92091a160be1d8ed7c25e7a7bc91933aa29278a4dd809bca92a6d1036d
|
|
7
|
+
data.tar.gz: 49159797c3615649188ab4a16d076b29a3b2134446731d6cf38c6fb59fddb4e49b823cdd07ef7390d030458cc04fabdcb20278581df2120b7c69721dbf6bf548
|
data/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Ruby client for the Exa.ai API. Search and analyze web content using neural sear
|
|
|
9
9
|
- [Configuration](#configuration)
|
|
10
10
|
- [Quick Start](#quick-start)
|
|
11
11
|
- [Features](#features)
|
|
12
|
+
- [Agent](#agent)
|
|
12
13
|
- [Error Handling](#error-handling)
|
|
13
14
|
- [Documentation](#documentation)
|
|
14
15
|
- [Development](#development)
|
|
@@ -226,6 +227,117 @@ The gem provides complete access to Exa's API endpoints:
|
|
|
226
227
|
- **Enrichments** — Create and manage AI-powered data enrichment tasks on websets
|
|
227
228
|
- **Imports** — Upload CSV files to import external data into websets
|
|
228
229
|
|
|
230
|
+
### Agent
|
|
231
|
+
- **Agent API** — Asynchronous research agent with citations, streaming, and full run lifecycle
|
|
232
|
+
|
|
233
|
+
## Agent
|
|
234
|
+
|
|
235
|
+
Exa's Agent API runs asynchronous research agents that search the web, synthesize findings, and return structured output with source citations. Runs are long-lived — create one, poll for completion, and stream events as it progresses.
|
|
236
|
+
|
|
237
|
+
### Ruby API
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
require 'exa-ai'
|
|
241
|
+
|
|
242
|
+
client = Exa::Client.new(api_key: ENV['EXA_API_KEY'])
|
|
243
|
+
|
|
244
|
+
# Create an async agent run
|
|
245
|
+
run = client.agent_run_create(
|
|
246
|
+
query: "AI infrastructure startups that raised Series A in 2025",
|
|
247
|
+
effort: "high"
|
|
248
|
+
)
|
|
249
|
+
puts run.id # => "run_abc123"
|
|
250
|
+
puts run.status # => "queued"
|
|
251
|
+
puts run.queued? # => true
|
|
252
|
+
|
|
253
|
+
# Poll for the result
|
|
254
|
+
run = client.agent_run_get(run.id)
|
|
255
|
+
if run.completed?
|
|
256
|
+
puts run.output[:text]
|
|
257
|
+
puts run.output[:structured] # already-parsed JSON — no string parsing needed
|
|
258
|
+
puts run.output[:grounding]
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Create a run with structured output and a system prompt
|
|
262
|
+
run = client.agent_run_create(
|
|
263
|
+
query: "AI infrastructure startups that raised Series A in 2025",
|
|
264
|
+
system_prompt: "Return only companies headquartered in the US.",
|
|
265
|
+
effort: "high",
|
|
266
|
+
output_schema: {
|
|
267
|
+
type: "object",
|
|
268
|
+
properties: {
|
|
269
|
+
companies: { type: "array", items: { type: "string" } }
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Attach premium data partners (Exa Connect) alongside web search.
|
|
275
|
+
# The agent queries each partner where it's strongest and blends the
|
|
276
|
+
# results into one grounded, structured answer.
|
|
277
|
+
run = client.agent_run_create(
|
|
278
|
+
query: "Profile Anthropic: total funding and estimated monthly web traffic",
|
|
279
|
+
data_sources: [{ provider: "fiber_ai" }, { provider: "similarweb" }],
|
|
280
|
+
output_schema: {
|
|
281
|
+
type: "object",
|
|
282
|
+
properties: {
|
|
283
|
+
name: { type: "string" },
|
|
284
|
+
totalFunding: { type: "string" }, # from Fiber.ai
|
|
285
|
+
monthlyVisits: { type: "number" } # from Similarweb
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Stream events as they arrive
|
|
291
|
+
client.agent_run_stream(query: "AI infrastructure startups that raised Series A in 2025") do |event, data|
|
|
292
|
+
puts "#{event}: #{data.inspect}"
|
|
293
|
+
# event is the SSE event-type string, e.g. "agent_run.completed"
|
|
294
|
+
# data is the parsed JSON hash
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# List recent runs
|
|
298
|
+
runs = client.agent_run_list(limit: 5)
|
|
299
|
+
puts runs.has_more # => true/false
|
|
300
|
+
puts runs.next_cursor # => pagination cursor
|
|
301
|
+
runs.data.each { |r| puts "#{r.id}: #{r.status}" }
|
|
302
|
+
|
|
303
|
+
# Fetch a run's events
|
|
304
|
+
events = client.agent_run_events(run.id)
|
|
305
|
+
|
|
306
|
+
# Cancel or delete a run
|
|
307
|
+
client.agent_run_cancel(run.id)
|
|
308
|
+
client.agent_run_delete(run.id)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
> **Note:** Multi-word keys inside `input`, `data_sources` items, and `metadata` are passed through verbatim — supply them in the exact shape the API expects, the same contract as `output_schema`.
|
|
312
|
+
|
|
313
|
+
### Command Line
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# Create a run and wait for it to finish
|
|
317
|
+
exa-ai agent-run-create --query "AI infrastructure startups that raised Series A in 2025" --wait --output-format pretty
|
|
318
|
+
|
|
319
|
+
# Attach premium data partners (Exa Connect) with a structured schema.
|
|
320
|
+
# Run `exa-ai agent-run-create --help` to see every provider and when to use it:
|
|
321
|
+
# fiber_ai, similarweb, baselayer, affiliate, particle_news, financial_datasets, jinko
|
|
322
|
+
exa-ai agent-run-create --wait \
|
|
323
|
+
--query "Profile Anthropic: total funding and monthly web traffic" \
|
|
324
|
+
--data-sources fiber_ai,similarweb \
|
|
325
|
+
--output-schema '{"type":"object","properties":{"name":{"type":"string"},"totalFunding":{"type":"string"},"monthlyVisits":{"type":"number"}}}'
|
|
326
|
+
|
|
327
|
+
# Fetch an existing run
|
|
328
|
+
exa-ai agent-run-get <run_id>
|
|
329
|
+
|
|
330
|
+
# List recent runs
|
|
331
|
+
exa-ai agent-run-list --limit 5
|
|
332
|
+
|
|
333
|
+
# Cancel or delete a run
|
|
334
|
+
exa-ai agent-run-cancel <run_id>
|
|
335
|
+
exa-ai agent-run-delete <run_id>
|
|
336
|
+
|
|
337
|
+
# Fetch a run's events
|
|
338
|
+
exa-ai agent-run-events <run_id>
|
|
339
|
+
```
|
|
340
|
+
|
|
229
341
|
## Error Handling
|
|
230
342
|
|
|
231
343
|
```ruby
|
data/exe/exa-ai
CHANGED
|
@@ -44,7 +44,13 @@ module ExaCLI
|
|
|
44
44
|
"monitor-update" => "Update a monitor",
|
|
45
45
|
"monitor-delete" => "Delete a monitor",
|
|
46
46
|
"monitor-runs-list" => "List monitor runs",
|
|
47
|
-
"monitor-runs-get" => "Get monitor run details"
|
|
47
|
+
"monitor-runs-get" => "Get monitor run details",
|
|
48
|
+
"agent-run-create" => "Create an agent run",
|
|
49
|
+
"agent-run-get" => "Get agent run details",
|
|
50
|
+
"agent-run-list" => "List agent runs",
|
|
51
|
+
"agent-run-cancel" => "Cancel an agent run",
|
|
52
|
+
"agent-run-delete" => "Delete an agent run",
|
|
53
|
+
"agent-run-events" => "Get events for an agent run"
|
|
48
54
|
}.freeze
|
|
49
55
|
|
|
50
56
|
def self.run
|
|
@@ -134,6 +140,18 @@ module ExaCLI
|
|
|
134
140
|
exec File.expand_path("../exa-ai-monitor-runs-list", __FILE__), *ARGV[1..]
|
|
135
141
|
when "monitor-runs-get"
|
|
136
142
|
exec File.expand_path("../exa-ai-monitor-runs-get", __FILE__), *ARGV[1..]
|
|
143
|
+
when "agent-run-create"
|
|
144
|
+
exec File.expand_path("../exa-ai-agent-run-create", __FILE__), *ARGV[1..]
|
|
145
|
+
when "agent-run-get"
|
|
146
|
+
exec File.expand_path("../exa-ai-agent-run-get", __FILE__), *ARGV[1..]
|
|
147
|
+
when "agent-run-list"
|
|
148
|
+
exec File.expand_path("../exa-ai-agent-run-list", __FILE__), *ARGV[1..]
|
|
149
|
+
when "agent-run-cancel"
|
|
150
|
+
exec File.expand_path("../exa-ai-agent-run-cancel", __FILE__), *ARGV[1..]
|
|
151
|
+
when "agent-run-delete"
|
|
152
|
+
exec File.expand_path("../exa-ai-agent-run-delete", __FILE__), *ARGV[1..]
|
|
153
|
+
when "agent-run-events"
|
|
154
|
+
exec File.expand_path("../exa-ai-agent-run-events", __FILE__), *ARGV[1..]
|
|
137
155
|
else
|
|
138
156
|
print_error_for_command(ARGV[0])
|
|
139
157
|
exit 1
|
|
@@ -198,6 +216,15 @@ module ExaCLI
|
|
|
198
216
|
["import-delete", "Delete an import"]
|
|
199
217
|
])
|
|
200
218
|
|
|
219
|
+
print_command_section("Agent Runs", [
|
|
220
|
+
["agent-run-create", "Create an agent run"],
|
|
221
|
+
["agent-run-get", "Get agent run details"],
|
|
222
|
+
["agent-run-list", "List agent runs"],
|
|
223
|
+
["agent-run-cancel", "Cancel an agent run"],
|
|
224
|
+
["agent-run-delete", "Delete an agent run"],
|
|
225
|
+
["agent-run-events", "Get events for an agent run"]
|
|
226
|
+
])
|
|
227
|
+
|
|
201
228
|
print_command_section("Webset Monitors", [
|
|
202
229
|
["monitor-create", "Create a webset monitor"],
|
|
203
230
|
["monitor-get", "Get monitor details"],
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "exa-ai"
|
|
5
|
+
|
|
6
|
+
# Parse command-line arguments
|
|
7
|
+
api_key = nil
|
|
8
|
+
run_id = nil
|
|
9
|
+
output_format = "json"
|
|
10
|
+
|
|
11
|
+
args = ARGV.dup
|
|
12
|
+
while args.any?
|
|
13
|
+
arg = args.shift
|
|
14
|
+
case arg
|
|
15
|
+
when "--api-key"
|
|
16
|
+
api_key = args.shift
|
|
17
|
+
when "--output-format"
|
|
18
|
+
output_format = args.shift
|
|
19
|
+
when "--help", "-h"
|
|
20
|
+
puts <<~HELP
|
|
21
|
+
Usage: exa-ai agent-run-cancel <run_id> [options]
|
|
22
|
+
|
|
23
|
+
Cancel an in-progress agent run.
|
|
24
|
+
|
|
25
|
+
Arguments:
|
|
26
|
+
run_id ID of the agent run to cancel
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
--api-key KEY Exa API key (or use EXA_API_KEY env var)
|
|
30
|
+
--output-format FORMAT Output format: json, pretty, or text (default: json)
|
|
31
|
+
--help, -h Show this help message
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
exa-ai agent-run-cancel run_123
|
|
35
|
+
exa-ai agent-run-cancel run_123 --output-format pretty
|
|
36
|
+
HELP
|
|
37
|
+
exit 0
|
|
38
|
+
else
|
|
39
|
+
# First positional argument is the run_id
|
|
40
|
+
if run_id.nil?
|
|
41
|
+
run_id = arg
|
|
42
|
+
else
|
|
43
|
+
warn "Unknown option: #{arg}"
|
|
44
|
+
exit 1
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Validate required arguments
|
|
50
|
+
if run_id.nil?
|
|
51
|
+
warn "Error: Run ID argument required"
|
|
52
|
+
warn "Usage: exa-ai agent-run-cancel <run_id> [options]"
|
|
53
|
+
warn "Try 'exa-ai agent-run-cancel --help' for more information"
|
|
54
|
+
exit 1
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
begin
|
|
58
|
+
# Resolve API key
|
|
59
|
+
api_key = Exa::CLI::Base.resolve_api_key(api_key)
|
|
60
|
+
|
|
61
|
+
# Build client
|
|
62
|
+
client = Exa::CLI::Base.build_client(api_key)
|
|
63
|
+
|
|
64
|
+
# Call API
|
|
65
|
+
result = client.agent_run_cancel(run_id)
|
|
66
|
+
|
|
67
|
+
# Format output
|
|
68
|
+
formatted = Exa::CLI::Formatters::AgentRunFormatter.format_run(result, output_format)
|
|
69
|
+
puts formatted
|
|
70
|
+
|
|
71
|
+
rescue Exa::NotFound => e
|
|
72
|
+
warn "Agent run not found: #{e.message}"
|
|
73
|
+
exit 1
|
|
74
|
+
rescue Exa::Unauthorized => e
|
|
75
|
+
warn "Authentication failed: #{e.message}"
|
|
76
|
+
warn "Please provide a valid API key via --api-key or EXA_API_KEY environment variable"
|
|
77
|
+
exit 1
|
|
78
|
+
rescue Exa::ClientError => e
|
|
79
|
+
warn "Client error: #{e.message}"
|
|
80
|
+
exit 1
|
|
81
|
+
rescue Exa::ServerError => e
|
|
82
|
+
warn "Server error: #{e.message}"
|
|
83
|
+
exit 1
|
|
84
|
+
rescue Exa::Error => e
|
|
85
|
+
warn "Error: #{e.message}"
|
|
86
|
+
exit 1
|
|
87
|
+
rescue StandardError => e
|
|
88
|
+
warn "Unexpected error: #{e.message}"
|
|
89
|
+
exit 1
|
|
90
|
+
end
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "exa-ai"
|
|
5
|
+
|
|
6
|
+
# Parse a JSON flag value: inline JSON, or @path to read JSON from a file.
|
|
7
|
+
def parse_json_arg(value, flag_name)
|
|
8
|
+
json = value.to_s.start_with?("@") ? File.read(value.to_s[1..]) : value.to_s
|
|
9
|
+
JSON.parse(json)
|
|
10
|
+
rescue JSON::ParserError => e
|
|
11
|
+
$stderr.puts "Error: #{flag_name} is not valid JSON (#{e.message})"
|
|
12
|
+
exit 1
|
|
13
|
+
rescue Errno::ENOENT
|
|
14
|
+
$stderr.puts "Error: #{flag_name} file not found: #{value.to_s[1..]}"
|
|
15
|
+
exit 1
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Parse command-line arguments
|
|
19
|
+
def parse_args(argv)
|
|
20
|
+
args = {
|
|
21
|
+
output_format: "json",
|
|
22
|
+
api_key: nil,
|
|
23
|
+
wait: false
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
i = 0
|
|
27
|
+
while i < argv.length
|
|
28
|
+
arg = argv[i]
|
|
29
|
+
case arg
|
|
30
|
+
when "--query"
|
|
31
|
+
args[:query] = argv[i + 1]
|
|
32
|
+
i += 2
|
|
33
|
+
when "--system-prompt"
|
|
34
|
+
args[:system_prompt] = argv[i + 1]
|
|
35
|
+
i += 2
|
|
36
|
+
when "--effort"
|
|
37
|
+
args[:effort] = argv[i + 1]
|
|
38
|
+
i += 2
|
|
39
|
+
when "--data-sources"
|
|
40
|
+
# ponytail: comma-separated slugs cover all documented usage ({provider: slug}).
|
|
41
|
+
# For richer per-provider config, use the Ruby API (client.agent_run_create).
|
|
42
|
+
args[:data_sources] = argv[i + 1].to_s.split(",").map { |s| { provider: s.strip } }
|
|
43
|
+
i += 2
|
|
44
|
+
when "--input-data"
|
|
45
|
+
args[:input_data] = parse_json_arg(argv[i + 1], "--input-data")
|
|
46
|
+
i += 2
|
|
47
|
+
when "--input-exclusion"
|
|
48
|
+
args[:input_exclusion] = parse_json_arg(argv[i + 1], "--input-exclusion")
|
|
49
|
+
i += 2
|
|
50
|
+
when "--previous-run-id"
|
|
51
|
+
args[:previous_run_id] = argv[i + 1]
|
|
52
|
+
i += 2
|
|
53
|
+
when "--output-schema"
|
|
54
|
+
args[:output_schema] = parse_json_arg(argv[i + 1], "--output-schema")
|
|
55
|
+
i += 2
|
|
56
|
+
when "--output-format"
|
|
57
|
+
args[:output_format] = argv[i + 1]
|
|
58
|
+
i += 2
|
|
59
|
+
when "--wait"
|
|
60
|
+
args[:wait] = true
|
|
61
|
+
i += 1
|
|
62
|
+
when "--api-key"
|
|
63
|
+
args[:api_key] = argv[i + 1]
|
|
64
|
+
i += 2
|
|
65
|
+
when "--help", "-h"
|
|
66
|
+
puts <<~HELP
|
|
67
|
+
Usage: exa-ai agent-run-create --query "TEXT" [OPTIONS]
|
|
68
|
+
|
|
69
|
+
Create an agent run using Exa AI. Optionally attach premium data
|
|
70
|
+
partners (Exa Connect) so the agent queries their databases alongside
|
|
71
|
+
web search and blends everything into one grounded answer.
|
|
72
|
+
|
|
73
|
+
Options:
|
|
74
|
+
--query TEXT Query for the agent run (required)
|
|
75
|
+
--system-prompt TEXT System prompt to guide the agent
|
|
76
|
+
--effort LEVEL Effort level: low, medium, high
|
|
77
|
+
--data-sources LIST Comma-separated partner slugs to attach (see
|
|
78
|
+
"Data sources" below), e.g. fiber_ai,similarweb
|
|
79
|
+
--input-data JSON JSON array of objects for the agent to process,
|
|
80
|
+
or @file.json to read from a file. Each element
|
|
81
|
+
is a record the agent enriches or acts on.
|
|
82
|
+
--input-exclusion JSON JSON array of objects to exclude from results,
|
|
83
|
+
or @file.json. Useful to skip already-known rows.
|
|
84
|
+
--previous-run-id ID Continue from a completed run. The agent picks
|
|
85
|
+
up where the prior run left off.
|
|
86
|
+
--output-schema JSON JSON schema for structured output, or @file.json
|
|
87
|
+
to read it from a file. Recommended alongside
|
|
88
|
+
--data-sources so the agent knows which fields
|
|
89
|
+
each partner should fill.
|
|
90
|
+
--wait Stream the run and wait for it to complete
|
|
91
|
+
--api-key KEY Exa API key (or set EXA_API_KEY env var)
|
|
92
|
+
--output-format FMT Output format: json, pretty, or text (default: json)
|
|
93
|
+
--help, -h Show this help message
|
|
94
|
+
|
|
95
|
+
Data sources (Exa Connect):
|
|
96
|
+
Each attached partner is billed per call on top of normal agent costs.
|
|
97
|
+
The agent picks the right source per step and cites everything.
|
|
98
|
+
|
|
99
|
+
fiber_ai GTM & recruiting. 40M+ companies, 850M+ people,
|
|
100
|
+
work/personal emails and phones. Use for B2B
|
|
101
|
+
prospecting, CRM enrichment, finding contacts.
|
|
102
|
+
similarweb Web analytics. Traffic estimates, global rankings,
|
|
103
|
+
and competitors for any domain. Use for traffic
|
|
104
|
+
benchmarking and market sizing.
|
|
105
|
+
baselayer Compliance & KYB. Verify US businesses: officers,
|
|
106
|
+
state registrations, risk scores. Use for KYB and
|
|
107
|
+
vendor/customer screening.
|
|
108
|
+
affiliate Commerce. Product catalog with live pricing,
|
|
109
|
+
brands, and merchant links. Use for price
|
|
110
|
+
comparison and shopping assistants.
|
|
111
|
+
particle_news Media intelligence. Podcast transcripts with
|
|
112
|
+
speaker attribution and timestamps. Use to find
|
|
113
|
+
who said what on podcasts.
|
|
114
|
+
financial_datasets Finance. Ticker-based news for US public
|
|
115
|
+
companies. Use to track news/earnings for stocks.
|
|
116
|
+
jinko Travel. Destinations ranked by lowest available
|
|
117
|
+
fare from your departure airports. Use for
|
|
118
|
+
budget trip ideas.
|
|
119
|
+
|
|
120
|
+
Examples:
|
|
121
|
+
exa-ai agent-run-create --query "Find Ruby performance tips"
|
|
122
|
+
exa-ai agent-run-create --query "Analyze AI trends" --wait
|
|
123
|
+
|
|
124
|
+
# Enrich a list of companies from a JSON file:
|
|
125
|
+
exa-ai agent-run-create --wait \\
|
|
126
|
+
--query "For each company, find the CEO and their LinkedIn URL." \\
|
|
127
|
+
--input-data @companies.json \\
|
|
128
|
+
--output-schema '{"type":"object","properties":{"companies":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"ceo":{"type":"string"},"linkedin":{"type":"string"}}}}}}'
|
|
129
|
+
|
|
130
|
+
# Company profile blending two partners + web, as structured JSON:
|
|
131
|
+
exa-ai agent-run-create --wait \\
|
|
132
|
+
--query "Profile Anthropic: total funding and monthly web traffic" \\
|
|
133
|
+
--data-sources fiber_ai,similarweb \\
|
|
134
|
+
--output-schema '{"type":"object","properties":{"name":{"type":"string"},"totalFunding":{"type":"string"},"monthlyVisits":{"type":"number"}}}'
|
|
135
|
+
|
|
136
|
+
# KYB check with a larger schema read from a file:
|
|
137
|
+
exa-ai agent-run-create --wait --query "Verify Stripe, Inc." \\
|
|
138
|
+
--data-sources baselayer --output-schema @schema.json
|
|
139
|
+
HELP
|
|
140
|
+
exit 0
|
|
141
|
+
else
|
|
142
|
+
i += 1
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
args
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Main execution
|
|
150
|
+
begin
|
|
151
|
+
args = parse_args(ARGV)
|
|
152
|
+
|
|
153
|
+
# Validate query
|
|
154
|
+
if args[:query].nil? || args[:query].empty?
|
|
155
|
+
$stderr.puts "Error: --query flag is required"
|
|
156
|
+
$stderr.puts "Run 'exa-ai agent-run-create --help' for usage information"
|
|
157
|
+
exit 1
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Resolve API key
|
|
161
|
+
api_key = Exa::CLI::Base.resolve_api_key(args[:api_key])
|
|
162
|
+
|
|
163
|
+
# Resolve output format
|
|
164
|
+
output_format = Exa::CLI::Base.resolve_output_format(args[:output_format])
|
|
165
|
+
|
|
166
|
+
# Build client
|
|
167
|
+
client = Exa::CLI::Base.build_client(api_key)
|
|
168
|
+
|
|
169
|
+
# Prepare run parameters
|
|
170
|
+
run_params = { query: args[:query] }
|
|
171
|
+
run_params[:system_prompt] = args[:system_prompt] if args[:system_prompt]
|
|
172
|
+
run_params[:effort] = args[:effort] if args[:effort]
|
|
173
|
+
run_params[:data_sources] = args[:data_sources] if args[:data_sources]
|
|
174
|
+
if args[:input_data] || args[:input_exclusion]
|
|
175
|
+
input = {}
|
|
176
|
+
input[:data] = args[:input_data] if args[:input_data]
|
|
177
|
+
input[:exclusion] = args[:input_exclusion] if args[:input_exclusion]
|
|
178
|
+
run_params[:input] = input
|
|
179
|
+
end
|
|
180
|
+
run_params[:previous_run_id] = args[:previous_run_id] if args[:previous_run_id]
|
|
181
|
+
run_params[:output_schema] = args[:output_schema] if args[:output_schema]
|
|
182
|
+
|
|
183
|
+
if args[:wait]
|
|
184
|
+
# Stream the run and watch it to completion. The SSE stream creates the run
|
|
185
|
+
# and pushes lifecycle events (created/started/completed); the final run is
|
|
186
|
+
# rendered the instant it lands — lower latency than polling, and the run
|
|
187
|
+
# still persists with an id.
|
|
188
|
+
start = Time.now
|
|
189
|
+
run_id = nil
|
|
190
|
+
phase = "starting"
|
|
191
|
+
done = false
|
|
192
|
+
|
|
193
|
+
# ponytail: the SSE block only fires on events, and nothing arrives between
|
|
194
|
+
# "started" and "completed". A background thread paints a live elapsed timer
|
|
195
|
+
# so the user can see it's alive during the silent running phase.
|
|
196
|
+
painter = Thread.new do
|
|
197
|
+
frames = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏]
|
|
198
|
+
i = 0
|
|
199
|
+
until done
|
|
200
|
+
elapsed = (Time.now - start).round
|
|
201
|
+
$stderr.print "\r#{frames[i % frames.size]} #{run_id || "agent run"} #{phase} #{elapsed}s "
|
|
202
|
+
$stderr.flush
|
|
203
|
+
i += 1
|
|
204
|
+
sleep 0.2
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
final = nil
|
|
209
|
+
stream_error = nil
|
|
210
|
+
begin
|
|
211
|
+
final = client.agent_run_stream(**run_params) do |event, data|
|
|
212
|
+
run_id ||= data["id"] if data.is_a?(Hash)
|
|
213
|
+
phase = "queued" if event == "agent_run.created"
|
|
214
|
+
phase = "running" if event == "agent_run.started"
|
|
215
|
+
end
|
|
216
|
+
rescue Faraday::Error, Exa::Error => e
|
|
217
|
+
stream_error = e # stream dropped mid-run; recover via polling below if we have an id
|
|
218
|
+
ensure
|
|
219
|
+
done = true
|
|
220
|
+
painter.join
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Fallback: stream ended/dropped without a terminal event. If we learned the
|
|
224
|
+
# run id, poll it to completion rather than losing the run.
|
|
225
|
+
if final.nil? && run_id
|
|
226
|
+
$stderr.print "\r(stream interrupted; polling #{run_id}) "
|
|
227
|
+
begin
|
|
228
|
+
final = Exa::CLI::Polling.poll(max_duration: 300, initial_delay: 2, max_delay: 10) do
|
|
229
|
+
current = client.agent_run_get(run_id)
|
|
230
|
+
{ done: current.finished?, result: current, status: current.status }
|
|
231
|
+
end
|
|
232
|
+
rescue Exa::CLI::Polling::TimeoutError
|
|
233
|
+
$stderr.puts "\nError: run did not complete within 5 minutes"
|
|
234
|
+
$stderr.puts "Run ID: #{run_id} — check later with: exa-ai agent-run-get #{run_id}"
|
|
235
|
+
exit 1
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
$stderr.print "\r" + (" " * 72) + "\r"
|
|
240
|
+
|
|
241
|
+
if final.nil?
|
|
242
|
+
$stderr.puts "Error: the run did not produce a result#{stream_error ? " (#{stream_error.message})" : ""}"
|
|
243
|
+
$stderr.puts "Run ID: #{run_id}" if run_id
|
|
244
|
+
exit 1
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
$stderr.puts "#{final.completed? ? "✓" : "•"} #{final.status} · #{(Time.now - start).round}s"
|
|
248
|
+
puts Exa::CLI::Formatters::AgentRunFormatter.format_run(final, output_format)
|
|
249
|
+
$stdout.flush
|
|
250
|
+
exit 1 if final.failed?
|
|
251
|
+
else
|
|
252
|
+
# Not waiting: create and return the initial (queued) run.
|
|
253
|
+
run = client.agent_run_create(**run_params)
|
|
254
|
+
puts Exa::CLI::Formatters::AgentRunFormatter.format_run(run, output_format)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
rescue Exa::ConfigurationError => e
|
|
258
|
+
$stderr.puts "Configuration error: #{e.message}"
|
|
259
|
+
exit 1
|
|
260
|
+
rescue Exa::Unauthorized => e
|
|
261
|
+
$stderr.puts "Authentication error: #{e.message}"
|
|
262
|
+
$stderr.puts "Check your API key (set EXA_API_KEY or use --api-key)"
|
|
263
|
+
exit 1
|
|
264
|
+
rescue Exa::ClientError => e
|
|
265
|
+
$stderr.puts "Client error: #{e.message}"
|
|
266
|
+
exit 1
|
|
267
|
+
rescue Exa::ServerError => e
|
|
268
|
+
$stderr.puts "Server error: #{e.message}"
|
|
269
|
+
$stderr.puts "The Exa API may be experiencing issues. Please try again later."
|
|
270
|
+
exit 1
|
|
271
|
+
rescue Exa::Error => e
|
|
272
|
+
$stderr.puts "Error: #{e.message}"
|
|
273
|
+
exit 1
|
|
274
|
+
rescue StandardError => e
|
|
275
|
+
$stderr.puts "Unexpected error: #{e.message}"
|
|
276
|
+
$stderr.puts e.backtrace.first(5) if ENV["DEBUG"]
|
|
277
|
+
exit 1
|
|
278
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "exa-ai"
|
|
5
|
+
|
|
6
|
+
# Parse command-line arguments
|
|
7
|
+
api_key = nil
|
|
8
|
+
run_id = nil
|
|
9
|
+
output_format = "json"
|
|
10
|
+
|
|
11
|
+
args = ARGV.dup
|
|
12
|
+
while args.any?
|
|
13
|
+
arg = args.shift
|
|
14
|
+
case arg
|
|
15
|
+
when "--api-key"
|
|
16
|
+
api_key = args.shift
|
|
17
|
+
when "--output-format"
|
|
18
|
+
output_format = args.shift
|
|
19
|
+
when "--help", "-h"
|
|
20
|
+
puts <<~HELP
|
|
21
|
+
Usage: exa-ai agent-run-delete <run_id> [options]
|
|
22
|
+
|
|
23
|
+
Delete an agent run.
|
|
24
|
+
|
|
25
|
+
Arguments:
|
|
26
|
+
run_id ID of the agent run to delete
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
--api-key KEY Exa API key (or use EXA_API_KEY env var)
|
|
30
|
+
--output-format FORMAT Output format: json, pretty, or text (default: json)
|
|
31
|
+
--help, -h Show this help message
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
exa-ai agent-run-delete run_123
|
|
35
|
+
exa-ai agent-run-delete run_123 --output-format pretty
|
|
36
|
+
HELP
|
|
37
|
+
exit 0
|
|
38
|
+
else
|
|
39
|
+
# First positional argument is the run_id
|
|
40
|
+
if run_id.nil?
|
|
41
|
+
run_id = arg
|
|
42
|
+
else
|
|
43
|
+
warn "Unknown option: #{arg}"
|
|
44
|
+
exit 1
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Validate required arguments
|
|
50
|
+
if run_id.nil?
|
|
51
|
+
warn "Error: Run ID argument required"
|
|
52
|
+
warn "Usage: exa-ai agent-run-delete <run_id> [options]"
|
|
53
|
+
warn "Try 'exa-ai agent-run-delete --help' for more information"
|
|
54
|
+
exit 1
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
begin
|
|
58
|
+
# Resolve API key
|
|
59
|
+
api_key = Exa::CLI::Base.resolve_api_key(api_key)
|
|
60
|
+
|
|
61
|
+
# Build client
|
|
62
|
+
client = Exa::CLI::Base.build_client(api_key)
|
|
63
|
+
|
|
64
|
+
# Call API
|
|
65
|
+
result = client.agent_run_delete(run_id)
|
|
66
|
+
|
|
67
|
+
# Format output
|
|
68
|
+
if result.respond_to?(:to_h)
|
|
69
|
+
formatted = Exa::CLI::Formatters::AgentRunFormatter.format_run(result, output_format)
|
|
70
|
+
puts formatted
|
|
71
|
+
else
|
|
72
|
+
puts({ deleted: true, id: run_id }.to_json) if output_format == "json"
|
|
73
|
+
puts "Deleted agent run: #{run_id}" if output_format == "pretty"
|
|
74
|
+
puts run_id if output_format == "text"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
rescue Exa::NotFound => e
|
|
78
|
+
warn "Agent run not found: #{e.message}"
|
|
79
|
+
exit 1
|
|
80
|
+
rescue Exa::Unauthorized => e
|
|
81
|
+
warn "Authentication failed: #{e.message}"
|
|
82
|
+
warn "Please provide a valid API key via --api-key or EXA_API_KEY environment variable"
|
|
83
|
+
exit 1
|
|
84
|
+
rescue Exa::ClientError => e
|
|
85
|
+
warn "Client error: #{e.message}"
|
|
86
|
+
exit 1
|
|
87
|
+
rescue Exa::ServerError => e
|
|
88
|
+
warn "Server error: #{e.message}"
|
|
89
|
+
exit 1
|
|
90
|
+
rescue Exa::Error => e
|
|
91
|
+
warn "Error: #{e.message}"
|
|
92
|
+
exit 1
|
|
93
|
+
rescue StandardError => e
|
|
94
|
+
warn "Unexpected error: #{e.message}"
|
|
95
|
+
exit 1
|
|
96
|
+
end
|