rager 0.5.0 → 0.6.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +50 -7
  3. data/lib/rager/chat/options.rb +9 -5
  4. data/lib/rager/chat/providers/openai.rb +27 -26
  5. data/lib/rager/chat/schema.rb +3 -2
  6. data/lib/rager/config.rb +5 -1
  7. data/lib/rager/context.rb +168 -55
  8. data/lib/rager/embed/options.rb +1 -0
  9. data/lib/rager/embed/providers/openai.rb +8 -4
  10. data/lib/rager/errors/credentials_error.rb +24 -0
  11. data/lib/rager/errors/dependency_error.rb +23 -0
  12. data/lib/rager/errors/http_error.rb +12 -5
  13. data/lib/rager/errors/options_error.rb +10 -5
  14. data/lib/rager/errors/parse_error.rb +9 -5
  15. data/lib/rager/errors/template_error.rb +10 -4
  16. data/lib/rager/errors/timeout_error.rb +25 -0
  17. data/lib/rager/http/adapters/async_http.rb +67 -13
  18. data/lib/rager/http/adapters/mock.rb +43 -45
  19. data/lib/rager/http/adapters/net_http.rb +145 -0
  20. data/lib/rager/http/request.rb +2 -0
  21. data/lib/rager/image_gen/options.rb +1 -0
  22. data/lib/rager/image_gen/providers/replicate.rb +5 -4
  23. data/lib/rager/mesh_gen/options.rb +1 -0
  24. data/lib/rager/mesh_gen/providers/replicate.rb +7 -5
  25. data/lib/rager/providers.rb +49 -0
  26. data/lib/rager/rerank/options.rb +1 -0
  27. data/lib/rager/rerank/{query.rb → output.rb} +2 -2
  28. data/lib/rager/rerank/providers/abstract.rb +3 -2
  29. data/lib/rager/rerank/providers/cohere.rb +17 -14
  30. data/lib/rager/result.rb +49 -29
  31. data/lib/rager/search/options.rb +3 -1
  32. data/lib/rager/search/output.rb +14 -0
  33. data/lib/rager/search/providers/jina.rb +67 -0
  34. data/lib/rager/template/providers/abstract.rb +3 -2
  35. data/lib/rager/template/providers/erb.rb +6 -5
  36. data/lib/rager/types.rb +11 -7
  37. data/lib/rager/utils/http.rb +35 -28
  38. data/lib/rager/utils/replicate.rb +13 -16
  39. data/lib/rager/version.rb +1 -1
  40. metadata +10 -30
  41. data/lib/rager/chat.rb +0 -35
  42. data/lib/rager/embed.rb +0 -35
  43. data/lib/rager/errors/missing_credentials_error.rb +0 -19
  44. data/lib/rager/errors/unknown_provider_error.rb +0 -17
  45. data/lib/rager/image_gen.rb +0 -31
  46. data/lib/rager/mesh_gen.rb +0 -31
  47. data/lib/rager/rerank/result.rb +0 -13
  48. data/lib/rager/rerank.rb +0 -35
  49. data/lib/rager/search/providers/brave.rb +0 -59
  50. data/lib/rager/search/result.rb +0 -14
  51. data/lib/rager/search.rb +0 -35
  52. data/lib/rager/template/input.rb +0 -11
  53. data/lib/rager/template.rb +0 -35
@@ -1,9 +1,10 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
5
4
  require "json"
6
5
 
6
+ require "sorbet-runtime"
7
+
7
8
  module Rager
8
9
  module MeshGen
9
10
  module Providers
@@ -13,7 +14,7 @@ module Rager
13
14
  sig { override.params(image_url: String, options: Rager::MeshGen::Options).returns(Rager::Types::MeshGenOutput) }
14
15
  def mesh_gen(image_url, options)
15
16
  api_key = options.api_key || ENV["REPLICATE_API_KEY"]
