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
@@ -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
@@ -18,7 +19,9 @@ module Rager
18
19
  end
19
20
  def embed(text, options)
20
21
  api_key = options.api_key || ENV["OPENAI_API_KEY"]
21
- raise Rager::Errors::MissingCredentialsError.new("OpenAI", "OPENAI_API_KEY") if api_key.nil?
22
+ raise Rager::Errors::CredentialsError.new("OpenAI", env_var: ["OPENAI_API_KEY"]) if api_key.nil?
23
+
24
+ base_url = options.url || ENV["OPENAI_URL"] || "https://api.openai.com/v1"
22
25
 
23
26
  headers = {"Content-Type" => "application/json"}
24
27
  headers["Authorization"] = "Bearer #{api_key}" if api_key
@@ -30,15 +33,16 @@ module Rager
30
33
 
31
34
  request = Rager::Http::Request.new(
32
35
  verb: Rager::Http::Verb::Post,
33
- url: options.url || ENV["OPENAI_URL"] || "https://api.openai.com/v1/embeddings",
36
+ url: "#{base_url}/embeddings",
34
37
  headers: headers,
35
- body: body.to_json
38
+ body: body.to_json,
39
+ timeout: options.timeout || Rager.config.timeout
36
40
  )
37
41
 
38
42
  response = Rager.config.http_adapter.make_request(request)
39
43
  response_body = T.cast(T.must(response.body), String)
40
44
 
41
- raise Rager::Errors::HttpError.new(Rager.config.http_adapter, response.status, response_body) if response.status != 200
45
+ raise Rager::Errors::HttpError.new(Rager.config.http_adapter, request.url, response.status, body: response_body) if response.status != 200
42
46
 
43
47
  parsed_response = JSON.parse(response_body)
44
48
  parsed_response["data"].map { |item| item["embedding"] }
@@ -0,0 +1,24 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Rager
7
+ module Errors
8
+ class CredentialsError < Rager::Error
9
+ extend T::Sig
10
+
11
+ sig { params(provider: String, env_var: T::Array[String], details: T.nilable(String)).void }
12
+ def initialize(provider, env_var: [], details: nil)
13
+ error_data = {
14
+ type: "credentials",
15
+ provider: provider,
16
+ env_var: env_var,
17
+ details: details
18
+ }
19
+
20
+ super(error_data.to_json)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Rager
7
+ module Errors
8
+ class DependencyError < Rager::Error
9
+ extend T::Sig
10
+
11
+ sig { params(dependency: String, details: T.nilable(String)).void }
12
+ def initialize(dependency, details: nil)
13
+ error_data = {
14
+ type: "dependency",
15
+ dependency: dependency,
16
+ details: details
17
+ }
18
+
19
+ super(error_data.to_json)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -8,11 +8,18 @@ module Rager
8
8
  class HttpError < Rager::Error
9
9
  extend T::Sig
10
10
 
11
- sig { params(adapter: Rager::Http::Adapters::Abstract, status: Integer, body: T.nilable(String)).void }
12
- def initialize(adapter, status, body)
13
- message = "HTTP Error #{status} using #{adapter.class.name}"
14
- message += ": #{body}" if body
15
- super(message)
11
+ sig { params(adapter: Rager::Http::Adapters::Abstract, url: T.nilable(String), status: Integer, body: T.nilable(String), details: T.nilable(String)).void }
12
+ def initialize(adapter, url, status, body: nil, details: nil)
13
+ error_data = {
14
+ type: "http",
15
+ adapter: adapter.class.name,
16
+ url: url,
17
+ status: status,
18
+ body: body,
19
+ details: details
20
+ }
21
+
22
+ super(error_data.to_json)
16
23
  end
17
24
  end
18
25
  end
@@ -8,11 +8,16 @@ module Rager
8
8
  class OptionsError < Rager::Error
9
9
  extend T::Sig
10
10
 
