Class: AgentHarness::Providers::Gemini
- Inherits:
-
Base
- Object
- Base
- AgentHarness::Providers::Gemini
show all
- Defined in:
- lib/agent_harness/providers/gemini.rb
Overview
Google Gemini CLI provider
Provides integration with the Google Gemini CLI tool.
Constant Summary
collapse
- MODEL_PATTERN =
Model name pattern for Gemini models
/^gemini-[\d.]+-(?:pro|flash|ultra)(?:-\d+)?$/i
- CLI_PACKAGE =
"@google/gemini-cli"
- SUPPORTED_CLI_VERSION =
"0.35.3"
- SUPPORTED_CLI_REQUIREMENT =
Gem::Requirement.new("= #{SUPPORTED_CLI_VERSION}").freeze
Constants inherited
from Base
Base::COMMON_ERROR_PATTERNS, Base::DEFAULT_SMOKE_TEST_CONTRACT
Instance Attribute Summary
Attributes inherited from Base
#config, #executor, #logger
Class Method Summary
collapse
Instance Method Summary
collapse
Methods inherited from Base
#configure, #initialize, #sandboxed_environment?, #send_message
Methods included from Adapter
#build_mcp_flags, #dangerous_mode_flags, #fetch_mcp_servers, included, #parse_rate_limit_reset, #send_message, #session_flags, #smoke_test, #smoke_test_contract, #supported_mcp_transports, #supports_dangerous_mode?, #supports_mcp?, #supports_sessions?, #validate_mcp_servers!
Class Method Details
.binary_name ⇒ Object
24
25
26
|
# File 'lib/agent_harness/providers/gemini.rb', line 24
def binary_name
"gemini"
end
|
.discover_models ⇒ Object
81
82
83
84
85
86
87
88
89
90
91
92
|
# File 'lib/agent_harness/providers/gemini.rb', line 81
def discover_models
return [] unless available?
[
{name: "gemini-2.0-flash", family: "gemini-2-0-flash", tier: "standard", provider: "gemini"},
{name: "gemini-2.5-pro", family: "gemini-2-5-pro", tier: "advanced", provider: "gemini"},
{name: "gemini-1.5-pro", family: "gemini-1-5-pro", tier: "standard", provider: "gemini"},
{name: "gemini-1.5-flash", family: "gemini-1-5-flash", tier: "mini", provider: "gemini"}
]
end
|
.firewall_requirements ⇒ Object
59
60
61
62
63
64
65
66
67
68
69
|
# File 'lib/agent_harness/providers/gemini.rb', line 59
def firewall_requirements
{
domains: [
"generativelanguage.googleapis.com",
"oauth2.googleapis.com",
"accounts.google.com",
"www.googleapis.com"
],
ip_ranges: []
}
end
|
.install_contract(version: SUPPORTED_CLI_VERSION) ⇒ Object
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
# File 'lib/agent_harness/providers/gemini.rb', line 33
def install_contract(version: SUPPORTED_CLI_VERSION)
parsed_version = begin
Gem::Version.new(version)
rescue ArgumentError
raise ArgumentError, "Unsupported Gemini CLI version #{version.inspect}. Supported requirement: #{SUPPORTED_CLI_REQUIREMENT}"
end
unless SUPPORTED_CLI_REQUIREMENT.satisfied_by?(parsed_version)
raise ArgumentError, "Unsupported Gemini CLI version #{version.inspect}. Supported requirement: #{SUPPORTED_CLI_REQUIREMENT}"
end
package_spec = "#{CLI_PACKAGE}@#{version}"
{
provider: provider_name,
source_type: :npm,
package_name: CLI_PACKAGE,
supported_version_requirement: SUPPORTED_CLI_REQUIREMENT,
default_version: SUPPORTED_CLI_VERSION,
resolved_version: version,
binary_name: binary_name,
install_command: ["npm", "install", "-g", "--ignore-scripts", package_spec],
install_command_string: "npm install -g --ignore-scripts #{package_spec}"
}
end
|
.instruction_file_paths ⇒ Object
71
72
73
74
75
76
77
78
79
|
# File 'lib/agent_harness/providers/gemini.rb', line 71
def instruction_file_paths
[
{
path: "GEMINI.md",
description: "Google Gemini agent instructions",
symlink: true
}
]
end
|
.model_family(provider_model_name) ⇒ Object
94
95
96
97
|
# File 'lib/agent_harness/providers/gemini.rb', line 94
def model_family(provider_model_name)
provider_model_name.sub(/-\d+$/, "")
end
|
.provider_model_name(family_name) ⇒ Object
99
100
101
|
# File 'lib/agent_harness/providers/gemini.rb', line 99
def provider_model_name(family_name)
family_name
end
|
.provider_name ⇒ Object
20
21
22
|
# File 'lib/agent_harness/providers/gemini.rb', line 20
def provider_name
:gemini
end
|
.smoke_test_contract ⇒ Object
.supports_model_family?(family_name) ⇒ Boolean
103
104
105
|
# File 'lib/agent_harness/providers/gemini.rb', line 103
def supports_model_family?(family_name)
MODEL_PATTERN.match?(family_name) || family_name.start_with?("gemini-")
end
|
Instance Method Details
#auth_status ⇒ Object
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
|
# File 'lib/agent_harness/providers/gemini.rb', line 192
def auth_status
api_key = [ENV["GEMINI_API_KEY"], ENV["GOOGLE_API_KEY"]].find { |key| key && !key.strip.empty? }
if api_key
return {valid: true, expires_at: nil, error: nil, auth_method: :api_key}
end
credentials = read_gemini_credentials
return {valid: false, expires_at: nil, error: "No Gemini credentials found. Run 'gemini auth login' or set GEMINI_API_KEY or GOOGLE_API_KEY"} unless credentials
token = credentials["access_token"] || credentials["oauth_token"]
unless token.is_a?(String) && !token.strip.empty?
return {valid: false, expires_at: nil, error: "No authentication token in Gemini credentials"}
end
expires_at = parse_gemini_expiry(credentials)
if expires_at && expires_at < Time.now
{valid: false, expires_at: expires_at, error: "Gemini session expired. Run 'gemini auth login' to re-authenticate"}
else
{valid: true, expires_at: expires_at, error: nil, auth_method: :oauth}
end
rescue IOError, JSON::ParserError => e
{valid: false, expires_at: nil, error: e.message}
end
|
#auth_type ⇒ Object
151
152
153
|
# File 'lib/agent_harness/providers/gemini.rb', line 151
def auth_type
:oauth
end
|
#capabilities ⇒ Object
139
140
141
142
143
144
145
146
147
148
149
|
# File 'lib/agent_harness/providers/gemini.rb', line 139
def capabilities
{
streaming: true,
file_upload: true,
vision: true,
tool_use: true,
json_mode: true,
mcp: false,
dangerous_mode: false
}
end
|
#configuration_schema ⇒ Object
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
# File 'lib/agent_harness/providers/gemini.rb', line 120
def configuration_schema
{
fields: [
{
name: :model,
type: :string,
label: "Model",
required: false,
hint: "Gemini model to use (e.g. gemini-2.5-pro, gemini-2.0-flash)",
accepts_arbitrary: true
}
],
auth_modes: [:api_key, :oauth],
openai_compatible: false
}
end
|
#display_name ⇒ Object
116
117
118
|
# File 'lib/agent_harness/providers/gemini.rb', line 116
def display_name
"Google Gemini"
end
|
#error_patterns ⇒ Object
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
|
# File 'lib/agent_harness/providers/gemini.rb', line 168
def error_patterns
{
rate_limited: [
/rate.?limit/i,
/quota.?exceeded/i,
/429/
],
auth_expired: [
/authentication/i,
/unauthorized/i,
/invalid.?credentials/i,
/login.*required/i,
/not.*logged.*in/i,
/credentials.*expired/i,
/account.*not.*verified/i
],
transient: [
/timeout/i,
/temporary/i,
/503/
]
}
end
|
#execution_semantics ⇒ Object
155
156
157
158
159
160
161
162
163
164
165
166
|
# File 'lib/agent_harness/providers/gemini.rb', line 155
def execution_semantics
{
prompt_delivery: :flag,
output_format: :text,
sandbox_aware: false,
uses_subcommand: false,
non_interactive_flag: nil,
legitimate_exit_codes: [0],
stderr_is_diagnostic: true,
parses_rate_limit_reset: false
}
end
|
#health_status ⇒ Object
216
217
218
219
220
221
222
223
224
225
226
227
|
# File 'lib/agent_harness/providers/gemini.rb', line 216
def health_status
unless self.class.available?
return {healthy: false, message: "Gemini CLI not found in PATH. Install from https://github.com/google-gemini/gemini-cli"}
end
auth = auth_status
unless auth[:valid]
return {healthy: false, message: auth[:error]}
end
{healthy: true, message: "Gemini CLI available and authenticated"}
end
|
#name ⇒ Object
112
113
114
|
# File 'lib/agent_harness/providers/gemini.rb', line 112
def name
"gemini"
end
|
#validate_config ⇒ Object
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
|
# File 'lib/agent_harness/providers/gemini.rb', line 229
def validate_config
errors = []
model = @config.model
if !model.nil? && !model.is_a?(String)
errors << "model must be a string"
elsif model.is_a?(String) && !model.empty?
unless self.class.supports_model_family?(model)
errors << "Unrecognized model '#{model}'. Expected a Gemini model (e.g., gemini-2.0-flash, gemini-2.5-pro)"
end
end
flags = @config.default_flags
unless flags.nil?
if flags.is_a?(Array)
invalid = flags.reject { |f| f.is_a?(String) }
errors << "default_flags contains non-string values" if invalid.any?
else
errors << "default_flags must be an array of strings"
end
end
{valid: errors.empty?, errors: errors}
end
|