16
- raise Rager::Errors::MissingCredentialsError.new("Replicate", "REPLICATE_API_KEY") if api_key.nil?
17
+ raise Rager::Errors::CredentialsError.new("Replicate", env_var: ["REPLICATE_API_KEY"]) if api_key.nil?
17
18
 
18
19
  body = {
19
20
  version: options.version,
@@ -36,18 +37,19 @@ module Rager
36
37
  "Authorization" => "Bearer #{api_key}",
37
38
  "Content-Type" => "application/json"
38
39
  },
39
- body: body.to_json
40
+ body: body.to_json,
41
+ timeout: options.timeout || Rager.config.timeout
40
42
  )
41
43
 
42
44
  response = Rager.config.http_adapter.make_request(request)
43
45
  response_body = T.cast(T.must(response.body), String)
44
46
 
45
- raise Rager::Errors::HttpError.new(Rager.config.http_adapter, response.status, response_body) unless [200, 201, 202].include?(response.status)
47
+ raise Rager::Errors::HttpError.new(Rager.config.http_adapter, request.url, response.status, body: response_body) unless [200, 201, 202].include?(response.status)
46
48
 
47
49
  json = JSON.parse(response_body)
48
50
  json.fetch("urls").fetch("get")
49
51
  rescue JSON::ParserError, KeyError => e
50
- raise Rager::Errors::ParseError.new(e.message, response_body)
52
+ raise Rager::Errors::ParseError.new(response_body || "", details: e.message)
51
53
  end
52
54
  end
53
55
  end
@@ -0,0 +1,49 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Rager
7
+ module Providers
8
+ extend T::Sig
9
+
10
+ PROVIDERS = T.let({
11
+ chat: {
12
+ "openai" => -> { Rager::Chat::Providers::Openai.new }
13
+ },
14
+ embed: {
15
+ "openai" => -> { Rager::Embed::Providers::Openai.new }
16
+ },
17
+ rerank: {
18
+ "cohere" => -> { Rager::Rerank::Providers::Cohere.new }
19
+ },
20
+ image_gen: {
21
+ "replicate" => -> { Rager::ImageGen::Providers::Replicate.new }
22
+ },
23
+ mesh_gen: {
24
+ "replicate" => -> { Rager::MeshGen::Providers::Replicate.new }
25
+ },
26
+ template: {
27
+ "erb" => -> { Rager::Template::Providers::Erb.new }
28
+ },
29
+ search: {
30
+ "jina" => -> { Rager::Search::Providers::Jina.new }
31
+ }
32
+ }.freeze, T::Hash[Symbol, T::Hash[String, T.proc.returns(T.untyped)]])
33
+
34
+ sig { params(operation: Symbol, provider_name: String, options: Rager::Options).returns(T.untyped) }
35
+ def self.get_provider(operation, provider_name, options)
36
+ operation_providers = PROVIDERS[operation]
37
+ if operation_providers&.key?(provider_name.downcase)
38
+ return T.must(operation_providers[provider_name.downcase]).call
39
+ end
40
+
41
+ known_providers = operation_providers&.keys&.join(", ") || "none"
42
+ raise Rager::Errors::OptionsError.new(
43
+ options,
44
+ ["provider"],
45
+ details: "Unknown #{operation} provider: #{provider_name}. Known providers: #{known_providers}"
46
+ )
47
+ end
48
+ end
49
+ end
@@ -14,6 +14,7 @@ module Rager
14
14
  const :model, T.nilable(String)
15
15
  const :n, T.nilable(Integer)
16
16
  const :api_key, T.nilable(String)
17
+ const :timeout, T.nilable(Numeric)
17
18
  end
18
19
  end
19
20
  end
@@ -5,8 +5,8 @@ require "sorbet-runtime"
5
5
 
6
6
  module Rager
7
7
  module Rerank