11
- sig { params(invalid_keys: T::Array[String], description: T.nilable(String)).void }
12
- def initialize(invalid_keys:, description: nil)
13
- message = "Invalid keys #{invalid_keys.join(", ")}"
14
- message += " (#{description})" if description
15
- super(message)
11
+ sig { params(options: Rager::Options, invalid_keys: T::Array[String], details: T.nilable(String)).void }
12
+ def initialize(options, invalid_keys, details: nil)
13
+ error_data = {
14
+ type: "options",
15
+ options: options.serialize_safe,
16
+ invalid_keys: invalid_keys,
17
+ details: details
18
+ }
19
+
20
+ super(error_data.to_json)
16
21
  end
17
22
  end
18
23
  end
@@ -8,11 +8,15 @@ module Rager
8
8
  class ParseError < Rager::Error
9
9
  extend T::Sig
10
10
 
11
- sig { params(description: String, body: T.nilable(String)).void }
12
- def initialize(description, body = nil)
13
- message = description
14
- message += " -- #{body}" if body
15
- super(message)
11
+ sig { params(body: String, details: T.nilable(String)).void }
12
+ def initialize(body, details: nil)
13
+ error_data = {
14
+ type: "parse",
15
+ body: body,
16
+ details: details
17
+ }
18
+
19
+ super(error_data.to_json)
16
20
  end
17
21
  end
18
22
  end
@@ -8,10 +8,16 @@ module Rager
8
8
  class TemplateError < Rager::Error
9
9
  extend T::Sig
10
10
 
11
- sig { params(input: Rager::Template::Input, message: String).void }
12
- def initialize(input, message)
13
- message = "Template Error: #{message} in #{input.template}"
14
- super(message)
11
+ sig { params(template: String, variables: T::Hash[Symbol, T.untyped], details: T.nilable(String)).void }
12
+ def initialize(template, variables, details: nil)
13
+ error_data = {
14
+ type: "template",
15
+ template: template,
16
+ variables: variables,
17
+ details: details
18
+ }
19
+
20
+ super(error_data.to_json)
15
21
  end
16
22
  end
17
23
  end
@@ -0,0 +1,25 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Rager
7
+ module Errors
8
+ class TimeoutError < Rager::Error
9
+ extend T::Sig
10
+
11
+ sig { params(operation: String, timeout_seconds: T.nilable(Numeric), attempts: T.nilable(Integer), details: T.nilable(String)).void }
12
+ def initialize(operation, timeout_seconds: nil, attempts: nil, details: nil)
13
+ error_data = {
14
+ type: "timeout",
15
+ operation: operation,
16
+ timeout_seconds: timeout_seconds,
17
+ attempts: attempts,
18
+ details: details
19
+ }
20
+
21
+ super(error_data.to_json)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -11,7 +11,11 @@ module Rager
11
11
 
12
12
  sig { void }
13
13
  def initialize
14
- require "async/http"
14
+ begin
15
+ require "async/http"
16
+ rescue LoadError
17
+ raise Rager::Errors::DependencyError.new("async-http", details: "Please install the async-http gem to use the AsyncHttp adapter")
18
+ end
15
19
 
16
20
  @internet = T.let(Async::HTTP::Internet.new, Async::HTTP::Internet)
17
21
  end
@@ -22,22 +26,21 @@ module Rager
22
26
  ).returns(Rager::Http::Response)
23
27
  }
24
28
  def make_request(request)
25
- response = @internet.call(
26
- request.verb.serialize,
27
- request.url,
28
- request.headers.to_a,
29
- request.body
30
- )
29
+ response = wrap_if_timeout(request.timeout) do
30
+ @internet.call(
31
+ request.verb.serialize,
32
+ request.url,
33
+ request.headers.to_a,
34
+ request.body
35
+ )
36
+ end
31
37
 
32
38
  body = if response.body.nil?
33
39
  nil
34
- elsif (response.headers["Transfer-Encoding"]&.downcase == "chunked") ||
35
- response.headers["content-type"]&.downcase&.include?("text/event-stream")
40
+ elsif request.streaming
36
41
  body_enum(response)
37
42
  else
