aigen-google 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.
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aigen
4
+ module Google
5
+ # GenerationConfig controls generation parameters for the Gemini API.
6
+ # Validates parameter ranges and raises InvalidRequestError before API calls.
7
+ #
8
+ # @example Basic configuration
9
+ # config = Aigen::Google::GenerationConfig.new(temperature: 0.7, max_output_tokens: 1024)
10
+ # config.to_h # => {temperature: 0.7, maxOutputTokens: 1024}
11
+ #
12
+ # @example All parameters
13
+ # config = Aigen::Google::GenerationConfig.new(
14
+ # temperature: 0.9,
15
+ # top_p: 0.95,
16
+ # top_k: 40,
17
+ # max_output_tokens: 2048
18
+ # )
19
+ class GenerationConfig
20
+ # Initializes a GenerationConfig instance with optional parameters.
21
+ # Validates all parameters and raises InvalidRequestError if invalid.
22
+ #
23
+ # @param temperature [Float, nil] controls randomness (0.0-1.0)
24
+ # @param top_p [Float, nil] nucleus sampling threshold (0.0-1.0)
25
+ # @param top_k [Integer, nil] top-k sampling limit (> 0)
26
+ # @param max_output_tokens [Integer, nil] maximum response tokens (> 0)
27
+ # @param response_modalities [Array<String>, nil] output modalities: ["TEXT"], ["IMAGE"], or ["TEXT", "IMAGE"]
28
+ # @param aspect_ratio [String, nil] image aspect ratio: "1:1", "16:9", "9:16", "4:3", "3:4", "5:4", "4:5"
29
+ # @param image_size [String, nil] image resolution: "1K", "2K", "4K" (uppercase required)
30
+ #
31
+ # @raise [InvalidRequestError] if any parameter is out of valid range
32
+ #
33
+ # @example
34
+ # config = GenerationConfig.new(temperature: 0.5)
35
+ #
36
+ # @example Image generation
37
+ # config = GenerationConfig.new(
38
+ # response_modalities: ["TEXT", "IMAGE"],
39
+ # aspect_ratio: "16:9",
40
+ # image_size: "2K"
41
+ # )
42
+ def initialize(temperature: nil, top_p: nil, top_k: nil, max_output_tokens: nil, response_modalities: nil, aspect_ratio: nil, image_size: nil)
43
+ validate_temperature(temperature) if temperature
44
+ validate_top_p(top_p) if top_p
45
+ validate_top_k(top_k) if top_k
46
+ validate_max_output_tokens(max_output_tokens) if max_output_tokens
47
+ validate_response_modalities(response_modalities) if response_modalities
48
+ validate_aspect_ratio(aspect_ratio) if aspect_ratio
49
+ validate_image_size(image_size) if image_size
50
+
51
+ @temperature = temperature
52
+ @top_p = top_p
53
+ @top_k = top_k
54
+ @max_output_tokens = max_output_tokens
55
+ @response_modalities = response_modalities
56
+ @aspect_ratio = aspect_ratio
57
+ @image_size = image_size
58
+ end
59
+
60
+ # Serializes the configuration to Gemini API format with camelCase keys.
61
+ # Omits nil values from the output.
62
+ #
63
+ # @return [Hash] the configuration hash with camelCase keys
64
+ #
65
+ # @example
66
+ # config = GenerationConfig.new(temperature: 0.7, max_output_tokens: 1024)
67
+ # config.to_h # => {temperature: 0.7, maxOutputTokens: 1024}
68
+ def to_h
69
+ result = {}
70
+ result[:temperature] = @temperature unless @temperature.nil?
71
+ result[:topP] = @top_p unless @top_p.nil?
72
+ result[:topK] = @top_k unless @top_k.nil?
73
+ result[:maxOutputTokens] = @max_output_tokens unless @max_output_tokens.nil?
74
+ result[:responseModalities] = @response_modalities unless @response_modalities.nil?
75
+
76
+ # Image generation parameters go under imageConfig
77
+ if @aspect_ratio || @image_size
78
+ image_config = {}
79
+ image_config[:aspectRatio] = @aspect_ratio unless @aspect_ratio.nil?
80
+ image_config[:imageSize] = @image_size unless @image_size.nil?
81
+ result[:imageConfig] = image_config
82
+ end
83
+
84
+ result
85
+ end
86
+
87
+ private
88
+
89
+ def validate_temperature(value)
90
+ unless value.between?(0.0, 1.0)
91
+ raise InvalidRequestError.new(
92
+ "temperature must be between 0.0 and 1.0, got #{value}",
93
+ status_code: nil
94
+ )
95
+ end
96
+ end
97
+
98
+ def validate_top_p(value)
99
+ unless value.between?(0.0, 1.0)
100
+ raise InvalidRequestError.new(
101
+ "top_p must be between 0.0 and 1.0, got #{value}",
102
+ status_code: nil
103
+ )
104
+ end
105
+ end
106
+
107
+ def validate_top_k(value)
108
+ unless value > 0
109
+ raise InvalidRequestError.new(
110
+ "top_k must be greater than 0, got #{value}",
111
+ status_code: nil
112
+ )
113
+ end
114
+ end
115
+
116
+ def validate_max_output_tokens(value)
117
+ unless value > 0
118
+ raise InvalidRequestError.new(
119
+ "max_output_tokens must be greater than 0, got #{value}",
120
+ status_code: nil
121
+ )
122
+ end
123
+ end
124
+
125
+ def validate_response_modalities(value)
126
+ unless value.is_a?(Array)
127
+ raise InvalidRequestError.new(
128
+ "response_modalities must be an array",
129
+ status_code: nil
130
+ )
131
+ end
132
+
133
+ if value.empty?
134
+ raise InvalidRequestError.new(
135
+ "response_modalities must not be empty",
136
+ status_code: nil
137
+ )
138
+ end
139
+
140
+ valid_modalities = ["TEXT", "IMAGE"]
141
+ invalid = value - valid_modalities
142
+ unless invalid.empty?
143
+ raise InvalidRequestError.new(
144
+ "response_modalities must only contain TEXT or IMAGE, got: #{invalid.join(", ")}",
145
+ status_code: nil
146
+ )
147
+ end
148
+ end
149
+
150
+ def validate_aspect_ratio(value)
151
+ valid_ratios = ["1:1", "16:9", "9:16", "4:3", "3:4", "5:4", "4:5"]
152
+ unless valid_ratios.include?(value)
153
+ raise InvalidRequestError.new(
154
+ "aspect_ratio must be one of #{valid_ratios.join(", ")}, got #{value}",
155
+ status_code: nil
156
+ )
157
+ end
158
+ end
159
+
160
+ def validate_image_size(value)
161
+ valid_sizes = ["1K", "2K", "4K"]
162
+ unless valid_sizes.include?(value)
163
+ raise InvalidRequestError.new(
164
+ "image_size must be one of #{valid_sizes.join(", ")}, got #{value}",
165
+ status_code: nil
166
+ )
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "json"
5
+
6
+ module Aigen
7
+ module Google
8
+ class HttpClient
9
+ BASE_URL = "https://generativelanguage.googleapis.com/v1beta"
10
+
11
+ def initialize(api_key:, timeout: 30, retry_count: 3)
12
+ @api_key = api_key
13
+ @timeout = timeout
14
+ @retry_count = retry_count
15
+ end
16
+
17
+ def post(path, payload, attempt: 1)
18
+ response = connection.post(path) do |req|
19
+ req.headers["Content-Type"] = "application/json"
20
+ req.headers["x-goog-api-key"] = @api_key
21
+ req.body = payload.to_json
22
+ end
23
+
24
+ handle_response(response, path, payload, attempt)
25
+ rescue Faraday::Error => e
26
+ # Handle timeout-like errors (including WebMock's .to_timeout)
27
+ is_timeout = e.is_a?(Faraday::TimeoutError) ||
28
+ e.is_a?(::Timeout::Error) ||
29
+ e.message.include?("execution expired") ||
30
+ e.message.include?("timed out")
31
+
32
+ if is_timeout
33
+ if attempt >= @retry_count
34
+ raise TimeoutError, "Request timed out after #{@retry_count} retries"
35
+ end
36
+ backoff_seconds = 2**(attempt - 1) # 1s, 2s, 4s
37
+ sleep backoff_seconds
38
+ post(path, payload, attempt: attempt + 1)
39
+ else
40
+ # Network connection errors
41
+ raise ServerError.new("Network error: #{e.message}", status_code: nil)
42
+ end
43
+ end
44
+
45
+ # Makes a streaming POST request to the Gemini API.
46
+ # Processes chunked responses and yields each parsed chunk to the provided block.
47
+ #
48
+ # @param path [String] the API endpoint path
49
+ # @param payload [Hash] the request payload (will be JSON encoded)
50
+ # @yieldparam chunk [Hash] parsed JSON chunk from the streaming response
51
+ #
52
+ # @return [nil] always returns nil when block is given
53
+ #
54
+ # @raise [ArgumentError] if no block is provided
55
+ # @raise [Aigen::Google::AuthenticationError] if API key is invalid (401/403)
56
+ # @raise [Aigen::Google::InvalidRequestError] if request is malformed (400/404)
57
+ # @raise [Aigen::Google::RateLimitError] if rate limit is exceeded (429)
58
+ # @raise [Aigen::Google::ServerError] if server error occurs (500+) or network error
59
+ #
60
+ # @example Stream generated content chunks
61
+ # http_client.post_stream("models/gemini-pro:streamGenerateContent", payload) do |chunk|
62
+ # text = chunk["candidates"][0]["content"]["parts"][0]["text"]
63
+ # print text
64
+ # end
65
+ def post_stream(path, payload, &block)
66
+ raise ArgumentError, "block required for streaming" unless block_given?
67
+
68
+ buffer = ""
69
+
70
+ response = connection.post(path) do |req|
71
+ req.headers["Content-Type"] = "application/json"
72
+ req.headers["x-goog-api-key"] = @api_key
73
+ req.body = payload.to_json
74
+
75
+ req.options.on_data = proc do |chunk, _total_bytes|
76
+ buffer += chunk
77
+
78
+ # Process complete lines (newline-delimited JSON)
79
+ while (newline_index = buffer.index("\n"))
80
+ line = buffer.slice!(0, newline_index + 1).strip
81
+ next if line.empty?
82
+
83
+ begin
84
+ parsed_chunk = JSON.parse(line)
85
+ block.call(parsed_chunk)
86
+ rescue JSON::ParserError => e
87
+ raise ServerError.new("Invalid JSON in stream chunk: #{e.message}", status_code: nil)
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ # Check for non-200 status codes
94
+ handle_stream_response_status(response)
95
+
96
+ nil
97
+ rescue Faraday::Error => e
98
+ raise ServerError.new("Network error during streaming: #{e.message}", status_code: nil)
99
+ end
100
+
101
+ private
102
+
103
+ def connection
104
+ @connection ||= Faraday.new(url: BASE_URL) do |conn|
105
+ conn.options.timeout = @timeout
106
+ conn.adapter Faraday.default_adapter do |http|
107
+ http.open_timeout = 5
108
+ http.read_timeout = @timeout
109
+ end
110
+ end
111
+ end
112
+
113
+ def handle_response(response, path, payload, attempt)
114
+ case response.status
115
+ when 200..299
116
+ begin
117
+ JSON.parse(response.body)
118
+ rescue JSON::ParserError => e
119
+ raise ServerError.new("Invalid JSON response from API: #{e.message}", status_code: response.status)
120
+ end
121
+ when 400
122
+ raise InvalidRequestError.new(extract_error_message(response), status_code: 400)
123
+ when 401, 403
124
+ raise AuthenticationError.new("Invalid API key. Get one at https://makersuite.google.com/app/apikey", status_code: response.status)
125
+ when 404
126
+ raise InvalidRequestError.new("Resource not found. Check model name and endpoint.", status_code: 404)
127
+ when 429
128
+ retry_response(path, payload, attempt, RateLimitError.new(extract_error_message(response), status_code: 429))
129
+ when 500..599
130
+ retry_response(path, payload, attempt, ServerError.new(extract_error_message(response), status_code: response.status))
131
+ else
132
+ raise ApiError.new("Unexpected status code: #{response.status}", status_code: response.status)
133
+ end
134
+ end
135
+
136
+ def retry_response(path, payload, attempt, error)
137
+ if attempt >= @retry_count
138
+ raise error
139
+ end
140
+
141
+ backoff_seconds = 2**(attempt - 1) # 1s, 2s, 4s
142
+ sleep backoff_seconds
143
+ post(path, payload, attempt: attempt + 1)
144
+ end
145
+
146
+ def extract_error_message(response)
147
+ body = JSON.parse(response.body)
148
+ body.dig("error", "message") || response.body
149
+ rescue JSON::ParserError
150
+ response.body
151
+ end
152
+
153
+ def handle_stream_response_status(response)
154
+ case response.status
155
+ when 200..299
156
+ # Success - streaming completed
157
+ when 400
158
+ raise InvalidRequestError.new(extract_error_message(response), status_code: 400)
159
+ when 401, 403
160
+ raise AuthenticationError.new("Invalid API key. Get one at https://makersuite.google.com/app/apikey", status_code: response.status)
161
+ when 404
162
+ raise InvalidRequestError.new("Resource not found. Check model name and endpoint.", status_code: 404)
163
+ when 429
164
+ raise RateLimitError.new(extract_error_message(response), status_code: 429)
165
+ when 500..599
166
+ raise ServerError.new(extract_error_message(response), status_code: response.status)
167
+ else
168
+ raise ApiError.new("Unexpected status code: #{response.status}", status_code: response.status)
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module Aigen
6
+ module Google
7
+ # Wraps an image generation API response with convenient helper methods.
8
+ # Provides easy access to generated images, text descriptions, and status information.
9
+ #
10
+ # @example Basic usage
11
+ # response = client.generate_image("A cute puppy")
12
+ # if response.success?
13
+ # response.save("puppy.png")
14
+ # puts response.text
15
+ # end
16
+ #
17
+ # @example Checking for failures
18
+ # response = client.generate_image("problematic prompt")
19
+ # unless response.success?
20
+ # puts "Failed: #{response.failure_reason}"
21
+ # puts response.failure_message
22
+ # end
23
+ class ImageResponse
24
+ attr_reader :raw_response
25
+
26
+ # Creates a new ImageResponse from a Gemini API response.
27
+ #
28
+ # @param response [Hash] the raw API response hash
29
+ def initialize(response)
30
+ @raw_response = response
31
+ end
32
+
33
+ # Checks if the image generation was successful.
34
+ #
35
+ # @return [Boolean] true if generation succeeded, false otherwise
36
+ def success?
37
+ finish_reason == "STOP"
38
+ end
39
+
40
+ # Checks if an image is present in the response.
41
+ #
42
+ # @return [Boolean] true if image data exists, false otherwise
43
+ def has_image?
44
+ !image_part.nil?
45
+ end
46
+
47
+ # Returns the text description that accompanied the generated image.
48
+ #
49
+ # @return [String, nil] the text description or nil if not present
50
+ def text
51
+ text_part&.dig("text")
52
+ end
53
+
54
+ # Returns the decoded binary image data.
55
+ #
56
+ # @return [String, nil] binary image data or nil if no image present
57
+ def image_data
58
+ return nil unless has_image?
59
+
60
+ Base64.decode64(image_part["inlineData"]["data"])
61
+ end
62
+
63
+ # Returns the MIME type of the generated image.
64
+ #
65
+ # @return [String, nil] MIME type (e.g., "image/png") or nil if no image
66
+ def mime_type
67
+ image_part&.dig("inlineData", "mimeType")
68
+ end
69
+
70
+ # Saves the generated image to the specified file path.
71
+ #
72
+ # @param path [String] the file path to save the image
73
+ # @raise [Aigen::Google::Error] if no image data is present
74
+ #
75
+ # @example
76
+ # response.save("output.png")
77
+ def save(path)
78
+ raise Error, "No image data to save" unless has_image?
79
+
80
+ File.write(path, image_data)
81
+ end
82
+
83
+ # Returns the finish reason for failed generations.
84
+ #
85
+ # @return [String, nil] the finish reason or nil if successful
86
+ def failure_reason
87
+ return nil if success?
88
+
89
+ candidate.dig("finishReason")
90
+ end
91
+
92
+ # Returns the failure message for failed generations.
93
+ #
94
+ # @return [String, nil] the failure message or nil if successful
95
+ def failure_message
96
+ return nil if success?
97
+
98
+ candidate.dig("finishMessage")
99
+ end
100
+
101
+ private
102
+
103
+ def candidate
104
+ @raw_response.dig("candidates", 0) || {}
105
+ end
106
+
107
+ def parts
108
+ candidate.dig("content", "parts") || []
109
+ end
110
+
111
+ def text_part
112
+ parts.find { |p| p.key?("text") }
113
+ end
114
+
115
+ def image_part
116
+ parts.find { |p| p.key?("inlineData") }
117
+ end
118
+
119
+ def finish_reason
120
+ candidate.dig("finishReason")
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aigen
4
+ module Google
5
+ # SafetySettings configures content filtering for the Gemini API.
6
+ # Provides constants for harm categories and thresholds, with sensible defaults.
7
+ #
8
+ # @example Using default settings (BLOCK_MEDIUM_AND_ABOVE for all categories)
9
+ # settings = Aigen::Google::SafetySettings.default
10
+ #
11
+ # @example Custom settings
12
+ # settings = Aigen::Google::SafetySettings.new([
13
+ # {
14
+ # category: Aigen::Google::SafetySettings::HARM_CATEGORY_HATE_SPEECH,
15
+ # threshold: Aigen::Google::SafetySettings::BLOCK_LOW_AND_ABOVE
16
+ # }
17
+ # ])
18
+ class SafetySettings
19
+ # Harm category constants
20
+ HARM_CATEGORY_HATE_SPEECH = "HARM_CATEGORY_HATE_SPEECH"
21
+ HARM_CATEGORY_DANGEROUS_CONTENT = "HARM_CATEGORY_DANGEROUS_CONTENT"
22
+ HARM_CATEGORY_HARASSMENT = "HARM_CATEGORY_HARASSMENT"
23
+ HARM_CATEGORY_SEXUALLY_EXPLICIT = "HARM_CATEGORY_SEXUALLY_EXPLICIT"
24
+
25
+ # Threshold constants
26
+ BLOCK_NONE = "BLOCK_NONE"
27
+ BLOCK_LOW_AND_ABOVE = "BLOCK_LOW_AND_ABOVE"
28
+ BLOCK_MEDIUM_AND_ABOVE = "BLOCK_MEDIUM_AND_ABOVE"
29
+ BLOCK_ONLY_HIGH = "BLOCK_ONLY_HIGH"
30
+
31
+ # Returns default safety settings with BLOCK_MEDIUM_AND_ABOVE for all categories.
32
+ #
33
+ # @return [Array<Hash>] array of default safety settings
34
+ #
35
+ # @example
36
+ # defaults = SafetySettings.default
37
+ # # => [
38
+ # # {category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_MEDIUM_AND_ABOVE"},
39
+ # # {category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_MEDIUM_AND_ABOVE"},
40
+ # # ...
41
+ # # ]
42
+ def self.default
43
+ [
44
+ {category: HARM_CATEGORY_HATE_SPEECH, threshold: BLOCK_MEDIUM_AND_ABOVE},
45
+ {category: HARM_CATEGORY_DANGEROUS_CONTENT, threshold: BLOCK_MEDIUM_AND_ABOVE},
46
+ {category: HARM_CATEGORY_HARASSMENT, threshold: BLOCK_MEDIUM_AND_ABOVE},
47
+ {category: HARM_CATEGORY_SEXUALLY_EXPLICIT, threshold: BLOCK_MEDIUM_AND_ABOVE}
48
+ ]
49
+ end
50
+
51
+ # Initializes a SafetySettings instance with an array of settings.
52
+ #
53
+ # @param settings [Array<Hash>] array of safety settings
54
+ # Each setting: {category: "HARM_CATEGORY_...", threshold: "BLOCK_..."}
55
+ #
56
+ # @example
57
+ # settings = SafetySettings.new([
58
+ # {category: HARM_CATEGORY_HATE_SPEECH, threshold: BLOCK_LOW_AND_ABOVE}
59
+ # ])
60
+ def initialize(settings)
61
+ @settings = settings
62
+ end
63
+
64
+ # Serializes the safety settings to Gemini API format.
65
+ #
66
+ # @return [Array<Hash>] the settings array in API format
67
+ #
68
+ # @example
69
+ # settings = SafetySettings.new([
70
+ # {category: HARM_CATEGORY_HATE_SPEECH, threshold: BLOCK_MEDIUM_AND_ABOVE}
71
+ # ])
72
+ # settings.to_h # => [{category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_MEDIUM_AND_ABOVE"}]
73
+ def to_h
74
+ @settings
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aigen
4
+ module Google
5
+ def self.gem_version
6
+ Gem::Version.new(VERSION::STRING)
7
+ end
8
+
9
+ module VERSION
10
+ MAJOR = 0
11
+ MINOR = 1
12
+ TINY = 0
13
+ PRE = nil
14
+ BUILD = nil
15
+ STRING = [MAJOR, MINOR, TINY, PRE, BUILD].compact.join(".")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "google/version"
4
+ require_relative "google/errors"
5
+ require_relative "google/configuration"
6
+ require_relative "google/content"
7
+ require_relative "google/generation_config"
8
+ require_relative "google/safety_settings"
9
+ require_relative "google/http_client"
10
+ require_relative "google/image_response"
11
+ require_relative "google/chat"
12
+ require_relative "google/client"
13
+
14
+ module Aigen
15
+ module Google
16
+ class << self
17
+ attr_accessor :configuration
18
+
19
+ def configure
20
+ self.configuration ||= Configuration.new
21
+ yield(configuration) if block_given?
22
+ configuration
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,6 @@
1
+ module Aigen
2
+ module Google
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aigen-google
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Lauri Jutila
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: faraday
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.0'
26
+ description: A Ruby-native SDK for Google's Generative AI (Gemini) APIs, providing
27
+ text generation, chat, streaming, multimodal content, and image generation (Nano
28
+ Banana) support.
29
+ email:
30
+ - ljuti@nmux.dev
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".rspec"
36
+ - ".standard.yml"
37
+ - CHANGELOG.md
38
+ - LICENSE.txt
39
+ - README.md
40
+ - Rakefile
41
+ - lib/aigen/google.rb
42
+ - lib/aigen/google/chat.rb
43
+ - lib/aigen/google/client.rb
44
+ - lib/aigen/google/configuration.rb
45
+ - lib/aigen/google/content.rb
46
+ - lib/aigen/google/errors.rb
47
+ - lib/aigen/google/generation_config.rb
48
+ - lib/aigen/google/http_client.rb
49
+ - lib/aigen/google/image_response.rb
50
+ - lib/aigen/google/safety_settings.rb
51
+ - lib/aigen/google/version.rb
52
+ - sig/aigen/google.rbs
53
+ homepage: https://github.com/neuralmux/aigen-google
54
+ licenses:
55
+ - MIT
56
+ metadata:
57
+ homepage_uri: https://github.com/neuralmux/aigen-google
58
+ source_code_uri: https://github.com/neuralmux/aigen-google
59
+ changelog_uri: https://github.com/neuralmux/aigen-google/blob/main/CHANGELOG.md
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 3.1.0
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.6.9
75
+ specification_version: 4
76
+ summary: Ruby SDK for Google Generative AI (Gemini API)
77
+ test_files: []