8
- class Query < T::Struct
9
- const :query, String
8
+ class Output < T::Struct
9
+ const :scores, T::Array[Float]
10
10
  const :documents, T::Array[String]
11
11
  end
12
12
  end
@@ -13,11 +13,12 @@ module Rager
13
13
 
14
14
  sig do
15
15
  abstract.params(
16
- query: Rager::Rerank::Query,
16
+ query: String,
17
+ documents: T::Array[String],
17
18
  options: Rager::Rerank::Options
18
19
  ).returns(Rager::Types::RerankOutput)
19
20
  end
20
- def rerank(query, options)
21
+ def rerank(query, documents, options)
21
22
  end
22
23
  end
23
24
  end
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "json"
5
+
5
6
  require "sorbet-runtime"
6
7
 
7
8
  module Rager
@@ -12,18 +13,21 @@ module Rager
12
13
 
13
14
  sig do
14
15
  override.params(
15
- query: Rager::Rerank::Query,
16
+ query: String,
17
+ documents: T::Array[String],
16
18
  options: Rager::Rerank::Options
17
19
  ).returns(Rager::Types::RerankOutput)
18
20
  end
19
- def rerank(query, options)
21
+ def rerank(query, documents, options)
20
22
  api_key = options.api_key || ENV["COHERE_API_KEY"]
21
- raise Rager::Errors::MissingCredentialsError.new("Cohere", "COHERE_API_KEY") if api_key.nil?
23
+ raise Rager::Errors::CredentialsError.new("Cohere", env_var: ["COHERE_API_KEY"]) if api_key.nil?
24
+
25
+ base_url = options.url || ENV["COHERE_URL"] || "https://api.cohere.com/v2"
22
26
 
23
27
  body = {
24
28
  model: options.model || "rerank-v3.5",
25
- query: query.query,
26
- documents: query.documents
29
+ query: query,
30
+ documents: documents
27
31
  }.tap do |b|
28
32
  b[:top_n] = options.n if options.n
29
33
  end
@@ -33,25 +37,24 @@ module Rager
33
37
 
34
38
  request = Rager::Http::Request.new(
35
39
  verb: Rager::Http::Verb::Post,
36
- url: options.url || ENV["COHERE_URL"] || "https://api.cohere.com/v2/rerank",
40
+ url: "#{base_url}/rerank",
37
41
  headers: headers,
38
- body: body.to_json
42
+ body: body.to_json,
43
+ timeout: options.timeout || Rager.config.timeout
39
44
  )
40
45
 
41
46
  response = Rager.config.http_adapter.make_request(request)
42
47
  response_body = T.cast(T.must(response.body), String)
43
48
 
44
- raise Rager::Errors::HttpError.new(Rager.config.http_adapter, response.status, response_body) if response.status != 200
49
+ raise Rager::Errors::HttpError.new(Rager.config.http_adapter, request.url, response.status, body: response_body) if response.status != 200
45
50
 
46
51
  parsed_response = JSON.parse(response_body)
47
52
  results = parsed_response["results"] || []
48
53
 
49
- results.map do |result|
50
- Rager::Rerank::Result.new(
51
- score: result["relevance_score"],
52
- index: result["index"]
53
- )
54
- end
54
+ scores = results.map { |r| r["relevance_score"].to_f }
55
+ reranked_documents = results.map { |r| documents[r["index"]] }
56
+
57
+ Rager::Rerank::Output.new(scores: scores, documents: reranked_documents)
55
58
  end
56
59
  end
57
60
  end
data/lib/rager/result.rb CHANGED
@@ -4,6 +4,7 @@
4
4
  require "json"
5
5
  require "logger"
6
6
  require "securerandom"
7
+
7
8
  require "sorbet-runtime"
8
9
 
9
10
  module Rager
@@ -40,11 +41,17 @@ module Rager
40
41
  sig { returns(T.nilable(String)) }
41
42
  attr_reader :context_name
42
43
 
