exa-ai-ruby 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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +4 -0
  3. data/LICENSE +21 -0
  4. data/README.md +247 -0
  5. data/lib/exa/client.rb +33 -0
  6. data/lib/exa/errors.rb +34 -0
  7. data/lib/exa/internal/transport/base_client.rb +171 -0
  8. data/lib/exa/internal/transport/pooled_net_requester.rb +113 -0
  9. data/lib/exa/internal/transport/stream.rb +74 -0
  10. data/lib/exa/internal/util.rb +133 -0
  11. data/lib/exa/resources/base.rb +26 -0
  12. data/lib/exa/resources/events.rb +32 -0
  13. data/lib/exa/resources/imports.rb +58 -0
  14. data/lib/exa/resources/research.rb +50 -0
  15. data/lib/exa/resources/search.rb +44 -0
  16. data/lib/exa/resources/webhooks.rb +67 -0
  17. data/lib/exa/resources/websets/enrichments.rb +57 -0
  18. data/lib/exa/resources/websets/items.rb +40 -0
  19. data/lib/exa/resources/websets/monitors.rb +75 -0
  20. data/lib/exa/resources/websets.rb +71 -0
  21. data/lib/exa/resources.rb +9 -0
  22. data/lib/exa/responses/contents_response.rb +35 -0
  23. data/lib/exa/responses/event_response.rb +43 -0
  24. data/lib/exa/responses/helpers.rb +29 -0
  25. data/lib/exa/responses/import_response.rb +90 -0
  26. data/lib/exa/responses/monitor_response.rb +77 -0
  27. data/lib/exa/responses/raw_response.rb +14 -0
  28. data/lib/exa/responses/research_response.rb +56 -0
  29. data/lib/exa/responses/result.rb +61 -0
  30. data/lib/exa/responses/search_response.rb +43 -0
  31. data/lib/exa/responses/webhook_response.rb +95 -0
  32. data/lib/exa/responses/webset_response.rb +136 -0
  33. data/lib/exa/responses.rb +13 -0
  34. data/lib/exa/types/answer.rb +30 -0
  35. data/lib/exa/types/base.rb +66 -0
  36. data/lib/exa/types/contents.rb +25 -0
  37. data/lib/exa/types/enums.rb +47 -0
  38. data/lib/exa/types/find_similar.rb +26 -0
  39. data/lib/exa/types/research.rb +18 -0
  40. data/lib/exa/types/schema.rb +58 -0
  41. data/lib/exa/types/search.rb +74 -0
  42. data/lib/exa/types.rb +10 -0
  43. data/lib/exa/version.rb +5 -0
  44. data/lib/exa.rb +14 -0
  45. metadata +170 -0
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exa
4
+ module Responses
5
+ class Webset < T::Struct
6
+ const :id, String
7
+ const :object, T.nilable(String)
8
+ const :status, T.nilable(String)
9
+ const :external_id, T.nilable(String)
10
+ const :title, T.nilable(String)
11
+ const :searches, T::Array[T.untyped]
12
+ const :imports, T::Array[T.untyped]
13
+ const :enrichments, T::Array[T.untyped]
14
+ const :monitors, T::Array[T.untyped]
15
+ const :streams, T::Array[T.untyped]
16
+ const :metadata, T.nilable(T::Hash[String, String])
17
+ const :created_at, T.nilable(String)
18
+ const :updated_at, T.nilable(String)
19
+ const :raw, T::Hash[Symbol, T.untyped]
20
+
21
+ def self.from_hash(hash)
22
+ sym = Helpers.symbolize_keys(hash)
23
+ new(
24
+ id: sym[:id],
25
+ object: sym[:object],
26
+ status: sym[:status],
27
+ external_id: sym[:externalId],
28
+ title: sym[:title],
29
+ searches: Array(sym[:searches]),
30
+ imports: Array(sym[:imports]),
31
+ enrichments: Array(sym[:enrichments]),
32
+ monitors: Array(sym[:monitors]),
33
+ streams: Array(sym[:streams]),
34
+ metadata: Helpers.stringify_string_hash(sym[:metadata]),
35
+ created_at: sym[:createdAt],
36
+ updated_at: sym[:updatedAt],
37
+ raw: sym
38
+ )
39
+ end
40
+ end
41
+
42
+ class WebsetListResponse < T::Struct
43
+ const :data, T::Array[Webset]
44
+ const :has_more, T.nilable(T::Boolean)
45
+ const :next_cursor, T.nilable(String)
46
+
47
+ def self.from_hash(hash)
48
+ sym = Helpers.symbolize_keys(hash)
49
+ new(
50
+ data: Array(sym[:data]).map { Webset.from_hash(_1) },
51
+ has_more: sym[:hasMore],
52
+ next_cursor: sym[:nextCursor]
53
+ )
54
+ end
55
+ end
56
+
57
+ class WebsetItem < T::Struct
58
+ const :id, String
59
+ const :object, T.nilable(String)
60
+ const :source, T.nilable(String)
61
+ const :source_id, T.nilable(String)
62
+ const :webset_id, T.nilable(String)
63
+ const :properties, T.nilable(T::Hash[Symbol, T.untyped])
64
+ const :evaluations, T::Array[T.untyped]
65
+ const :enrichments, T.nilable(T::Array[T.untyped])
66
+ const :created_at, T.nilable(String)
67
+ const :updated_at, T.nilable(String)
68
+ const :raw, T::Hash[Symbol, T.untyped]
69
+
70
+ def self.from_hash(hash)
71
+ sym = Helpers.symbolize_keys(hash)
72
+ new(
73
+ id: sym[:id],
74
+ object: sym[:object],
75
+ source: sym[:source],
76
+ source_id: sym[:sourceId],
77
+ webset_id: sym[:websetId],
78
+ properties: sym[:properties].is_a?(Hash) ? Helpers.symbolize_keys(sym[:properties]) : sym[:properties],
79
+ evaluations: Array(sym[:evaluations]),
80
+ enrichments: sym[:enrichments]&.map { Helpers.symbolize_keys(_1) },
81
+ created_at: sym[:createdAt],
82
+ updated_at: sym[:updatedAt],
83
+ raw: sym
84
+ )
85
+ end
86
+ end
87
+
88
+ class WebsetItemListResponse < T::Struct
89
+ const :data, T::Array[WebsetItem]
90
+ const :has_more, T.nilable(T::Boolean)
91
+ const :next_cursor, T.nilable(String)
92
+
93
+ def self.from_hash(hash)
94
+ sym = Helpers.symbolize_keys(hash)
95
+ new(
96
+ data: Array(sym[:data]).map { WebsetItem.from_hash(_1) },
97
+ has_more: sym[:hasMore],
98
+ next_cursor: sym[:nextCursor]
99
+ )
100
+ end
101
+ end
102
+
103
+ class WebsetEnrichment < T::Struct
104
+ const :id, String
105
+ const :object, T.nilable(String)
106
+ const :status, T.nilable(String)
107
+ const :webset_id, T.nilable(String)
108
+ const :title, T.nilable(String)
109
+ const :description, T.nilable(String)
110
+ const :format, T.nilable(String)
111
+ const :options, T.nilable(T::Array[T::Hash[Symbol, T.untyped]])
112
+ const :metadata, T.nilable(T::Hash[String, String])
113
+ const :created_at, T.nilable(String)
114
+ const :updated_at, T.nilable(String)
115
+ const :raw, T::Hash[Symbol, T.untyped]
116
+
117
+ def self.from_hash(hash)
118
+ sym = Helpers.symbolize_keys(hash)
119
+ new(
120
+ id: sym[:id],
121
+ object: sym[:object],
122
+ status: sym[:status],
123
+ webset_id: sym[:websetId],
124
+ title: sym[:title],
125
+ description: sym[:description],
126
+ format: sym[:format],
127
+ options: sym[:options]&.map { Helpers.symbolize_keys(_1) },
128
+ metadata: Helpers.stringify_string_hash(sym[:metadata]),
129
+ created_at: sym[:createdAt],
130
+ updated_at: sym[:updatedAt],
131
+ raw: sym
132
+ )
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "responses/helpers"
4
+ require_relative "responses/result"
5
+ require_relative "responses/search_response"
6
+ require_relative "responses/contents_response"
7
+ require_relative "responses/monitor_response"
8
+ require_relative "responses/event_response"
9
+ require_relative "responses/webhook_response"
10
+ require_relative "responses/import_response"
11
+ require_relative "responses/webset_response"
12
+ require_relative "responses/research_response"
13
+ require_relative "responses/raw_response"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exa
4
+ module Types
5
+ class AnswerSearchOptions < T::Struct
6
+ include StructWrapper
7
+ include SearchOptionProps
8
+ end
9
+
10
+ class AnswerSummaryOptions < T::Struct
11
+ include StructWrapper
12
+ const :schema, T.nilable(T.untyped)
13
+ end
14
+
15
+ class AnswerRequest < T::Struct
16
+ include StructWrapper
17
+ const :query, String
18
+ const :summary, T.nilable(AnswerSummaryOptions)
19
+ const :search_options, T.nilable(AnswerSearchOptions)
20
+
21
+ def to_payload
22
+ payload = Serializer.to_payload(self)
23
+ if payload["searchOptions"].is_a?(Hash)
24
+ payload["searchOptions"] = payload["searchOptions"].merge("query" => query)
25
+ end
26
+ payload
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exa
4
+ module Types
5
+ module Serializer
6
+ module_function
7
+
8
+ def to_payload(value)
9
+ schema_payload = Exa::Types::Schema.maybe_convert(value)
10
+ return schema_payload unless schema_payload.nil?
11
+
12
+ case value
13
+ when StructWrapper
14
+ value.__exa_attributes__
15
+ when ::T::Struct
16
+ serialize_struct(value)
17
+ when Hash
18
+ serialize_hash(value)
19
+ when Array
20
+ value.map { to_payload(_1) }
21
+ when Symbol
22
+ value.to_s
23
+ when T::Enum
24
+ value.serialize.to_s
25
+ else
26
+ value
27
+ end
28
+ end
29
+
30
+ def serialize_struct(struct)
31
+ struct.serialize.each_with_object({}) do |(key, val), acc|
32
+ next if val.nil?
33
+ method_name = key.to_sym
34
+ raw_value = struct.respond_to?(method_name) ? struct.public_send(method_name) : nil
35
+ schema_value = Exa::Types::Schema.maybe_convert(raw_value)
36
+ acc[camelize(method_name)] = schema_value.nil? ? to_payload(val) : schema_value
37
+ end
38
+ end
39
+
40
+ def serialize_hash(hash)
41
+ hash.each_with_object({}) do |(key, val), acc|
42
+ next if val.nil?
43
+ acc[camelize(key)] = to_payload(val)
44
+ end
45
+ end
46
+
47
+ def camelize(sym)
48
+ key = sym.to_s
49
+ return key if key.include?("$")
50
+ parts = key.split("_")
51
+ return key if parts.length == 1
52
+ parts.first + parts[1..].map(&:capitalize).join
53
+ end
54
+ end
55
+
56
+ module StructWrapper
57
+ def to_payload
58
+ Serializer.to_payload(self)
59
+ end
60
+
61
+ def __exa_attributes__
62
+ Serializer.serialize_struct(self)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exa
4
+ module Types
5
+ class ContentsRequest < T::Struct
6
+ include StructWrapper
7
+ const :urls, T::Array[String]
8
+ const :text, T.nilable(T.any(T::Boolean, TextContentsOptions))
9
+ const :highlights, T.nilable(T.any(T::Boolean, HighlightsContentsOptions))
10
+ const :summary, T.nilable(T.any(T::Boolean, SummaryContentsOptions))
11
+ const :context, T.nilable(T.any(T::Boolean, ContextOptions))
12
+ const :metadata, T.nilable(T::Boolean)
13
+ const :livecrawl_timeout, T.nilable(Integer)
14
+ const :livecrawl, T.nilable(LivecrawlMode)
15
+ const :filter_empty_results, T.nilable(T::Boolean)
16
+ const :subpages, T.nilable(Integer)
17
+ const :subpage_target, T.nilable(T.any(String, T::Array[String]))
18
+ const :extras, T.nilable(ExtrasOptions)
19
+
20
+ def to_payload
21
+ Serializer.to_payload(self)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exa
4
+ module Types
5
+ class SearchType < T::Enum
6
+ enums do
7
+ Keyword = new(:keyword)
8
+ Neural = new(:neural)
9
+ Hybrid = new(:hybrid)
10
+ Fast = new(:fast)
11
+ Deep = new(:deep)
12
+ Auto = new(:auto)
13
+ end
14
+ end
15
+
16
+ class Category < T::Enum
17
+ enums do
18
+ Company = new(:company)
19
+ ResearchPaper = new(:research_paper)
20
+ News = new(:news)
21
+ Pdf = new(:pdf)
22
+ Github = new(:github)
23
+ Tweet = new(:tweet)
24
+ PersonalSite = new(:personal_site)
25
+ LinkedinProfile = new(:linkedin_profile)
26
+ FinancialReport = new(:financial_report)
27
+ end
28
+ end
29
+
30
+ class LivecrawlMode < T::Enum
31
+ enums do
32
+ Never = new(:never)
33
+ Fallback = new(:fallback)
34
+ Always = new(:always)
35
+ Auto = new(:auto)
36
+ Preferred = new(:preferred)
37
+ end
38
+ end
39
+
40
+ class ResearchModel < T::Enum
41
+ enums do
42
+ Fast = new(:"exa-research-fast")
43
+ Standard = new(:"exa-research-standard")
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exa
4
+ module Types
5
+ class FindSimilarRequest < T::Struct
6
+ include StructWrapper
7
+ const :url, String
8
+ const :num_results, T.nilable(Integer)
9
+ const :include_domains, T.nilable(T::Array[String])
10
+ const :exclude_domains, T.nilable(T::Array[String])
11
+ const :start_crawl_date, T.nilable(String)
12
+ const :end_crawl_date, T.nilable(String)
13
+ const :start_published_date, T.nilable(String)
14
+ const :end_published_date, T.nilable(String)
15
+ const :include_text, T.nilable(T::Array[String])
16
+ const :exclude_text, T.nilable(T::Array[String])
17
+ const :exclude_source_domain, T.nilable(T::Boolean)
18
+ const :category, T.nilable(Category)
19
+ const :flags, T.nilable(T::Array[String])
20
+
21
+ def to_payload
22
+ Serializer.to_payload(self)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exa
4
+ module Types
5
+ class ResearchCreateRequest < T::Struct
6
+ include StructWrapper
7
+ const :instructions, String
8
+ const :model, T.nilable(ResearchModel)
9
+ const :output_schema, T.nilable(T.untyped)
10
+ const :events, T.nilable(T::Boolean)
11
+ const :stream, T.nilable(T::Boolean)
12
+
13
+ def to_payload
14
+ Serializer.to_payload(self)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module Exa
6
+ module Types
7
+ module Schema
8
+ module_function
9
+
10
+ HAS_DSPY_SCHEMA = begin
11
+ require "dspy/schema"
12
+ true
13
+ rescue LoadError
14
+ false
15
+ end
16
+
17
+ def to_json_schema(type)
18
+ ensure_converter!
19
+ DSPy::TypeSystem::SorbetJsonSchema.type_to_json_schema(type)
20
+ end
21
+
22
+ def maybe_convert(value)
23
+ return nil if value.nil?
24
+ return value if value.is_a?(Hash)
25
+ return nil unless HAS_DSPY_SCHEMA
26
+
27
+ if struct_class?(value)
28
+ to_json_schema(value)
29
+ elsif type_alias?(value)
30
+ to_json_schema(value)
31
+ elsif sorbet_type_object?(value)
32
+ to_json_schema(value)
33
+ else
34
+ nil
35
+ end
36
+ end
37
+
38
+ def struct_class?(value)
39
+ value.is_a?(Class) && !value.name.nil? && (value < T::Struct || value < T::Enum)
40
+ end
41
+
42
+ def type_alias?(value)
43
+ defined?(T::Private::Types::TypeAlias) && value.is_a?(T::Private::Types::TypeAlias)
44
+ end
45
+
46
+ def sorbet_type_object?(value)
47
+ defined?(T::Types::Base) && value.is_a?(T::Types::Base)
48
+ end
49
+
50
+ def ensure_converter!
51
+ return if HAS_DSPY_SCHEMA
52
+
53
+ raise NotImplementedError,
54
+ "Sorbet JSON Schema converter not available. Install dspy-schema to enable this feature."
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exa
4
+ module Types
5
+ module SearchOptionProps
6
+ def self.included(base)
7
+ base.const :num_results, T.nilable(Integer)
8
+ base.const :include_domains, T.nilable(T::Array[String])
9
+ base.const :exclude_domains, T.nilable(T::Array[String])
10
+ base.const :start_crawl_date, T.nilable(String)
11
+ base.const :end_crawl_date, T.nilable(String)
12
+ base.const :start_published_date, T.nilable(String)
13
+ base.const :end_published_date, T.nilable(String)
14
+ base.const :include_text, T.nilable(T::Array[String])
15
+ base.const :exclude_text, T.nilable(T::Array[String])
16
+ base.const :use_autoprompt, T.nilable(T::Boolean)
17
+ base.const :type, T.nilable(SearchType)
18
+ base.const :category, T.nilable(Category)
19
+ base.const :flags, T.nilable(T::Array[String])
20
+ base.const :moderation, T.nilable(T::Boolean)
21
+ base.const :user_location, T.nilable(String)
22
+ base.const :livecrawl, T.nilable(LivecrawlMode)
23
+ base.const :livecrawl_timeout, T.nilable(Integer)
24
+ base.const :subpages, T.nilable(Integer)
25
+ base.const :subpage_target, T.nilable(T.any(String, T::Array[String]))
26
+ base.const :extras, T.nilable(ExtrasOptions)
27
+ base.const :text, T.nilable(T.any(T::Boolean, TextContentsOptions))
28
+ base.const :highlights, T.nilable(T.any(T::Boolean, HighlightsContentsOptions))
29
+ base.const :summary, T.nilable(T.any(T::Boolean, SummaryContentsOptions))
30
+ base.const :context, T.nilable(T.any(T::Boolean, ContextOptions))
31
+ end
32
+ end
33
+
34
+ class TextContentsOptions < T::Struct
35
+ include StructWrapper
36
+ const :max_characters, T.nilable(Integer)
37
+ const :include_html_tags, T.nilable(T::Boolean)
38
+ end
39
+
40
+ class HighlightsContentsOptions < T::Struct
41
+ include StructWrapper
42
+ const :query, T.nilable(String)
43
+ const :num_sentences, T.nilable(Integer)
44
+ const :highlights_per_url, T.nilable(Integer)
45
+ end
46
+
47
+ class SummaryContentsOptions < T::Struct
48
+ include StructWrapper
49
+ const :query, T.nilable(String)
50
+ const :schema, T.nilable(T.untyped)
51
+ end
52
+
53
+ class ContextOptions < T::Struct
54
+ include StructWrapper
55
+ const :max_characters, T.nilable(Integer)
56
+ end
57
+
58
+ class ExtrasOptions < T::Struct
59
+ include StructWrapper
60
+ const :links, T.nilable(Integer)
61
+ const :image_links, T.nilable(Integer)
62
+ end
63
+
64
+ class SearchRequest < T::Struct
65
+ include StructWrapper
66
+ include SearchOptionProps
67
+ const :query, String
68
+
69
+ def to_payload
70
+ Serializer.to_payload(self)
71
+ end
72
+ end
73
+ end
74
+ end
data/lib/exa/types.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "types/base"
4
+ require_relative "types/enums"
5
+ require_relative "types/search"
6
+ require_relative "types/find_similar"
7
+ require_relative "types/contents"
8
+ require_relative "types/answer"
9
+ require_relative "types/research"
10
+ require_relative "types/schema"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exa
4
+ VERSION = "1.0.0"
5
+ end
data/lib/exa.rb ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sorbet-runtime"
4
+
5
+ require_relative "exa/version"
6
+ require_relative "exa/errors"
7
+ require_relative "exa/internal/util"
8
+ require_relative "exa/internal/transport/pooled_net_requester"
9
+ require_relative "exa/internal/transport/base_client"
10
+ require_relative "exa/internal/transport/stream"
11
+ require_relative "exa/client"
12
+ require_relative "exa/types"
13
+ require_relative "exa/responses"
14
+ require_relative "exa/resources"