geminize 1.0.0 → 1.2.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/mcp.json +3 -0
  3. data/.cursor/rules/isolation_rules/Core/command-execution.mdc +235 -0
  4. data/.cursor/rules/isolation_rules/Core/complexity-decision-tree.mdc +187 -0
  5. data/.cursor/rules/isolation_rules/Core/creative-phase-enforcement.mdc +145 -0
  6. data/.cursor/rules/isolation_rules/Core/creative-phase-metrics.mdc +195 -0
  7. data/.cursor/rules/isolation_rules/Core/file-verification.mdc +198 -0
  8. data/.cursor/rules/isolation_rules/Core/platform-awareness.mdc +71 -0
  9. data/.cursor/rules/isolation_rules/Level3/planning-comprehensive.mdc +159 -0
  10. data/.cursor/rules/isolation_rules/Level3/task-tracking-intermediate.mdc +135 -0
  11. data/.cursor/rules/isolation_rules/Phases/CreativePhase/creative-phase-architecture.mdc +187 -0
  12. data/.cursor/rules/isolation_rules/main.mdc +123 -0
  13. data/.cursor/rules/isolation_rules/visual-maps/archive-mode-map.mdc +277 -0
  14. data/.cursor/rules/isolation_rules/visual-maps/creative-mode-map.mdc +224 -0
  15. data/.cursor/rules/isolation_rules/visual-maps/implement-mode-map.mdc +321 -0
  16. data/.cursor/rules/isolation_rules/visual-maps/plan-mode-map.mdc +269 -0
  17. data/.cursor/rules/isolation_rules/visual-maps/qa-mode-map.mdc +495 -0
  18. data/.cursor/rules/isolation_rules/visual-maps/reflect-mode-map.mdc +234 -0
  19. data/.cursor/rules/isolation_rules/visual-maps/van-mode-map.mdc +902 -0
  20. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-complexity-determination.mdc +60 -0
  21. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-file-verification.mdc +49 -0
  22. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-mode-map.mdc +49 -0
  23. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-platform-detection.mdc +50 -0
  24. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-qa-checks/build-test.mdc +117 -0
  25. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-qa-checks/config-check.mdc +103 -0
  26. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-qa-checks/dependency-check.mdc +147 -0
  27. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-qa-checks/environment-check.mdc +104 -0
  28. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-qa-checks/file-verification.mdc +1 -0
  29. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-qa-main.mdc +142 -0
  30. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-qa-utils/common-fixes.mdc +92 -0
  31. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-qa-utils/mode-transitions.mdc +101 -0
  32. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-qa-utils/reports.mdc +149 -0
  33. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-qa-utils/rule-calling-guide.mdc +66 -0
  34. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-qa-utils/rule-calling-help.mdc +19 -0
  35. data/.cursor/rules/isolation_rules/visual-maps/van_mode_split/van-qa-validation.md.old +363 -0
  36. data/.env.example +7 -0
  37. data/.memory_bank/activeContext.md +102 -0
  38. data/.memory_bank/progress.md +93 -0
  39. data/.memory_bank/projectbrief.md +45 -0
  40. data/.memory_bank/systemPatterns.md +90 -0
  41. data/.memory_bank/tasks.md +142 -0
  42. data/.memory_bank/techContext.md +73 -0
  43. data/.tool-versions +1 -0
  44. data/CHANGELOG.md +42 -0
  45. data/README.md +223 -5
  46. data/examples/function_calling.rb +218 -0
  47. data/examples/models_api.rb +125 -0
  48. data/examples/safety_settings.rb +82 -0
  49. data/lib/geminize/configuration.rb +4 -4
  50. data/lib/geminize/model_info.rb +87 -8
  51. data/lib/geminize/models/content_request_extensions.rb +219 -0
  52. data/lib/geminize/models/content_request_safety.rb +123 -0
  53. data/lib/geminize/models/content_response_extensions.rb +120 -0
  54. data/lib/geminize/models/function_declaration.rb +112 -0
  55. data/lib/geminize/models/function_response.rb +70 -0
  56. data/lib/geminize/models/model.rb +101 -109
  57. data/lib/geminize/models/model_list.rb +70 -28
  58. data/lib/geminize/models/safety_setting.rb +102 -0
  59. data/lib/geminize/models/tool.rb +47 -0
  60. data/lib/geminize/models/tool_config.rb +52 -0
  61. data/lib/geminize/module_extensions.rb +228 -0
  62. data/lib/geminize/module_safety.rb +135 -0
  63. data/lib/geminize/request_builder.rb +29 -0
  64. data/lib/geminize/version.rb +1 -1
  65. data/lib/geminize.rb +83 -14
  66. metadata +57 -2
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geminize
4
+ module Models
5
+ # Extends ContentRequest with function calling and JSON mode support
6
+ class ContentRequest
7
+ # Additional attributes for function calling and JSON mode
8
+
9
+ # @return [Array<Geminize::Models::Tool>] The tools for this request
10
+ attr_reader :tools
11
+
12
+ # @return [Geminize::Models::ToolConfig, nil] The tool configuration
13
+ attr_reader :tool_config
14
+
15
+ # @return [String, nil] The MIME type for the response format
16
+ attr_accessor :response_mime_type
17
+
18
+ # Add a function to the request
19
+ # @param name [String] The name of the function
20
+ # @param description [String] A description of what the function does
21
+ # @param parameters [Hash] JSON schema for function parameters
22
+ # @return [self] The request object for chaining
23
+ # @raise [Geminize::ValidationError] If the function is invalid
24
+ def add_function(name, description, parameters)
25
+ @tools ||= []
26
+
27
+ function_declaration = FunctionDeclaration.new(name, description, parameters)
28
+ tool = Tool.new(function_declaration)
29
+
30
+ @tools << tool
31
+ self
32
+ end
33
+
34
+ # Set the tool config for function execution
35
+ # @param execution_mode [String] The execution mode for functions ("AUTO", "MANUAL", or "NONE")
36
+ # @return [self] The request object for chaining
37
+ # @raise [Geminize::ValidationError] If the tool config is invalid
38
+ def set_tool_config(execution_mode = "AUTO")
39
+ @tool_config = ToolConfig.new(execution_mode)
40
+ self
41
+ end
42
+
43
+ # Enable JSON mode for structured output
44
+ # @return [self] The request object for chaining
45
+ def enable_json_mode
46
+ @response_mime_type = "application/json"
47
+ self
48
+ end
49
+
50
+ # Disable JSON mode and return to regular text output
51
+ # @return [self] The request object for chaining
52
+ def disable_json_mode
53
+ @response_mime_type = nil
54
+ self
55
+ end
56
+
57
+ # Override the to_hash method to include additional function calling features
58
+ # @return [Hash] The request as a hash
59
+ def to_hash
60
+ # First get the base implementation's hash by calling the standard method
61
+ # Use the implementation from ContentRequest directly
62
+ request = {
63
+ contents: [
64
+ {
65
+ parts: @content_parts.map do |part|
66
+ if part[:type] == "text"
67
+ {text: part[:text]}
68
+ elsif part[:type] == "image"
69
+ {
70
+ inlineData: {
71
+ mimeType: part[:mime_type],
72
+ data: part[:data]
73
+ }
74
+ }
75
+ end
76
+ end.compact
77
+ }
78
+ ]
79
+ }
80
+
81
+ # Add generation config
82
+ if @temperature || @max_tokens || @top_p || @top_k || @stop_sequences
83
+ request[:generationConfig] = {}
84
+ request[:generationConfig][:temperature] = @temperature if @temperature
85
+ request[:generationConfig][:maxOutputTokens] = @max_tokens if @max_tokens
86
+ request[:generationConfig][:topP] = @top_p if @top_p
87
+ request[:generationConfig][:topK] = @top_k if @top_k
88
+ request[:generationConfig][:stopSequences] = @stop_sequences if @stop_sequences
89
+ end
90
+
91
+ # Add system instruction
92
+ if @system_instruction
93
+ request[:systemInstruction] = {
94
+ parts: [
95
+ {
96
+ text: @system_instruction
97
+ }
98
+ ]
99
+ }
100
+ end
101
+
102
+ # Add tools if present
103
+ if @tools && !@tools.empty?
104
+ request[:tools] = @tools.map(&:to_hash)
105
+ end
106
+
107
+ # Add tool config if present
108
+ if @tool_config
109
+ request[:toolConfig] = @tool_config.to_hash
110
+ end
111
+
112
+ # Add response format if JSON mode is enabled
113
+ if @response_mime_type
114
+ request[:generationConfig] ||= {}
115
+ request[:generationConfig][:responseSchema] = {
116
+ type: "object",
117
+ properties: {
118
+ # Add a sample property to satisfy the API requirement
119
+ # This is a generic structure that will be overridden by the model's understanding
120
+ # of what properties to include based on the prompt
121
+ result: {
122
+ type: "array",
123
+ items: {
124
+ type: "object",
125
+ properties: {
126
+ name: {type: "string"},
127
+ value: {type: "string"}
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ request[:generationConfig][:responseMimeType] = @response_mime_type
134
+ end
135
+
136
+ request
137
+ end
138
+
139
+ # Original validate! method includes validation for tools and JSON mode
140
+ alias_method :original_validate!, :validate!
141
+
142
+ def validate!
143
+ # Don't call super, instead call the original specific validations directly
144
+ validate_prompt!
145
+ validate_system_instruction! if @system_instruction
146
+ validate_temperature! if @temperature
147
+ validate_max_tokens! if @max_tokens
148
+ validate_top_p! if @top_p
149
+ validate_top_k! if @top_k
150
+ validate_stop_sequences! if @stop_sequences
151
+ validate_content_parts!
152
+
153
+ # Then validate our extensions
154
+ validate_tools!
155
+ validate_tool_config!
156
+ validate_response_mime_type!
157
+ true
158
+ end
159
+
160
+ private
161
+
162
+ # Validate the tools
163
+ # @raise [Geminize::ValidationError] If the tools are invalid
164
+ def validate_tools!
165
+ return if @tools.nil? || @tools.empty?
166
+
167
+ unless @tools.is_a?(Array)
168
+ raise Geminize::ValidationError.new(
169
+ "Tools must be an array, got #{@tools.class}",
170
+ "INVALID_ARGUMENT"
171
+ )
172
+ end
173
+
174
+ @tools.each_with_index do |tool, index|
175
+ unless tool.is_a?(Tool)
176
+ raise Geminize::ValidationError.new(
177
+ "Tool at index #{index} must be a Tool, got #{tool.class}",
178
+ "INVALID_ARGUMENT"
179
+ )
180
+ end
181
+ end
182
+ end
183
+
184
+ # Validate the tool config
185
+ # @raise [Geminize::ValidationError] If the tool config is invalid
186
+ def validate_tool_config!
187
+ return if @tool_config.nil?
188
+
189
+ unless @tool_config.is_a?(ToolConfig)
190
+ raise Geminize::ValidationError.new(
191
+ "Tool config must be a ToolConfig, got #{@tool_config.class}",
192
+ "INVALID_ARGUMENT"
193
+ )
194
+ end
195
+ end
196
+
197
+ # Validate the response MIME type
198
+ # @raise [Geminize::ValidationError] If the response MIME type is invalid
199
+ def validate_response_mime_type!
200
+ return if @response_mime_type.nil?
201
+
202
+ unless @response_mime_type.is_a?(String)
203
+ raise Geminize::ValidationError.new(
204
+ "Response MIME type must be a string, got #{@response_mime_type.class}",
205
+ "INVALID_ARGUMENT"
206
+ )
207
+ end
208
+
209
+ # For now, only allow JSON
210
+ unless @response_mime_type == "application/json"
211
+ raise Geminize::ValidationError.new(
212
+ "Response MIME type must be 'application/json', got #{@response_mime_type}",
213
+ "INVALID_ARGUMENT"
214
+ )
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geminize
4
+ module Models
5
+ # Extends ContentRequest with safety settings support
6
+ class ContentRequest
7
+ # @return [Array<Geminize::Models::SafetySetting>] The safety settings for this request
8
+ attr_reader :safety_settings
9
+
10
+ # Add a safety setting to the request
11
+ # @param category [String] The harm category this setting applies to
12
+ # @param threshold [String] The threshold level for filtering
13
+ # @return [self] The request object for chaining
14
+ # @raise [Geminize::ValidationError] If the safety setting is invalid
15
+ def add_safety_setting(category, threshold)
16
+ @safety_settings ||= []
17
+
18
+ safety_setting = SafetySetting.new(category, threshold)
19
+ @safety_settings << safety_setting
20
+
21
+ self
22
+ end
23
+
24
+ # Set default safety settings for all harm categories
25
+ # @param threshold [String] The threshold level to apply to all categories
26
+ # @return [self] The request object for chaining
27
+ # @raise [Geminize::ValidationError] If the threshold is invalid
28
+ def set_default_safety_settings(threshold)
29
+ @safety_settings = []
30
+
31
+ SafetySetting::HARM_CATEGORIES.each do |category|
32
+ add_safety_setting(category, threshold)
33
+ end
34
+
35
+ self
36
+ end
37
+
38
+ # Block all harmful content (most conservative setting)
39
+ # @return [self] The request object for chaining
40
+ def block_all_harmful_content
41
+ set_default_safety_settings("BLOCK_LOW_AND_ABOVE")
42
+ end
43
+
44
+ # Block only high-risk content (least conservative setting)
45
+ # @return [self] The request object for chaining
46
+ def block_only_high_risk_content
47
+ set_default_safety_settings("BLOCK_ONLY_HIGH")
48
+ end
49
+
50
+ # Remove all safety settings (use with caution)
51
+ # @return [self] The request object for chaining
52
+ def remove_safety_settings
53
+ @safety_settings = []
54
+ self
55
+ end
56
+
57
+ # Get the base to_hash method - this will use the one defined in content_request_extensions.rb if available
58
+ alias_method :safety_original_to_hash, :to_hash unless method_defined?(:safety_original_to_hash)
59
+
60
+ # Override the to_hash method to include safety settings
61
+ # @return [Hash] The request as a hash
62
+ def to_hash
63
+ # Get the base hash (will include tools if that extension is loaded)
64
+ request = defined?(original_to_hash) ? original_to_hash : safety_original_to_hash
65
+
66
+ # Add safety settings if present
67
+ if @safety_settings && !@safety_settings.empty?
68
+ request[:safetySettings] = @safety_settings.map(&:to_hash)
69
+ end
70
+
71
+ request
72
+ end
73
+
74
+ # Validate method for safety settings - should only be called if not overridden by content_request_extensions.rb
75
+ # If that file's validate! is called, it should also call validate_safety_settings!
76
+ def validate!
77
+ # Don't call super, instead call the necessary validations directly
78
+ # Check if original_validate! is defined from the extensions
79
+ if defined?(original_validate!)
80
+ original_validate!
81
+ else
82
+ # Call the original internal validation methods
83
+ validate_prompt!
84
+ validate_system_instruction! if @system_instruction
85
+ validate_temperature! if @temperature
86
+ validate_max_tokens! if @max_tokens
87
+ validate_top_p! if @top_p
88
+ validate_top_k! if @top_k
89
+ validate_stop_sequences! if @stop_sequences
90
+ validate_content_parts!
91
+ end
92
+
93
+ # Add our safety validation
94
+ validate_safety_settings!
95
+ true
96
+ end
97
+
98
+ private
99
+
100
+ # Validate the safety settings
101
+ # @raise [Geminize::ValidationError] If the safety settings are invalid
102
+ def validate_safety_settings!
103
+ return if @safety_settings.nil? || @safety_settings.empty?
104
+
105
+ unless @safety_settings.is_a?(Array)
106
+ raise Geminize::ValidationError.new(
107
+ "Safety settings must be an array, got #{@safety_settings.class}",
108
+ "INVALID_ARGUMENT"
109
+ )
110
+ end
111
+
112
+ @safety_settings.each_with_index do |setting, index|
113
+ unless setting.is_a?(SafetySetting)
114
+ raise Geminize::ValidationError.new(
115
+ "Safety setting at index #{index} must be a SafetySetting, got #{setting.class}",
116
+ "INVALID_ARGUMENT"
117
+ )
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geminize
4
+ module Models
5
+ # Extends ContentResponse with function calling response handling
6
+ class ContentResponse
7
+ # Get the function call information from the response
8
+ # @return [Geminize::Models::FunctionResponse, nil] Function call information or nil if not present
9
+ def function_call
10
+ return @function_call if defined?(@function_call)
11
+
12
+ @function_call = nil
13
+ candidates = @raw_response["candidates"]
14
+
15
+ if candidates && !candidates.empty?
16
+ content = candidates.first["content"]
17
+ if content && content["parts"] && !content["parts"].empty?
18
+ function_call_part = content["parts"].find { |part| part["functionCall"] || part["function_call"] }
19
+
20
+ if function_call_part
21
+ # Handle both "functionCall" and "function_call" formats (API may vary)
22
+ function_data = function_call_part["functionCall"] || function_call_part["function_call"]
23
+
24
+ if function_data
25
+ # Extract name and args - handle different API response formats
26
+ name = function_data["name"]
27
+ args = function_data["args"] || function_data["arguments"]
28
+
29
+ if name
30
+ @function_call = FunctionResponse.new(name, args || {})
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ @function_call
38
+ end
39
+
40
+ # Check if the response contains a function call
41
+ # @return [Boolean] True if the response contains a function call
42
+ def has_function_call?
43
+ !function_call.nil?
44
+ end
45
+
46
+ # Get structured JSON response if available
47
+ # @return [Hash, nil] Parsed JSON response or nil if not a JSON response
48
+ def json_response
49
+ return @json_response if defined?(@json_response)
50
+
51
+ @json_response = nil
52
+
53
+ if has_text?
54
+ begin
55
+ @json_response = JSON.parse(text)
56
+ rescue JSON::ParserError
57
+ # Try to parse any JSON-like content from the text
58
+ json_match = text.match(/```json\s*(.*?)\s*```/m) || text.match(/\{.*\}/m) || text.match(/\[.*\]/m)
59
+ if json_match
60
+ begin
61
+ @json_response = JSON.parse(json_match[1] || json_match[0])
62
+ rescue JSON::ParserError
63
+ # Still not valid JSON
64
+ @json_response = nil
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ @json_response
71
+ end
72
+
73
+ # Check if the response contains valid JSON
74
+ # @return [Boolean] True if the response contains valid JSON
75
+ def has_json_response?
76
+ !json_response.nil?
77
+ end
78
+
79
+ # Enhanced parse_response method to handle function calls
80
+ alias_method :original_parse_response, :parse_response
81
+
82
+ private
83
+
84
+ # Parse the response data and extract relevant information
85
+ def parse_response
86
+ original_parse_response
87
+ # Clear any cached function call before parsing again
88
+ remove_instance_variable(:@function_call) if defined?(@function_call)
89
+ parse_function_call
90
+ end
91
+
92
+ # Parse function call information from the response
93
+ def parse_function_call
94
+ candidates = @raw_response["candidates"]
95
+
96
+ if candidates && !candidates.empty?
97
+ content = candidates.first["content"]
98
+ if content && content["parts"] && !content["parts"].empty?
99
+ function_call_part = content["parts"].find { |part| part["functionCall"] || part["function_call"] }
100
+
101
+ if function_call_part
102
+ # Handle both "functionCall" and "function_call" formats (API may vary)
103
+ function_data = function_call_part["functionCall"] || function_call_part["function_call"]
104
+
105
+ if function_data
106
+ # Extract name and args - handle different API response formats
107
+ name = function_data["name"]
108
+ args = function_data["args"] || function_data["arguments"]
109
+
110
+ if name
111
+ @function_call = FunctionResponse.new(name, args || {})
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geminize
4
+ module Models
5
+ # Represents a function declaration for function calling in Gemini API
6
+ class FunctionDeclaration
7
+ # @return [String] Name of the function
8
+ attr_reader :name
9
+
10
+ # @return [String] Description of what the function does
11
+ attr_reader :description
12
+
13
+ # @return [Hash] JSON schema for function parameters
14
+ attr_reader :parameters
15
+
16
+ # Initialize a new function declaration
17
+ # @param name [String] The name of the function
18
+ # @param description [String] A description of what the function does
19
+ # @param parameters [Hash] JSON schema for function parameters
20
+ # @raise [Geminize::ValidationError] If the function declaration is invalid
21
+ def initialize(name, description, parameters)
22
+ @name = name
23
+ @description = description
24
+ @parameters = parameters
25
+ validate!
26
+ end
27
+
28
+ # Validate the function declaration
29
+ # @raise [Geminize::ValidationError] If the function declaration is invalid
30
+ # @return [Boolean] true if validation passes
31
+ def validate!
32
+ validate_name!
33
+ validate_description!
34
+ validate_parameters!
35
+ true
36
+ end
37
+
38
+ # Convert the function declaration to a hash for API requests
39
+ # @return [Hash] The function declaration as a hash
40
+ def to_hash
41
+ {
42
+ name: @name,
43
+ description: @description,
44
+ parameters: @parameters
45
+ }
46
+ end
47
+
48
+ # Alias for to_hash
49
+ # @return [Hash] The function declaration as a hash
50
+ def to_h
51
+ to_hash
52
+ end
53
+
54
+ private
55
+
56
+ # Validate the function name
57
+ # @raise [Geminize::ValidationError] If the name is invalid
58
+ def validate_name!
59
+ unless @name.is_a?(String)
60
+ raise Geminize::ValidationError.new(
61
+ "Function name must be a string, got #{@name.class}",
62
+ "INVALID_ARGUMENT"
63
+ )
64
+ end
65
+
66
+ if @name.empty?
67
+ raise Geminize::ValidationError.new(
68
+ "Function name cannot be empty",
69
+ "INVALID_ARGUMENT"
70
+ )
71
+ end
72
+ end
73
+
74
+ # Validate the function description
75
+ # @raise [Geminize::ValidationError] If the description is invalid
76
+ def validate_description!
77
+ unless @description.is_a?(String)
78
+ raise Geminize::ValidationError.new(
79
+ "Function description must be a string, got #{@description.class}",
80
+ "INVALID_ARGUMENT"
81
+ )
82
+ end
83
+
84
+ if @description.empty?
85
+ raise Geminize::ValidationError.new(
86
+ "Function description cannot be empty",
87
+ "INVALID_ARGUMENT"
88
+ )
89
+ end
90
+ end
91
+
92
+ # Validate the function parameters
93
+ # @raise [Geminize::ValidationError] If the parameters are invalid
94
+ def validate_parameters!
95
+ unless @parameters.is_a?(Hash)
96
+ raise Geminize::ValidationError.new(
97
+ "Function parameters must be a hash, got #{@parameters.class}",
98
+ "INVALID_ARGUMENT"
99
+ )
100
+ end
101
+
102
+ # Validate that the parameters follow JSON Schema format
103
+ unless @parameters.key?(:type) || @parameters.key?("type")
104
+ raise Geminize::ValidationError.new(
105
+ "Function parameters must include a 'type' field",
106
+ "INVALID_ARGUMENT"
107
+ )
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geminize
4
+ module Models
5
+ # Represents a function response from Gemini API
6
+ class FunctionResponse
7
+ # @return [String] The name of the function that was called
8
+ attr_reader :name
9
+
10
+ # @return [Hash, Array, String, Numeric, Boolean, nil] The response from the function
11
+ attr_reader :response
12
+
13
+ # Initialize a new function response
14
+ # @param name [String] The name of the function that was called
15
+ # @param response [Hash, Array, String, Numeric, Boolean, nil] The response from the function
16
+ def initialize(name, response)
17
+ @name = name
18
+ @response = response
19
+ validate!
20
+ end
21
+
22
+ # Validate the function response
23
+ # @raise [Geminize::ValidationError] If the function response is invalid
24
+ # @return [Boolean] true if validation passes
25
+ def validate!
26
+ if @name.nil? || @name.empty?
27
+ raise Geminize::ValidationError.new(
28
+ "Function name cannot be empty",
29
+ "INVALID_ARGUMENT"
30
+ )
31
+ end
32
+
33
+ true
34
+ end
35
+
36
+ # Create a FunctionResponse from a hash
37
+ # @param hash [Hash] The hash representation of a function response
38
+ # @return [Geminize::Models::FunctionResponse] The function response
39
+ # @raise [Geminize::ValidationError] If the hash is invalid
40
+ def self.from_hash(hash)
41
+ unless hash.is_a?(Hash)
42
+ raise Geminize::ValidationError.new(
43
+ "Expected a Hash, got #{hash.class}",
44
+ "INVALID_ARGUMENT"
45
+ )
46
+ end
47
+
48
+ name = hash["name"] || hash[:name]
49
+ response = hash["response"] || hash[:response]
50
+
51
+ new(name, response)
52
+ end
53
+
54
+ # Convert the function response to a hash
55
+ # @return [Hash] The function response as a hash
56
+ def to_hash
57
+ {
58
+ name: @name,
59
+ response: @response
60
+ }
61
+ end
62
+
63
+ # Alias for to_hash
64
+ # @return [Hash] The function response as a hash
65
+ def to_h
66
+ to_hash
67
+ end
68
+ end
69
+ end
70
+ end