reve_ai 0.1.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 +7 -0
- data/README.md +204 -0
- data/Rakefile +15 -0
- data/lib/reve_ai/client.rb +105 -0
- data/lib/reve_ai/configuration.rb +100 -0
- data/lib/reve_ai/errors.rb +194 -0
- data/lib/reve_ai/http/client.rb +208 -0
- data/lib/reve_ai/resources/base.rb +109 -0
- data/lib/reve_ai/resources/images.rb +184 -0
- data/lib/reve_ai/response.rb +136 -0
- data/lib/reve_ai/version.rb +6 -0
- data/lib/reve_ai.rb +90 -0
- metadata +87 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 280bf1673c22b411163ea4dad09eae4a7ca07a06dceefee1d10b54cc2a07a097
|
|
4
|
+
data.tar.gz: 288d3493fcd73b1827003b785c29fd0c7ce53a76a302769e03054686508d2dc4
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 604e38817990a60d470e0c24b96033c88b27774012e6dac07e6392c7f413d02fd8c123a92978868eb972790dea4ad23f11ce73226c11db88f0c3a5482aff7e52
|
|
7
|
+
data.tar.gz: 202c65bafdcd96b002cff7a083f10b7efd4c05e187b0add59831833e8d6dfd88e7aa0fac49037b986a5aedb4bcf19eceb3b6d4a679741a2acb3bd7577bc5a27f
|
data/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# ReveAI
|
|
2
|
+
|
|
3
|
+
Ruby client for the [Reve image generation API](https://api.reve.com/console/docs).
|
|
4
|
+
|
|
5
|
+
[](https://badge.fury.io/rb/reve_ai)
|
|
6
|
+
[](https://github.com/dpaluy/reve_ai/actions/workflows/ci.yml)
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
bundle add reve_ai
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
Configure once:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
ReveAI.configure do |config|
|
|
20
|
+
config.api_key = ENV.fetch("REVE_AI_API_KEY")
|
|
21
|
+
end
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Create Image
|
|
25
|
+
|
|
26
|
+
Generate an image from a text prompt:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
client = ReveAI::Client.new
|
|
30
|
+
|
|
31
|
+
response = client.images.create(prompt: "A beautiful sunset over mountains")
|
|
32
|
+
|
|
33
|
+
response.image # => "base64encodeddata..."
|
|
34
|
+
response.version # => "reve-create@20250915"
|
|
35
|
+
response.request_id # => "rsid-..."
|
|
36
|
+
response.credits_used # => 18
|
|
37
|
+
response.credits_remaining # => 982
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
With aspect ratio:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
response = client.images.create(
|
|
44
|
+
prompt: "A beautiful sunset over mountains",
|
|
45
|
+
aspect_ratio: "16:9" # Options: 16:9, 9:16, 3:2, 2:3, 4:3, 3:4, 1:1 (default: 3:2)
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
With specific model version:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
response = client.images.create(
|
|
53
|
+
prompt: "A beautiful sunset over mountains",
|
|
54
|
+
version: "reve-create@20250915" # Or "latest" (default)
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Edit Image
|
|
59
|
+
|
|
60
|
+
Modify an existing image using text instructions:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
require "base64"
|
|
64
|
+
|
|
65
|
+
client = ReveAI::Client.new
|
|
66
|
+
|
|
67
|
+
# Load and encode the image
|
|
68
|
+
image_data = Base64.strict_encode64(File.read("my-image.png"))
|
|
69
|
+
|
|
70
|
+
response = client.images.edit(
|
|
71
|
+
edit_instruction: "Add dramatic clouds to the sky",
|
|
72
|
+
reference_image: image_data
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
response.image # => "base64editeddata..."
|
|
76
|
+
response.version # => "reve-edit@20250915"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Available versions for edit: `latest`, `latest-fast`, `reve-edit@20250915`, `reve-edit-fast@20251030`
|
|
80
|
+
|
|
81
|
+
### Remix Images
|
|
82
|
+
|
|
83
|
+
Combine text prompts with reference images to create new variations:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
require "base64"
|
|
87
|
+
|
|
88
|
+
client = ReveAI::Client.new
|
|
89
|
+
|
|
90
|
+
# Load and encode reference images
|
|
91
|
+
image1 = Base64.strict_encode64(File.read("person.png"))
|
|
92
|
+
image2 = Base64.strict_encode64(File.read("background.png"))
|
|
93
|
+
|
|
94
|
+
# Use <img>N</img> tags to reference specific images by index
|
|
95
|
+
response = client.images.remix(
|
|
96
|
+
prompt: "The person from <img>0</img> standing in the scene from <img>1</img>",
|
|
97
|
+
reference_images: [image1, image2],
|
|
98
|
+
aspect_ratio: "16:9" # Optional
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
response.image # => "base64remixeddata..."
|
|
102
|
+
response.version # => "reve-remix@20250915"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Available versions for remix: `latest`, `latest-fast`, `reve-remix@20250915`, `reve-remix-fast@20251030`
|
|
106
|
+
|
|
107
|
+
### Rails
|
|
108
|
+
|
|
109
|
+
Create `config/initializers/reve_ai.rb`:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
ReveAI.configure do |c|
|
|
113
|
+
c.api_key = Rails.application.credentials.dig(:reve, :api_key)
|
|
114
|
+
# c.base_url = "https://api.reve.com"
|
|
115
|
+
# c.timeout = 120
|
|
116
|
+
# c.open_timeout = 30
|
|
117
|
+
# c.max_retries = 2
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Error Handling
|
|
122
|
+
|
|
123
|
+
The gem provides detailed error classes for different scenarios:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
begin
|
|
127
|
+
client.images.create(prompt: "A sunset")
|
|
128
|
+
rescue ReveAI::ValidationError => e
|
|
129
|
+
# Input validation failed (prompt too long, invalid aspect ratio, etc.)
|
|
130
|
+
puts "Validation error: #{e.message}"
|
|
131
|
+
rescue ReveAI::UnauthorizedError => e
|
|
132
|
+
# Invalid API key (401)
|
|
133
|
+
puts "Auth error: #{e.message}"
|
|
134
|
+
rescue ReveAI::InsufficientCreditsError => e
|
|
135
|
+
# Budget has run out (402)
|
|
136
|
+
puts "Out of credits: #{e.message}"
|
|
137
|
+
rescue ReveAI::UnprocessableEntityError => e
|
|
138
|
+
# Inputs could not be understood (422)
|
|
139
|
+
puts "Unprocessable: #{e.message}"
|
|
140
|
+
rescue ReveAI::RateLimitError => e
|
|
141
|
+
# Rate limited (429) - check retry_after
|
|
142
|
+
puts "Rate limited. Retry after: #{e.retry_after} seconds"
|
|
143
|
+
rescue ReveAI::BadRequestError => e
|
|
144
|
+
# Invalid request parameters (400)
|
|
145
|
+
puts "Bad request: #{e.message}"
|
|
146
|
+
rescue ReveAI::ServerError => e
|
|
147
|
+
# Server-side error (5xx)
|
|
148
|
+
puts "Server error: #{e.message}"
|
|
149
|
+
rescue ReveAI::TimeoutError => e
|
|
150
|
+
# Request timed out
|
|
151
|
+
puts "Timeout: #{e.message}"
|
|
152
|
+
rescue ReveAI::ConnectionError => e
|
|
153
|
+
# Connection failed
|
|
154
|
+
puts "Connection error: #{e.message}"
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Content Moderation
|
|
159
|
+
|
|
160
|
+
The API may flag content violations:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
response = client.images.create(prompt: "...")
|
|
164
|
+
|
|
165
|
+
if response.content_violation?
|
|
166
|
+
puts "Content was flagged by moderation"
|
|
167
|
+
end
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Configuration Options
|
|
171
|
+
|
|
172
|
+
| Option | Default | Description |
|
|
173
|
+
|--------|---------|-------------|
|
|
174
|
+
| `api_key` | `ENV["REVE_AI_API_KEY"]` | Your Reve API key |
|
|
175
|
+
| `base_url` | `https://api.reve.com` | API base URL |
|
|
176
|
+
| `timeout` | `120` | Request timeout in seconds |
|
|
177
|
+
| `open_timeout` | `30` | Connection timeout in seconds |
|
|
178
|
+
| `max_retries` | `2` | Number of retries for failed requests |
|
|
179
|
+
| `logger` | `nil` | Logger instance for debugging |
|
|
180
|
+
| `debug` | `false` | Enable debug logging |
|
|
181
|
+
|
|
182
|
+
### Validation Constraints
|
|
183
|
+
|
|
184
|
+
| Constraint | Value |
|
|
185
|
+
|------------|-------|
|
|
186
|
+
| Max prompt length | 2560 characters |
|
|
187
|
+
| Max reference images (remix) | 6 |
|
|
188
|
+
| Valid aspect ratios | 16:9, 9:16, 3:2, 2:3, 4:3, 3:4, 1:1 |
|
|
189
|
+
|
|
190
|
+
## Development
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
bundle install
|
|
194
|
+
bundle exec rake test
|
|
195
|
+
bundle exec rubocop
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Contributing
|
|
199
|
+
|
|
200
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/dpaluy/reve_ai.
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
require "rubocop/rake_task"
|
|
6
|
+
|
|
7
|
+
Rake::TestTask.new(:test) do |t|
|
|
8
|
+
t.libs << "test"
|
|
9
|
+
t.libs << "lib"
|
|
10
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
RuboCop::RakeTask.new
|
|
14
|
+
|
|
15
|
+
task default: %i[test rubocop]
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReveAI
|
|
4
|
+
# HTTP client for the Reve image generation API.
|
|
5
|
+
#
|
|
6
|
+
# Provides access to all Reve API endpoints through resource objects.
|
|
7
|
+
# Configure globally via {ReveAI.configure} or pass parameters directly
|
|
8
|
+
# to the constructor.
|
|
9
|
+
#
|
|
10
|
+
# @example Using global configuration
|
|
11
|
+
# ReveAI.configure do |config|
|
|
12
|
+
# config.api_key = ENV["REVE_AI_API_KEY"]
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# client = ReveAI::Client.new
|
|
16
|
+
# result = client.images.create(prompt: "A cat wearing a hat")
|
|
17
|
+
#
|
|
18
|
+
# @example Using per-instance configuration
|
|
19
|
+
# client = ReveAI::Client.new(
|
|
20
|
+
# api_key: "your-key",
|
|
21
|
+
# timeout: 60
|
|
22
|
+
# )
|
|
23
|
+
#
|
|
24
|
+
# @see Resources::Images
|
|
25
|
+
# @see Configuration
|
|
26
|
+
class Client
|
|
27
|
+
# @return [Configuration] Configuration instance for this client
|
|
28
|
+
attr_reader :configuration
|
|
29
|
+
|
|
30
|
+
# Creates a new Reve API client.
|
|
31
|
+
#
|
|
32
|
+
# @param api_key [String, nil] API key (defaults to global config or ENV["REVE_AI_API_KEY"])
|
|
33
|
+
# @param options [Hash] Additional configuration options
|
|
34
|
+
# @option options [String] :base_url Base URL for API requests
|
|
35
|
+
# @option options [Integer] :timeout Request timeout in seconds
|
|
36
|
+
# @option options [Integer] :open_timeout Connection timeout in seconds
|
|
37
|
+
# @option options [Integer] :max_retries Number of retry attempts
|
|
38
|
+
# @option options [Logger] :logger Logger instance for debugging
|
|
39
|
+
# @option options [Boolean] :debug Enable debug logging
|
|
40
|
+
#
|
|
41
|
+
# @raise [ConfigurationError] if api_key is missing or empty
|
|
42
|
+
#
|
|
43
|
+
# @example
|
|
44
|
+
# client = ReveAI::Client.new(api_key: "your-api-key")
|
|
45
|
+
def initialize(api_key: nil, **options)
|
|
46
|
+
@configuration = build_configuration(api_key, options)
|
|
47
|
+
validate_configuration!
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Returns the Images resource for image generation operations.
|
|
51
|
+
#
|
|
52
|
+
# @return [Resources::Images] Image operations interface
|
|
53
|
+
# @see Resources::Images
|
|
54
|
+
#
|
|
55
|
+
# @example Generate an image
|
|
56
|
+
# result = client.images.create(prompt: "A sunset over mountains")
|
|
57
|
+
# puts result.base64 # Base64 encoded PNG
|
|
58
|
+
#
|
|
59
|
+
# @example Edit an image
|
|
60
|
+
# result = client.images.edit(
|
|
61
|
+
# edit_instruction: "Make the sky more blue",
|
|
62
|
+
# reference_image: base64_encoded_image
|
|
63
|
+
# )
|
|
64
|
+
#
|
|
65
|
+
# @example Remix images
|
|
66
|
+
# result = client.images.remix(
|
|
67
|
+
# prompt: "Combine <img>1</img> and <img>2</img> into one scene",
|
|
68
|
+
# reference_images: [image1_base64, image2_base64]
|
|
69
|
+
# )
|
|
70
|
+
def images
|
|
71
|
+
@images ||= Resources::Images.new(self)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Returns the HTTP client for making API requests.
|
|
75
|
+
#
|
|
76
|
+
# @return [HTTP::Client] HTTP client instance
|
|
77
|
+
# @api private
|
|
78
|
+
def http_client
|
|
79
|
+
@http_client ||= HTTP::Client.new(configuration)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
# Builds configuration from global config and options.
|
|
85
|
+
#
|
|
86
|
+
# @param api_key [String, nil] API key override
|
|
87
|
+
# @param options [Hash] Configuration options
|
|
88
|
+
# @return [Configuration] Merged configuration
|
|
89
|
+
# @api private
|
|
90
|
+
def build_configuration(api_key, options)
|
|
91
|
+
config = ReveAI.configuration&.dup || Configuration.new
|
|
92
|
+
config.api_key = api_key if api_key
|
|
93
|
+
options.each { |key, value| config.public_send("#{key}=", value) }
|
|
94
|
+
config
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Validates that required configuration is present.
|
|
98
|
+
#
|
|
99
|
+
# @raise [ConfigurationError] if API key is missing
|
|
100
|
+
# @api private
|
|
101
|
+
def validate_configuration!
|
|
102
|
+
raise ConfigurationError, "API key is required" unless configuration.valid?
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReveAI
|
|
4
|
+
# Configuration settings for the ReveAI client.
|
|
5
|
+
#
|
|
6
|
+
# Stores API credentials and connection settings. Can be configured globally
|
|
7
|
+
# via {ReveAI.configure} or per-instance when creating a {Client}.
|
|
8
|
+
#
|
|
9
|
+
# @example Global configuration
|
|
10
|
+
# ReveAI.configure do |config|
|
|
11
|
+
# config.api_key = ENV["REVE_AI_API_KEY"]
|
|
12
|
+
# config.timeout = 120
|
|
13
|
+
# config.debug = true
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# @example Environment variable
|
|
17
|
+
# # Set REVE_AI_API_KEY environment variable
|
|
18
|
+
# export REVE_AI_API_KEY="your-api-key"
|
|
19
|
+
#
|
|
20
|
+
# # API key is automatically loaded
|
|
21
|
+
# client = ReveAI::Client.new
|
|
22
|
+
#
|
|
23
|
+
# @see Client
|
|
24
|
+
class Configuration
|
|
25
|
+
# @return [String] Default base URL for the Reve API
|
|
26
|
+
DEFAULT_BASE_URL = "https://api.reve.com"
|
|
27
|
+
|
|
28
|
+
# @return [Integer] Default request timeout in seconds (2 minutes for image generation)
|
|
29
|
+
DEFAULT_TIMEOUT = 120
|
|
30
|
+
|
|
31
|
+
# @return [Integer] Default connection open timeout in seconds
|
|
32
|
+
DEFAULT_OPEN_TIMEOUT = 30
|
|
33
|
+
|
|
34
|
+
# @return [Integer] Default number of retry attempts for failed requests
|
|
35
|
+
DEFAULT_MAX_RETRIES = 2
|
|
36
|
+
|
|
37
|
+
# @return [Array<String>] Valid aspect ratios for image generation
|
|
38
|
+
VALID_ASPECT_RATIOS = %w[16:9 9:16 3:2 2:3 4:3 3:4 1:1].freeze
|
|
39
|
+
|
|
40
|
+
# @return [Integer] Maximum allowed prompt length in characters
|
|
41
|
+
MAX_PROMPT_LENGTH = 2560
|
|
42
|
+
|
|
43
|
+
# @return [Integer] Maximum number of reference images for remix operations
|
|
44
|
+
MAX_REFERENCE_IMAGES = 6
|
|
45
|
+
|
|
46
|
+
# @return [String, nil] Reve API key for authentication
|
|
47
|
+
attr_accessor :api_key
|
|
48
|
+
|
|
49
|
+
# @return [String] Base URL for API requests (defaults to https://api.reve.com)
|
|
50
|
+
attr_accessor :base_url
|
|
51
|
+
|
|
52
|
+
# @return [Integer] Request timeout in seconds
|
|
53
|
+
attr_accessor :timeout
|
|
54
|
+
|
|
55
|
+
# @return [Integer] Connection open timeout in seconds
|
|
56
|
+
attr_accessor :open_timeout
|
|
57
|
+
|
|
58
|
+
# @return [Integer] Number of retry attempts for transient failures
|
|
59
|
+
attr_accessor :max_retries
|
|
60
|
+
|
|
61
|
+
# @return [Logger, nil] Logger instance for debug output
|
|
62
|
+
attr_accessor :logger
|
|
63
|
+
|
|
64
|
+
# @return [Boolean] Enable debug logging of HTTP requests/responses
|
|
65
|
+
attr_accessor :debug
|
|
66
|
+
|
|
67
|
+
# Creates a new configuration with default values.
|
|
68
|
+
#
|
|
69
|
+
# Automatically loads API key from the REVE_AI_API_KEY environment variable
|
|
70
|
+
# if present.
|
|
71
|
+
#
|
|
72
|
+
# @example
|
|
73
|
+
# config = ReveAI::Configuration.new
|
|
74
|
+
# config.api_key = "your-api-key"
|
|
75
|
+
# config.timeout = 60
|
|
76
|
+
def initialize
|
|
77
|
+
@api_key = ENV.fetch("REVE_AI_API_KEY", nil)
|
|
78
|
+
@base_url = DEFAULT_BASE_URL
|
|
79
|
+
@timeout = DEFAULT_TIMEOUT
|
|
80
|
+
@open_timeout = DEFAULT_OPEN_TIMEOUT
|
|
81
|
+
@max_retries = DEFAULT_MAX_RETRIES
|
|
82
|
+
@logger = nil
|
|
83
|
+
@debug = false
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Checks if the configuration has a valid API key.
|
|
87
|
+
#
|
|
88
|
+
# @return [Boolean] true if api_key is present and not empty
|
|
89
|
+
#
|
|
90
|
+
# @example
|
|
91
|
+
# config = ReveAI::Configuration.new
|
|
92
|
+
# config.valid? # => false
|
|
93
|
+
#
|
|
94
|
+
# config.api_key = "your-key"
|
|
95
|
+
# config.valid? # => true
|
|
96
|
+
def valid?
|
|
97
|
+
!api_key.nil? && !api_key.empty?
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReveAI
|
|
4
|
+
# Base error class for all ReveAI errors.
|
|
5
|
+
#
|
|
6
|
+
# All library exceptions inherit from this class.
|
|
7
|
+
#
|
|
8
|
+
# @example Catching all ReveAI errors
|
|
9
|
+
# begin
|
|
10
|
+
# client.images.create(prompt: "A cat")
|
|
11
|
+
# rescue ReveAI::Error => e
|
|
12
|
+
# puts "ReveAI error: #{e.message}"
|
|
13
|
+
# end
|
|
14
|
+
class Error < StandardError; end
|
|
15
|
+
|
|
16
|
+
# Raised when configuration is invalid or missing.
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# client = ReveAI::Client.new(api_key: nil)
|
|
20
|
+
# # => ReveAI::ConfigurationError: API key is required
|
|
21
|
+
class ConfigurationError < Error; end
|
|
22
|
+
|
|
23
|
+
# Raised when input validation fails before making an API call.
|
|
24
|
+
#
|
|
25
|
+
# @example Invalid prompt
|
|
26
|
+
# client.images.create(prompt: "")
|
|
27
|
+
# # => ReveAI::ValidationError: Prompt is required
|
|
28
|
+
#
|
|
29
|
+
# @example Invalid aspect ratio
|
|
30
|
+
# client.images.create(prompt: "A cat", aspect_ratio: "5:3")
|
|
31
|
+
# # => ReveAI::ValidationError: Invalid aspect_ratio '5:3'. Must be one of: 16:9, 9:16, ...
|
|
32
|
+
class ValidationError < Error; end
|
|
33
|
+
|
|
34
|
+
# Base class for network-level errors.
|
|
35
|
+
#
|
|
36
|
+
# Indicates connection or transport failures rather than API errors.
|
|
37
|
+
class NetworkError < Error; end
|
|
38
|
+
|
|
39
|
+
# Raised when a request times out.
|
|
40
|
+
#
|
|
41
|
+
# @example
|
|
42
|
+
# # Request exceeded timeout
|
|
43
|
+
# client.images.create(prompt: "A complex scene...")
|
|
44
|
+
# # => ReveAI::TimeoutError: Request timed out: execution expired
|
|
45
|
+
class TimeoutError < NetworkError; end
|
|
46
|
+
|
|
47
|
+
# Raised when unable to establish a connection to the API.
|
|
48
|
+
#
|
|
49
|
+
# @example
|
|
50
|
+
# # DNS failure or network unreachable
|
|
51
|
+
# # => ReveAI::ConnectionError: Connection failed: getaddrinfo: nodename nor servname provided
|
|
52
|
+
class ConnectionError < NetworkError; end
|
|
53
|
+
|
|
54
|
+
# Base class for API errors with HTTP status and response details.
|
|
55
|
+
#
|
|
56
|
+
# All API-related exceptions inherit from this class and include
|
|
57
|
+
# HTTP status code, response body, and headers for debugging.
|
|
58
|
+
#
|
|
59
|
+
# @example Handling API errors
|
|
60
|
+
# begin
|
|
61
|
+
# client.images.create(prompt: "...")
|
|
62
|
+
# rescue ReveAI::UnauthorizedError => e
|
|
63
|
+
# puts "Auth failed: #{e.message}"
|
|
64
|
+
# puts "Status: #{e.status}"
|
|
65
|
+
# rescue ReveAI::RateLimitError => e
|
|
66
|
+
# puts "Rate limited, retry after #{e.retry_after} seconds"
|
|
67
|
+
# rescue ReveAI::APIError => e
|
|
68
|
+
# puts "API error (#{e.status}): #{e.message}"
|
|
69
|
+
# puts "Request ID: #{e.request_id}"
|
|
70
|
+
# end
|
|
71
|
+
class APIError < Error
|
|
72
|
+
# @return [Integer, nil] HTTP status code
|
|
73
|
+
attr_reader :status
|
|
74
|
+
|
|
75
|
+
# @return [Hash] Response body parsed as Hash
|
|
76
|
+
attr_reader :body
|
|
77
|
+
|
|
78
|
+
# @return [Hash] Response headers
|
|
79
|
+
attr_reader :headers
|
|
80
|
+
|
|
81
|
+
# Creates a new API error instance.
|
|
82
|
+
#
|
|
83
|
+
# @param message [String, nil] Error message
|
|
84
|
+
# @param status [Integer, nil] HTTP status code
|
|
85
|
+
# @param body [Hash, nil] Response body
|
|
86
|
+
# @param headers [Hash, nil] Response headers
|
|
87
|
+
def initialize(message = nil, status: nil, body: nil, headers: nil)
|
|
88
|
+
@status = status
|
|
89
|
+
@body = body || {}
|
|
90
|
+
@headers = headers || {}
|
|
91
|
+
super(message)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns the request ID from response headers.
|
|
95
|
+
#
|
|
96
|
+
# Useful for debugging and support requests.
|
|
97
|
+
#
|
|
98
|
+
# @return [String, nil] Request ID if present
|
|
99
|
+
def request_id
|
|
100
|
+
headers["x-reve-request-id"]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Returns the error code from the response body.
|
|
104
|
+
#
|
|
105
|
+
# @return [String, nil] Error code (e.g., "PROMPT_TOO_LONG", "INVALID_API_KEY")
|
|
106
|
+
def error_code
|
|
107
|
+
body[:error_code]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Raised on 400 Bad Request responses.
|
|
112
|
+
#
|
|
113
|
+
# Indicates invalid request parameters or malformed request body.
|
|
114
|
+
#
|
|
115
|
+
# @example
|
|
116
|
+
# # Prompt too long
|
|
117
|
+
# client.images.create(prompt: "x" * 10000)
|
|
118
|
+
# # => ReveAI::BadRequestError: Prompt exceeds maximum length
|
|
119
|
+
class BadRequestError < APIError; end
|
|
120
|
+
|
|
121
|
+
# Raised on 401 Unauthorized responses.
|
|
122
|
+
#
|
|
123
|
+
# Indicates invalid or missing API key.
|
|
124
|
+
#
|
|
125
|
+
# @example
|
|
126
|
+
# client = ReveAI::Client.new(api_key: "invalid-key")
|
|
127
|
+
# client.images.create(prompt: "A cat")
|
|
128
|
+
# # => ReveAI::UnauthorizedError: Invalid API key
|
|
129
|
+
class UnauthorizedError < APIError; end
|
|
130
|
+
|
|
131
|
+
# Raised on 402 Payment Required responses.
|
|
132
|
+
#
|
|
133
|
+
# Indicates the account has run out of credits.
|
|
134
|
+
#
|
|
135
|
+
# @example
|
|
136
|
+
# # Account has insufficient credits
|
|
137
|
+
# client.images.create(prompt: "A cat")
|
|
138
|
+
# # => ReveAI::InsufficientCreditsError: Your budget has run out
|
|
139
|
+
class InsufficientCreditsError < APIError; end
|
|
140
|
+
|
|
141
|
+
# Raised on 403 Forbidden responses.
|
|
142
|
+
#
|
|
143
|
+
# Indicates the API key lacks permission for the requested operation.
|
|
144
|
+
class ForbiddenError < APIError; end
|
|
145
|
+
|
|
146
|
+
# Raised on 404 Not Found responses.
|
|
147
|
+
#
|
|
148
|
+
# Indicates the requested resource does not exist.
|
|
149
|
+
class NotFoundError < APIError; end
|
|
150
|
+
|
|
151
|
+
# Raised on 422 Unprocessable Entity responses.
|
|
152
|
+
#
|
|
153
|
+
# Indicates the inputs could not be understood or processed.
|
|
154
|
+
#
|
|
155
|
+
# @example
|
|
156
|
+
# # Invalid reference image format
|
|
157
|
+
# client.images.edit(edit_instruction: "...", reference_image: "not-valid-base64")
|
|
158
|
+
# # => ReveAI::UnprocessableEntityError: The inputs could not be understood
|
|
159
|
+
class UnprocessableEntityError < APIError; end
|
|
160
|
+
|
|
161
|
+
# Raised on 429 Too Many Requests responses.
|
|
162
|
+
#
|
|
163
|
+
# Indicates the rate limit has been exceeded. Check {#retry_after}
|
|
164
|
+
# to determine when to retry.
|
|
165
|
+
#
|
|
166
|
+
# @example
|
|
167
|
+
# begin
|
|
168
|
+
# client.images.create(prompt: "A cat")
|
|
169
|
+
# rescue ReveAI::RateLimitError => e
|
|
170
|
+
# sleep e.retry_after
|
|
171
|
+
# retry
|
|
172
|
+
# end
|
|
173
|
+
class RateLimitError < APIError
|
|
174
|
+
# Returns the retry-after header value in seconds.
|
|
175
|
+
#
|
|
176
|
+
# Indicates how long to wait before retrying the request.
|
|
177
|
+
#
|
|
178
|
+
# @return [Integer, nil] Seconds to wait before retrying
|
|
179
|
+
def retry_after
|
|
180
|
+
headers["retry-after"]&.to_i
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Raised on 5xx server error responses.
|
|
185
|
+
#
|
|
186
|
+
# Indicates an internal server error on the Reve API side.
|
|
187
|
+
# These are typically transient and can be retried.
|
|
188
|
+
#
|
|
189
|
+
# @example
|
|
190
|
+
# # Server error
|
|
191
|
+
# client.images.create(prompt: "A cat")
|
|
192
|
+
# # => ReveAI::ServerError: Internal server error
|
|
193
|
+
class ServerError < APIError; end
|
|
194
|
+
end
|