43
- sig { returns(T.nilable(T::Array[String])) }
44
- attr_reader :iids
44
+ sig { returns(T::Array[String]) }
45
+ attr_reader :tags
45
46
 
46
- sig { returns(T.nilable(String)) }
47
- attr_reader :error
47
+ sig { returns(T::Array[String]) }
48
+ attr_reader :input_ids
49
+
50
+ sig { returns(T::Array[String]) }
51
+ attr_reader :errors
52
+
53
+ sig { returns(Integer) }
54
+ attr_reader :attempt
48
55
 
49
56
  sig do
50
57
  params(
@@ -58,8 +65,10 @@ module Rager
58
65
  end_time: Integer,
59
66
  name: T.nilable(String),
60
67
  context_name: T.nilable(String),
61
- iids: T.nilable(T::Array[String]),
62
- error: T.nilable(String)
68
+ tags: T::Array[String],
69
+ input_ids: T::Array[String],
70
+ errors: T::Array[String],
71
+ attempt: Integer
63
72
  ).void
64
73
  end
65
74
  def initialize(
@@ -73,8 +82,10 @@ module Rager
73
82
  end_time:,
74
83
  name: nil,
75
84
  context_name: nil,
76
- iids: nil,
77
- error: nil
85
+ tags: [],
86
+ input_ids: [],
87
+ errors: [],
88
+ attempt: 0
78
89
  )
79
90
  @id = id
80
91
  @context_id = context_id
@@ -86,8 +97,10 @@ module Rager
86
97
  @end_time = end_time
87
98
  @name = T.let(name, T.nilable(String))
88
99
  @context_name = T.let(context_name, T.nilable(String))
89
- @iids = T.let(iids, T.nilable(T::Array[String]))
90
- @error = T.let(error, T.nilable(String))
100
+ @tags = T.let(tags, T::Array[String])
101
+ @input_ids = T.let(input_ids, T::Array[String])
102
+ @errors = T.let(errors, T::Array[String])
103
+ @attempt = attempt
91
104
 
92
105
  @stream = T.let(nil, T.nilable(Rager::Types::Stream))
93
106
  @buffer = T.let([], Rager::Types::Buffer)
@@ -96,7 +109,7 @@ module Rager
96
109
 
97
110
  sig { returns(T::Boolean) }
98
111
  def success?
99
- @error.nil?
112
+ @errors.empty?
100
113
  end
101
114
 
102
115
  sig { returns(T::Boolean) }
@@ -143,6 +156,7 @@ module Rager
143
156
  parts
144
157
  .sort_by { |index, _| index }
145
158
  .map { |_, content| content }
159
+ .then { |parts| (parts.length == 1) ? parts.first : parts }
146
160
  end
147
161
 
148
162
  sig do
@@ -158,10 +172,8 @@ module Rager
158
172
  case @input
159
173
  when String
160
174
  @input
161
- when Rager::Rerank::Query
162
- @input.serialize
163
- when Rager::Template::Input
164
- @input.serialize
175
+ when Hash
176
+ @input.transform_keys(&:to_s)
165
177
  when Array
166
178
  @input.map do |item|
167
179
  if !item.is_a?(String)
@@ -170,6 +182,8 @@ module Rager
170
182
  item
171
183
  end
172
184
  end
185
+ else
186
+ @input.to_s
173
187
  end
174
188
  end
175
189
 
@@ -183,20 +197,22 @@ module Rager
183
197
  sig { returns(T::Hash[String, T.untyped]) }
184
198
  def to_h
185
199
  {
200
+ id: @id,
186
201
  version: "1",
187
202
  data: {
188
- id: @id,
189
203
  context_id: @context_id,
190
204
  operation: @operation.serialize,
191
- input: serialize_input,
192
- output: serialize_output,
205
+ input: {input: serialize_input},
206
+ output: {output: serialize_output},
193
207
  options: @options.serialize_safe,
194
208
  start_time: @start_time,
195
209
  end_time: @end_time,
196
210
  name: @name,
197
211
  context_name: @context_name,
198
- iids: @iids,
199
- error: @error
212
+ tags: @tags,
213
+ input_ids: @input_ids,
214
+ errors: @errors,
215
+ attempt: @attempt
200
216
  }
201
217
  }