38
- body_parts = []
39
- response.body.each { |chunk| body_parts << chunk }
40
- body_parts.join.force_encoding("UTF-8")
43
+ response.body.join
41
44
  end
42
45
 
43
46
  Response.new(
@@ -45,10 +48,61 @@ module Rager
45
48
  headers: response.headers.to_h,
46
49
  body: body
47
50
  )
51
+ rescue SocketError => e
52
+ raise Rager::Errors::HttpError.new(
53
+ self,
54
+ request.url,
55
+ 0,
56
+ body: nil,
57
+ details: "DNS resolution failed: #{e.message}"
58
+ )
59
+ rescue Errno::ECONNREFUSED => e
60
+ raise Rager::Errors::HttpError.new(
61
+ self,
62
+ request.url,
63
+ 0,
64
+ body: nil,
65
+ details: "Connection refused: #{e.message}"
66
+ )
67
+ rescue Errno::ETIMEDOUT => e
68
+ raise Rager::Errors::HttpError.new(
69
+ self,
70
+ request.url,
71
+ 0,
72
+ body: nil,
73
+ details: "Connection timed out: #{e.message}"
74
+ )
75
+ rescue Async::TimeoutError => e
76
+ raise Rager::Errors::HttpError.new(
77
+ self,
78
+ request.url,
79
+ 0,
80
+ body: nil,
81
+ details: "Request timed out: #{e.message}"
82
+ )
83
+ rescue EOFError => e
84
+ raise Rager::Errors::HttpError.new(
85
+ self,
86
+ request.url,
87
+ 0,
88
+ body: nil,
89
+ details: "Connection closed unexpectedly: #{e.message}"
90
+ )
48
91
  end
49
92
 
50
93
  private
51
94
 
95
+ sig { params(timeout: T.nilable(Numeric), block: T.proc.returns(T.untyped)).returns(T.untyped) }
96
+ def wrap_if_timeout(timeout, &block)
97
+ if timeout
98
+ Async::Task.current.with_timeout(timeout) do
99
+ block.call
100
+ end
101
+ else
102
+ block.call
103
+ end
104
+ end
105
+
52
106
  sig {
53
107
  params(
54
108
  response: Async::HTTP::Protocol::Response
@@ -56,7 +110,7 @@ module Rager
56
110
  }
57
111
  def body_enum(response)
58
112
  Enumerator.new do |yielder|
59
- response.body.each { |chunk| yielder << chunk.force_encoding("UTF-8") }
113
+ response.body.each { |chunk| yielder << chunk }
60
114
  ensure
61
115
  response.close
62
116
  end
@@ -1,8 +1,10 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "base64"
4
5
  require "fileutils"
5
6
  require "json"
7
+
6
8
  require "sorbet-runtime"
7
9
 
8
10
  module Rager
@@ -20,13 +22,9 @@ module Rager
20
22
  chunk_delimiter: T.nilable(String)
21
23
  ).void
22
24
  end
23
- def initialize(
24
- test_file_path,
25
- fallback_adapter = nil,
26
- chunk_delimiter = nil
27
- )
25
+ def initialize(test_file_path, fallback_adapter = nil, chunk_delimiter = nil)
28
26
  @test_file_path = T.let(test_file_path, String)
29
- @fallback_adapter = T.let(fallback_adapter || Rager::Http::Adapters::AsyncHttp.new, Rager::Http::Adapters::Abstract)
27
+ @fallback_adapter = T.let(fallback_adapter || Rager::Http::Adapters::NetHttp.new, Rager::Http::Adapters::Abstract)
30
28
  @cache = T.let(load_cache, Cache)
31
29
  end
32
30
 
@@ -34,48 +32,50 @@ module Rager
34
32
  def make_request(request)
35
33
  key = request.serialize.to_json
36
34
  cached_entry = @cache[key]
37
- if cached_entry
38
- build_response_from_cache(cached_entry)
39
- else
40
- fetch_and_cache_response(request, key)
41
- end
35
+ cached_entry ? build_response_from_cache(cached_entry) : fetch_and_cache_response(request, key)
42
36
  end