202
218
  end
@@ -217,22 +233,26 @@ module Rager
217
233
  url = Rager.config.url
218
234
  api_key = Rager.config.api_key
219
235
 
236
+ unless url && api_key
237
+ raise Rager::Errors::CredentialsError.new("Rager Cloud", details: "Missing url or api_key for remote logging")
238
+ end
239
+
220
240
  headers = {
221
241
  "Content-Type" => "application/json",
222
242
  "Authorization" => "Bearer #{api_key}"
223
243
  }
224
244
 
225
- if url && api_key
226
- request = Rager::Http::Request.new(
227
- url: url,
228
- verb: Rager::Http::Verb::Post,
229
- headers: headers,
230
- body: json
231
- )
245
+ request = Rager::Http::Request.new(
246
+ url: url,
247
+ verb: Rager::Http::Verb::Post,
248
+ headers: headers,
249
+ body: json
250
+ )
232
251
 
233
- response = http_adapter.make_request(request)
252
+ response = http_adapter.make_request(request)
234
253
 
235
- logger.warn "Remote log failed: \\#{response.status} \\#{response.body}" unless response.success?
254
+ unless response.success?
255
+ logger.error("Failed to log to remote server: #{response.status} #{response.body}")
236
256
  end
237
257
  end
238
258
  end
@@ -9,9 +9,11 @@ module Rager
9
9
  extend T::Sig
10
10
  include Rager::Options
11
11
 
12
- const :provider, String, default: "brave"
12
+ const :provider, String, default: "jina"
13
+ const :max_length, T.nilable(Integer)
13
14
  const :n, T.nilable(Integer)
14
15
  const :api_key, T.nilable(String)
16
+ const :timeout, T.nilable(Numeric)
15
17
  end
16
18
  end
17
19
  end
@@ -0,0 +1,14 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Rager
7
+ module Search
8
+ class Output < T::Struct
9
+ const :urls, T::Array[String]
10
+ const :titles, T::Array[String]
11
+ const :contents, T::Array[String]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,67 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require "uri"
6
+
7
+ require "sorbet-runtime"
8
+
9
+ module Rager
10
+ module Search
11
+ module Providers
12
+ class Jina < Rager::Search::Providers::Abstract
13
+ extend T::Sig
14
+
15
+ sig do
16
+ override.params(
17
+ query: String,
18
+ options: Rager::Search::Options
19
+ ).returns(Rager::Types::SearchOutput)
20
+ end
21
+ def search(query, options)
22
+ api_key = options.api_key || ENV["JINA_API_KEY"]
23
+ raise Rager::Errors::CredentialsError.new("Jina", env_var: ["JINA_API_KEY"]) if api_key.nil?
24
+
25
+ params = {"q" => query}
26
+
27
+ headers = {
28
+ "Accept" => "application/json",
29
+ "X-Return-Format" => "markdown"
30
+ }
31
+
32
+ headers["Authorization"] = "Bearer #{api_key}" if api_key
33
+
34
+ request = Rager::Http::Request.new(
35
+ verb: Rager::Http::Verb::Get,
36
+ url: "https://s.jina.ai/?#{URI.encode_www_form(params)}",
37
+ headers: headers,
38
+ timeout: options.timeout || Rager.config.timeout
39
+ )
40
+
41
+ response = Rager.config.http_adapter.make_request(request)
42
+ response_body = T.cast(T.must(response.body), String)
43
+
44
+ raise Rager::Errors::HttpError.new(Rager.config.http_adapter, request.url, response.status, body: response_body) if response.status != 200
45
+
46
+ parsed_response = JSON.parse(response_body)
47
+ search_results = parsed_response["data"] || []
48
+
49
+ search_results = search_results.first(options.n) if options.n
50
+
51
+ urls = search_results.map { |r| r["url"] }
52
+ titles = search_results.map { |r| r["title"] }
53
+ contents = search_results.map { |r|
54
+ content = r["content"] || r["description"]
55
+ options.max_length ? content&.slice(0, options.max_length) : content
56
+ }
57
+
58
+ Rager::Search::Output.new(
59
+ urls: urls,
60
+ titles: titles,
61
+ contents: contents
62
+ )
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -14,11 +14,12 @@ module Rager
14
14
 
15
15
  sig do
16
16
  abstract.params(
17
- input: Rager::Template::Input,
17
+ template: String,
18
+ variables: T::Hash[Symbol, T.untyped],
18
19
  options: Rager::Template::Options
19
20
  ).returns(Rager::Types::TemplateOutput)
20
21
  end
21
- def template(input, options)
22
+ def template(template, variables, options)
22
23
  end
23
24
  end
24
25
  end
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "erb"
5
- require "ostruct"
6
5
 
7
6
  module Rager
8
7
  module Template
@@ -12,13 +11,15 @@ module Rager
12
11
 
13
12
  sig do
14
13
  override.params(
15
- input: Rager::Template::Input,
14
+ template: String,
15
+ variables: T::Hash[Symbol, T.untyped],
16
16
  options: Rager::Template::Options
17
17
  ).returns(Rager::Types::TemplateOutput)
18
18
  end
19
- def template(input, options)
20
- scope = OpenStruct.new(input.variables)
21
- ERB.new(input.template).result(scope.instance_eval { binding })
19
+ def template(template, variables, options)
20
+ ERB.new(template).result_with_hash(variables)
21
+ rescue SyntaxError, NameError => e
22
+ raise Rager::Errors::TemplateError.new(template, variables, details: e.message)
22
23
  end
23
24
  end
24
25
  end
data/lib/rager/types.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require_relative "rerank/output"
5
+ require_relative "search/output"
6
+
4
7
  module Rager
5
8
  module Types
6
9
  extend T::Sig
@@ -9,9 +12,9 @@ module Rager
9
12
  EmbedInput = T.type_alias { T::Array[String] }
10
13
  ImageGenInput = T.type_alias { String }
11
14
  MeshGenInput = T.type_alias { String }
12
- RerankInput = T.type_alias { Rager::Rerank::Query }
15
+ RerankInput = T.type_alias { {query: String, documents: T.any(T::Array[String], T::Array[T::Hash[Symbol, String]])} }
13
16
  SearchInput = T.type_alias { String }
14
- TemplateInput = T.type_alias { Rager::Template::Input }
17
+ TemplateInput = T.type_alias { {template: String, variables: T::Hash[Symbol, T.untyped]} }
15
18
  Input = T.type_alias {
16
19
  T.any(
17
20
  ChatInput,
@@ -20,24 +23,25 @@ module Rager
20
23
  MeshGenInput,
21
24
  RerankInput,
22
25
  SearchInput,
23
- TemplateInput
26
+ TemplateInput,
27
+ Rager::Result
24
28
  )
25
29
  }
26
30
 
27
- ChatNonStream = T.type_alias { T::Array[String] }
28
-
29
31
  ChatStream = T.type_alias { T::Enumerator[Rager::Chat::MessageDelta] }
30
32
  Stream = T.type_alias { ChatStream }
31
33
 
32
34
  ChatBuffer = T.type_alias { T::Array[Rager::Chat::MessageDelta] }
33
35
  Buffer = T.type_alias { ChatBuffer }
34
36
 
37
+ ChatNonStream = T.type_alias { T.any(String, T::Array[String]) }
38
+
35
39
  ChatOutput = T.type_alias { T.any(ChatNonStream, ChatStream) }