43
37
 
44
- sig {
45
- params(
46
- request: Rager::Http::Request,
47
- key: String
48
- ).returns(Rager::Http::Response)
49
- }
38
+ private
39
+
40
+ sig { params(request: Rager::Http::Request, key: String).returns(Rager::Http::Response) }
50
41
  def fetch_and_cache_response(request, key)
51
42
  response = @fallback_adapter.make_request(request)
52
43
 
53
- serialized_response = T.let({
44
+ serialized_response = {
54
45
  "status" => response.status,
55
46
  "headers" => response.headers
56
- }, T::Hash[String, T.untyped])
47
+ }
57
48
 
58
- if response.body.is_a?(Enumerator)
59
- chunks = T.let([], T::Array[String])
49
+ if request.streaming && response.body.is_a?(Enumerator)
50
+ raw_chunks = T.let([], T::Array[String])
51
+ T.cast(response.body, T::Enumerator[String]).each { |chunk| raw_chunks << chunk }
60
52
 
61
- T.cast(response.body, T::Enumerator[String]).each do |chunk|
62
- chunks << chunk
63
- end
53
+ has_binary = raw_chunks.any? { |chunk| binary_content?(chunk) }
54
+ chunks = raw_chunks.map { |chunk| binary_content?(chunk) ? Base64.strict_encode64(chunk) : chunk }
64
55
 
65
56
  serialized_response["body"] = chunks
66
57
  serialized_response["is_stream"] = true
58
+ serialized_response["is_binary"] = has_binary
67
59
 
68
60
  response_body = Enumerator.new do |yielder|
69
- chunks.each { |chunk| yielder << chunk }
61
+ raw_chunks.each { |chunk| yielder << chunk }
70
62
  end
71
63
  else
72
- serialized_response["body"] = response.body
64
+ body = T.cast(response.body, T.nilable(String)) || ""
65
+
66
+ if binary_content?(body)
67
+ serialized_response["body"] = Base64.strict_encode64(body)
68
+ serialized_response["is_binary"] = true
69
+ else
70
+ serialized_response["body"] = body
71
+ serialized_response["is_binary"] = false
72
+ end
73
+
73
74
  serialized_response["is_stream"] = false
74
- response_body = response.body
75
+ response_body = body
75
76
  end
76
77
 
77
78
  @cache[key] = serialized_response
78
-
79
79
  save_cache
80
80
 
81
81
  Rager::Http::Response.new(
@@ -85,19 +85,19 @@ module Rager
85
85
  )
86
86
  end
87
87
 
88
- sig {
89
- params(
90
- entry: T::Hash[String, T.untyped]
91
- ).returns(Rager::Http::Response)
92
- }
88
+ sig { params(entry: T::Hash[String, T.untyped]).returns(Rager::Http::Response) }
93
89
  def build_response_from_cache(entry)
94
90
  body = entry["body"]
95
91
  is_stream = entry["is_stream"] || false
92
+ is_binary = entry["is_binary"] || false
96
93
 
97
94
  body = if is_stream
98
- T.cast(body, T::Array[String]).to_enum
95
+ chunks = T.cast(body, T::Array[String])
96
+ Enumerator.new do |yielder|
97
+ chunks.each { |chunk| yielder << (is_binary ? Base64.strict_decode64(chunk) : chunk) }
98
+ end
99
99
  else
100
- body
100
+ is_binary ? Base64.strict_decode64(T.cast(body, String)) : body
101
101
  end
102
102
 