36
40
  EmbedOutput = T.type_alias { T::Array[T::Array[Float]] }
37
41
  ImageGenOutput = T.type_alias { String }
38
42
  MeshGenOutput = T.type_alias { String }
39
- RerankOutput = T.type_alias { T::Array[Rager::Rerank::Result] }
40
- SearchOutput = T.type_alias { T::Array[Rager::Search::Result] }
43
+ RerankOutput = T.type_alias { Rager::Rerank::Output }
44
+ SearchOutput = T.type_alias { Rager::Search::Output }
41
45
  TemplateOutput = T.type_alias { String }
42
46
  Output = T.type_alias {
43
47
  T.any(
@@ -1,48 +1,55 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
4
+ require "fileutils"
5
+ require "json"
6
+ require "net/http"
5
7
  require "uri"
6
8
 
9
+ require "sorbet-runtime"
10
+
7
11
  module Rager
8
12
  module Utils
9
13
  module Http
10
14
  extend T::Sig
11
15
 
12
- sig { params(url: String, headers: T.nilable(T::Hash[String, String])).returns(T.nilable(String)) }
13
- def self.download_binary(url, headers = nil)
14
- response = Rager.config.http_adapter.make_request(
15
- Rager::Http::Request.new(url: url, headers: headers || {})
16
- )
17
-
18
- if response.success? && response.body
19
- binary_data = T.cast(response.body, String)
20
- binary_data.force_encoding(Encoding::BINARY)
21
- binary_data
16
+ sig { params(duration: Numeric).void }
17
+ def self.sleep(duration)
18
+ if defined?(Async::Task) && (task = Async::Task.current?)
19
+ task.sleep(duration)
20
+ else
21
+ Kernel.sleep(duration)
22
22
  end
23
- rescue
24
- nil
25
23
  end
26
24
 
27
- sig { params(url: String, path: T.nilable(String), headers: T.nilable(T::Hash[String, String])).returns(String) }
28
- def self.download_file(url, path = nil, headers = nil)
29
- path ||= begin
30
- uri_path = URI.parse(url).path
31
- if uri_path.nil? || uri_path.empty?
32
- "output"
33
- else
34
- filename = File.basename(uri_path)
35
- filename.empty? ? "output" : filename
36
- end
25
+ sig { params(url: String, http_adapter: T.nilable(Rager::Http::Adapters::Abstract)).returns(T.nilable(String)) }
26
+ def self.download_binary(url, http_adapter: nil)
27
+ adapter = http_adapter || Rager.config.http_adapter
28
+ response = adapter.make_request(
29
+ Rager::Http::Request.new(url: url, headers: {})
30
+ )
31
+
32
+ return nil unless response.success?
33
+ body = response.body
34
+ return nil if body.nil?
35
+
36
+ if body.is_a?(String)
37
+ body
38
+ elsif body.respond_to?(:each)
39
+ body.to_a.join
40
+ else
41
+ body.to_s
37
42
  end
43
+ end
38
44
 
39
- binary_data = download_binary(url, headers)
40
- raise Rager::Errors::HttpError.new(Rager.config.http_adapter, 500, "Download failed") if binary_data.nil?
45
+ sig { params(url: String, path: String, http_adapter: T.nilable(Rager::Http::Adapters::Abstract)).returns(T.nilable(String)) }
46
+ def self.download_file(url, path, http_adapter: nil)
47
+ content = download_binary(url, http_adapter: http_adapter)
48
+ return nil if content.nil?
41
49
 
42
- File.binwrite(path, binary_data)
50
+ FileUtils.mkdir_p(File.dirname(path))
51
+ File.write(path, content)
43
52
  path
44
- rescue => e
45
- raise Rager::Errors::HttpError.new(Rager.config.http_adapter, 500, "Download failed: #{e.message}")
46
53
  end
47
54
  end
48
55
  end