103
103
  Rager::Http::Response.new(
@@ -116,21 +116,19 @@ module Rager
116
116
  sig { returns(Cache) }
117
117
  def load_cache
118
118
  create_file_if_not_exists
119
- JSON.parse(File.read(@test_file_path, encoding: "UTF-8"))
119
+ JSON.parse(File.read(@test_file_path))
120
120
  end
121
121
 
122
122
  sig { void }
123
123
  def save_cache
124
- current_file_content =
125
- if File.exist?(@test_file_path)
126
- JSON.parse(File.read(@test_file_path, encoding: "UTF-8"))
127
- else
128
- {}
129
- end
130
-
124
+ current_file_content = File.exist?(@test_file_path) ? JSON.parse(File.read(@test_file_path)) : {}
131
125
  output = current_file_content.merge(@cache)
132
- json_string = JSON.generate(output).force_encoding("UTF-8")
133
- File.write(@test_file_path, json_string, encoding: "UTF-8")
126
+ File.write(@test_file_path, JSON.generate(output))
127
+ end
128
+
129
+ sig { params(content: String).returns(T::Boolean) }
130
+ def binary_content?(content)
131
+ content.include?("\0")
134
132
  end
135
133
  end
136
134
  end
@@ -0,0 +1,145 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "net/http"
5
+ require "uri"
6
+ require "sorbet-runtime"
7
+
8
+ module Rager
9
+ module Http
10
+ module Adapters
11
+ class NetHttp < Rager::Http::Adapters::Abstract
12
+ extend T::Sig
13
+
14
+ sig { void }
15
+ def initialize
16
+ end
17
+
18
+ sig {
19
+ override.params(
20
+ request: Rager::Http::Request
21
+ ).returns(Rager::Http::Response)
22
+ }
23
+ def make_request(request)
24
+ uri = URI(request.url)
25
+
26
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
27
+ if request.timeout
28
+ http.open_timeout = request.timeout
29
+ http.read_timeout = request.timeout
30
+ end
31
+
32
+ http_request = build_http_request(request, uri)
33
+
34
+ http.request(http_request) do |response|
35
+ body = if request.streaming
36
+ body_enum(response)
37
+ else
38
+ response.body
39
+ end
40
+
41
+ return Rager::Http::Response.new(
42
+ status: response.code.to_i,
43
+ headers: response.to_hash,
44
+ body: body
45
+ )
46
+ end
47
+ end
48
+ rescue SocketError => e
49
+ raise Rager::Errors::HttpError.new(
50
+ self,
51
+ request.url,
52
+ 0,
53
+ body: nil,
54
+ details: "DNS resolution failed: #{e.message}"
55
+ )
56
+ rescue Errno::ECONNREFUSED => e
57
+ raise Rager::Errors::HttpError.new(
58
+ self,
59
+ request.url,
60
+ 0,
61
+ body: nil,
62
+ details: "Connection refused: #{e.message}"
63
+ )
64
+ rescue Errno::EBUSY => e
65
+ raise Rager::Errors::HttpError.new(
66
+ self,
67
+ request.url,
68
+ 0,
69
+ body: nil,
70
+ details: "Device or resource busy: #{e.message}"
71
+ )
72
+ rescue Net::OpenTimeout => e
73
+ raise Rager::Errors::HttpError.new(
74
+ self,
75
+ request.url,
76
+ 0,
77
+ body: nil,
78
+ details: "Connection timed out: #{e.message}"
79
+ )
80
+ rescue Net::ReadTimeout => e
81
+ raise Rager::Errors::HttpError.new(
82
+ self,
83
+ request.url,
84
+ 0,
85
+ body: nil,
86
+ details: "Read timed out: #{e.message}"
87
+ )
88
+ rescue EOFError => e
89
+ raise Rager::Errors::HttpError.new(
90
+ self,
91
+ request.url,
92
+ 0,
93
+ body: nil,
94
+ details: "Connection closed unexpectedly: #{e.message}"
95
+ )
96
+ end
97
+
98
+ private
99
+
100
+ sig {
101
+ params(
102
+ response: Net::HTTPResponse
103
+ ).returns(T::Enumerator[String])
104
+ }
105
+ def body_enum(response)
106
+ chunks = T.let([], T::Array[String])
107
+
108
+ response.read_body do |chunk|
109
+ chunks << chunk
110
+ end
111
+
112
+ chunks.to_enum
113
+ end
114
+
115
+ sig {
116
+ params(
117
+ request: Rager::Http::Request,
118
+ uri: URI::Generic
119
+ ).returns(Net::HTTPRequest)
120
+ }
121
+ def build_http_request(request, uri)
122
+ http_request = case request.verb
123
+ when Rager::Http::Verb::Get then Net::HTTP::Get.new(uri)
124
+ when Rager::Http::Verb::Post then Net::HTTP::Post.new(uri)
125
+ when Rager::Http::Verb::Put then Net::HTTP::Put.new(uri)
126
+ when Rager::Http::Verb::Patch then Net::HTTP::Patch.new(uri)
127
+ when Rager::Http::Verb::Delete then Net::HTTP::Delete.new(uri)
128
+ when Rager::Http::Verb::Head then Net::HTTP::Head.new(uri)
129
+ when Rager::Http::Verb::Options then Net::HTTP::Options.new(uri)
130
+ end
131
+
132
+ request.headers.each do |key, value|
133
+ http_request[key] = value
134
+ end
135
+
136
+ if request.body && http_request.request_body_permitted?
137
+ http_request.body = request.body
138
+ end
139
+
140
+ http_request
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -10,6 +10,8 @@ module Rager
10
10
  const :verb, Rager::Http::Verb, default: Rager::Http::Verb::Get
11
11
  const :headers, T::Hash[String, String]
12
12
  const :body, T.nilable(String)
13
+ const :streaming, T::Boolean, default: false
14
+ const :timeout, T.nilable(Numeric)
13
15
  end
14
16
  end
15
17
  end
@@ -14,6 +14,7 @@ module Rager
14
14
  const :output_format, T.nilable(Rager::ImageGen::OutputFormat)
15
15
  const :api_key, T.nilable(String)
16
16
  const :seed, T.nilable(Integer)
17
+ const :timeout, T.nilable(Numeric)
17
18
  end
18
19
  end
19
20
  end
@@ -13,7 +13,7 @@ module Rager
13
13
  sig { override.params(prompt: String, options: Rager::ImageGen::Options).returns(Rager::Types::ImageGenOutput) }
14
14
  def image_gen(prompt, options)
15
15
  api_key = options.api_key || ENV["REPLICATE_API_KEY"]
16
- raise Rager::Errors::MissingCredentialsError.new("Replicate", "REPLICATE_API_KEY") if api_key.nil?
16
+ raise Rager::Errors::CredentialsError.new("Replicate", env_var: ["REPLICATE_API_KEY"]) if api_key.nil?
17
17
 
18
18
  body = {
19
19
  input: {
@@ -32,18 +32,19 @@ module Rager
32
32
  "Content-Type" => "application/json",
33
33
  "Prefer" => "wait"
34
34
  },
35
- body: body.to_json
35
+ body: body.to_json,
36
+ timeout: options.timeout || Rager.config.timeout
36
37
  )
37
38
 
38
39
  response = Rager.config.http_adapter.make_request(request)
39
40
  response_body = T.cast(T.must(response.body), String)
40
41
 
41
- raise Rager::Errors::HttpError.new(Rager.config.http_adapter, response.status, response_body) unless [200, 201].include?(response.status)
42
+ raise Rager::Errors::HttpError.new(Rager.config.http_adapter, request.url, response.status, body: response_body) unless [200, 201].include?(response.status)
42
43
 
43
44
  parsed = JSON.parse(response_body)
44
45
  parsed.fetch("output").first
45
46
  rescue JSON::ParserError, KeyError => e
46
- raise Rager::Errors::ParseError.new(e.message, response_body)
47
+ raise Rager::Errors::ParseError.new(response_body || "", details: e.message)
47
48
  end
48
49
  end
49
50
  end
@@ -14,6 +14,7 @@ module Rager
14
14
  const :version, String, default: "4876f2a8da1c544772dffa32e8889da4a1bab3a1f5c1937bfcfccb99ae347251"
15
15
  const :api_key, T.nilable(String)
16
16
  const :seed, T.nilable(Integer)
17
+ const :timeout, T.nilable(Numeric)
17
18
  end
18
19
  end
19
20
  end