riffer 0.27.2 → 0.29.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 (146) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/architecture.md +18 -11
  3. data/.agents/code-style.md +1 -1
  4. data/.agents/rbs-inline.md +2 -2
  5. data/.agents/testing.md +9 -5
  6. data/.release-please-manifest.json +1 -1
  7. data/AGENTS.md +17 -10
  8. data/CHANGELOG.md +31 -0
  9. data/README.md +17 -18
  10. data/Steepfile +7 -1
  11. data/docs/03_AGENTS.md +34 -3
  12. data/docs/04_AGENT_LIFECYCLE.md +134 -86
  13. data/docs/05_AGENT_LOOP.md +2 -2
  14. data/docs/06_TOOLS.md +9 -4
  15. data/docs/07_TOOL_ADVANCED.md +23 -19
  16. data/docs/08_MESSAGES.md +28 -31
  17. data/docs/09_STREAM_EVENTS.md +1 -1
  18. data/docs/10_CONFIGURATION.md +25 -15
  19. data/docs/providers/01_PROVIDERS.md +6 -0
  20. data/docs/providers/06_MOCK_PROVIDER.md +2 -1
  21. data/docs/providers/07_CUSTOM_PROVIDERS.md +4 -4
  22. data/docs/providers/08_GEMINI.md +2 -2
  23. data/docs/providers/09_OPENROUTER.md +242 -0
  24. data/lib/riffer/agent/config.rb +173 -0
  25. data/lib/riffer/agent/context.rb +125 -0
  26. data/lib/riffer/agent/response.rb +11 -2
  27. data/lib/riffer/agent/run.rb +308 -0
  28. data/lib/riffer/agent/session/repair.rb +112 -0
  29. data/lib/riffer/agent/session.rb +268 -0
  30. data/lib/riffer/{structured_output → agent/structured_output}/result.rb +1 -1
  31. data/lib/riffer/{structured_output.rb → agent/structured_output.rb} +4 -4
  32. data/lib/riffer/agent.rb +246 -684
  33. data/lib/riffer/config.rb +56 -7
  34. data/lib/riffer/evals/evaluator.rb +13 -3
  35. data/lib/riffer/evals/judge.rb +2 -2
  36. data/lib/riffer/evals/run_result.rb +2 -1
  37. data/lib/riffer/evals/scenario_result.rb +2 -1
  38. data/lib/riffer/guardrails/runner.rb +3 -2
  39. data/lib/riffer/helpers/call_or_value.rb +16 -0
  40. data/lib/riffer/helpers.rb +0 -1
  41. data/lib/riffer/mcp/authenticated_tool.rb +4 -0
  42. data/lib/riffer/mcp/client.rb +1 -1
  43. data/lib/riffer/mcp/registration.rb +2 -3
  44. data/lib/riffer/mcp/registry.rb +3 -1
  45. data/lib/riffer/mcp/tool_factory.rb +5 -0
  46. data/lib/riffer/messages/assistant.rb +9 -3
  47. data/lib/riffer/messages/base.rb +22 -0
  48. data/lib/riffer/messages/converter.rb +6 -6
  49. data/lib/riffer/{file_part.rb → messages/file_part.rb} +5 -5
  50. data/lib/riffer/messages/tool.rb +1 -1
  51. data/lib/riffer/messages/user.rb +4 -4
  52. data/lib/riffer/{boolean.rb → params/boolean.rb} +3 -3
  53. data/lib/riffer/{param.rb → params/param.rb} +6 -6
  54. data/lib/riffer/params.rb +27 -21
  55. data/lib/riffer/providers/amazon_bedrock.rb +19 -20
  56. data/lib/riffer/providers/anthropic.rb +27 -28
  57. data/lib/riffer/providers/base.rb +10 -9
  58. data/lib/riffer/providers/gemini.rb +15 -12
  59. data/lib/riffer/providers/mock.rb +41 -13
  60. data/lib/riffer/providers/open_ai.rb +24 -22
  61. data/lib/riffer/providers/open_router.rb +318 -0
  62. data/lib/riffer/providers/repository.rb +1 -0
  63. data/lib/riffer/{token_usage.rb → providers/token_usage.rb} +4 -4
  64. data/lib/riffer/providers.rb +1 -0
  65. data/lib/riffer/runner/fibers.rb +4 -3
  66. data/lib/riffer/runner/sequential.rb +1 -1
  67. data/lib/riffer/runner/threaded.rb +1 -1
  68. data/lib/riffer/runner.rb +1 -1
  69. data/lib/riffer/skills/activate_tool.rb +4 -3
  70. data/lib/riffer/skills/config.rb +1 -1
  71. data/lib/riffer/skills/context.rb +3 -3
  72. data/lib/riffer/skills/filesystem_backend.rb +7 -5
  73. data/lib/riffer/skills/markdown_adapter.rb +1 -1
  74. data/lib/riffer/skills/xml_adapter.rb +1 -1
  75. data/lib/riffer/stream_events/interrupt.rb +10 -3
  76. data/lib/riffer/stream_events/token_usage_done.rb +2 -2
  77. data/lib/riffer/stream_events/web_search_status.rb +1 -1
  78. data/lib/riffer/tool.rb +3 -3
  79. data/lib/riffer/{tool_runtime → tools/runtime}/fibers.rb +2 -2
  80. data/lib/riffer/{tool_runtime → tools/runtime}/inline.rb +1 -1
  81. data/lib/riffer/{tool_runtime → tools/runtime}/threaded.rb +2 -2
  82. data/lib/riffer/{tool_runtime.rb → tools/runtime.rb} +21 -15
  83. data/lib/riffer/{toolable.rb → tools/toolable.rb} +12 -9
  84. data/lib/riffer/version.rb +1 -1
  85. data/lib/riffer.rb +2 -1
  86. data/sig/generated/riffer/agent/config.rbs +119 -0
  87. data/sig/generated/riffer/agent/context.rbs +91 -0
  88. data/sig/generated/riffer/agent/response.rbs +10 -2
  89. data/sig/generated/riffer/agent/run.rbs +144 -0
  90. data/sig/generated/riffer/agent/session/repair.rbs +51 -0
  91. data/sig/generated/riffer/agent/session.rbs +145 -0
  92. data/sig/generated/riffer/{structured_output → agent/structured_output}/result.rbs +2 -2
  93. data/sig/generated/riffer/{structured_output.rbs → agent/structured_output.rbs} +6 -6
  94. data/sig/generated/riffer/agent.rbs +154 -225
  95. data/sig/generated/riffer/config.rbs +50 -5
  96. data/sig/generated/riffer/evals/judge.rbs +2 -2
  97. data/sig/generated/riffer/helpers/call_or_value.rbs +9 -0
  98. data/sig/generated/riffer/helpers.rbs +0 -1
  99. data/sig/generated/riffer/messages/assistant.rbs +7 -3
  100. data/sig/generated/riffer/messages/base.rbs +18 -0
  101. data/sig/generated/riffer/messages/converter.rbs +4 -4
  102. data/sig/generated/riffer/{file_part.rbs → messages/file_part.rbs} +5 -5
  103. data/sig/generated/riffer/messages/user.rbs +4 -4
  104. data/sig/generated/riffer/params/boolean.rbs +10 -0
  105. data/sig/generated/riffer/{param.rbs → params/param.rbs} +3 -3
  106. data/sig/generated/riffer/params.rbs +15 -15
  107. data/sig/generated/riffer/providers/amazon_bedrock.rbs +22 -22
  108. data/sig/generated/riffer/providers/anthropic.rbs +4 -4
  109. data/sig/generated/riffer/providers/base.rbs +10 -10
  110. data/sig/generated/riffer/providers/gemini.rbs +4 -4
  111. data/sig/generated/riffer/providers/mock.rbs +25 -5
  112. data/sig/generated/riffer/providers/open_ai.rbs +4 -4
  113. data/sig/generated/riffer/providers/open_router.rbs +85 -0
  114. data/sig/generated/riffer/{token_usage.rbs → providers/token_usage.rbs} +5 -5
  115. data/sig/generated/riffer/providers.rbs +1 -0
  116. data/sig/generated/riffer/runner/fibers.rbs +2 -2
  117. data/sig/generated/riffer/runner/sequential.rbs +2 -2
  118. data/sig/generated/riffer/runner/threaded.rbs +2 -2
  119. data/sig/generated/riffer/runner.rbs +2 -2
  120. data/sig/generated/riffer/skills/activate_tool.rbs +4 -3
  121. data/sig/generated/riffer/skills/config.rbs +1 -1
  122. data/sig/generated/riffer/skills/context.rbs +2 -2
  123. data/sig/generated/riffer/stream_events/interrupt.rbs +7 -2
  124. data/sig/generated/riffer/stream_events/token_usage_done.rbs +3 -3
  125. data/sig/generated/riffer/tool.rbs +5 -5
  126. data/sig/generated/riffer/{tool_runtime → tools/runtime}/fibers.rbs +3 -3
  127. data/sig/generated/riffer/{tool_runtime → tools/runtime}/inline.rbs +2 -2
  128. data/sig/generated/riffer/{tool_runtime → tools/runtime}/threaded.rbs +3 -3
  129. data/sig/generated/riffer/{tool_runtime.rbs → tools/runtime.rbs} +19 -13
  130. data/sig/generated/riffer/{toolable.rbs → tools/toolable.rbs} +6 -6
  131. data/sig/stubs/agent_ivars.rbs +7 -0
  132. data/sig/stubs/async.rbs +24 -0
  133. data/sig/stubs/aws-sdk-core/seahorse_request_context.rbs +7 -0
  134. data/sig/stubs/aws-sdk-core/static_token_provider.rbs +5 -0
  135. data/sig/stubs/extend_self.rbs +11 -0
  136. data/sig/stubs/lib_ivars.rbs +101 -0
  137. data/sig/stubs/mcp_sdk.rbs +22 -0
  138. data/sig/stubs/provider_ivars.rbs +36 -0
  139. data/sig/stubs/provider_sdk_methods.rbs +50 -0
  140. data/sig/stubs/zeitwerk.rbs +12 -0
  141. metadata +54 -33
  142. data/lib/riffer/core.rb +0 -28
  143. data/lib/riffer/helpers/validations.rb +0 -18
  144. data/sig/generated/riffer/boolean.rbs +0 -10
  145. data/sig/generated/riffer/core.rbs +0 -19
  146. data/sig/generated/riffer/helpers/validations.rbs +0 -12
data/lib/riffer/params.rb CHANGED
@@ -12,7 +12,7 @@
12
12
  # end
13
13
  #
14
14
  class Riffer::Params
15
- attr_reader :parameters #: Array[Riffer::Param]
15
+ attr_reader :parameters #: Array[Riffer::Params::Param]
16
16
 
17
17
  #--
18
18
  #: () -> void
@@ -23,10 +23,10 @@ class Riffer::Params
23
23
  # Defines a required parameter.
24
24
  #
25
25
  #--
26
- #: (Symbol, Class, ?description: String?, ?enum: Array[untyped]?, ?of: Class?) ?{ () -> void } -> void
26
+ #: (Symbol, Class, ?description: String?, ?enum: Array[untyped]?, ?of: Class?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> void
27
27
  def required(name, type, description: nil, enum: nil, of: nil, &block)
28
28
  nested = build_nested(type, of, &block)
29
- @parameters << Riffer::Param.new(
29
+ @parameters << Riffer::Params::Param.new(
30
30
  name: name,
31
31
  type: type,
32
32
  required: true,
@@ -40,10 +40,10 @@ class Riffer::Params
40
40
  # Defines an optional parameter.
41
41
  #
42
42
  #--
43
- #: (Symbol, Class, ?description: String?, ?enum: Array[untyped]?, ?default: untyped, ?of: Class?) ?{ () -> void } -> void
43
+ #: (Symbol, Class, ?description: String?, ?enum: Array[untyped]?, ?default: untyped, ?of: Class?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> void
44
44
  def optional(name, type, description: nil, enum: nil, default: nil, of: nil, &block)
45
45
  nested = build_nested(type, of, &block)
46
- @parameters << Riffer::Param.new(
46
+ @parameters << Riffer::Params::Param.new(
47
47
  name: name,
48
48
  type: type,
49
49
  required: false,
@@ -62,8 +62,8 @@ class Riffer::Params
62
62
  #--
63
63
  #: (Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
64
64
  def validate(arguments)
65
- validated = {}
66
- errors = []
65
+ validated = {} #: Hash[Symbol, untyped]
66
+ errors = [] #: Array[String]
67
67
 
68
68
  @parameters.each do |param|
69
69
  value = arguments[param.name]
@@ -107,8 +107,8 @@ class Riffer::Params
107
107
  #--
108
108
  #: (?strict: bool) -> Hash[Symbol, untyped]
109
109
  def to_json_schema(strict: false)
110
- properties = {}
111
- required_params = []
110
+ properties = {} #: Hash[String, untyped]
111
+ required_params = [] #: Array[String]
112
112
 
113
113
  @parameters.each do |param|
114
114
  properties[param.name.to_s] = param.to_json_schema(strict: strict)
@@ -126,7 +126,7 @@ class Riffer::Params
126
126
  private
127
127
 
128
128
  #--
129
- #: (Class, Class?) ?{ () -> void } -> Riffer::Params?
129
+ #: (Class, Class?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
130
130
  def build_nested(type, of, &block)
131
131
  if of && block
132
132
  raise Riffer::ArgumentError, "cannot use both of: and a block"
@@ -136,9 +136,9 @@ class Riffer::Params
136
136
  unless type == Array
137
137
  raise Riffer::ArgumentError, "of: can only be used with Array type, got #{type}"
138
138
  end
139
- unless Riffer::Param::PRIMITIVE_TYPES.include?(of)
139
+ unless Riffer::Params::Param::PRIMITIVE_TYPES.include?(of)
140
140
  raise Riffer::ArgumentError,
141
- "of: must be a primitive type (#{Riffer::Param::PRIMITIVE_TYPES.map(&:name).join(", ")}), got #{of}"
141
+ "of: must be a primitive type (#{Riffer::Params::Param::PRIMITIVE_TYPES.map(&:name).join(", ")}), got #{of}"
142
142
  end
143
143
  return nil
144
144
  end
@@ -154,7 +154,7 @@ class Riffer::Params
154
154
  end
155
155
 
156
156
  #--
157
- #: (Riffer::Param, untyped, Array[String]) -> untyped
157
+ #: (Riffer::Params::Param, untyped, Array[String]) -> untyped
158
158
  def validate_nested(param, value, errors)
159
159
  if param.type == Hash && param.nested_params
160
160
  validate_nested_hash(param, value, errors)
@@ -169,9 +169,11 @@ class Riffer::Params
169
169
  end
170
170
 
171
171
  #--
172
- #: (Riffer::Param, Hash[Symbol, untyped], Array[String]) -> Hash[Symbol, untyped]
172
+ #: (Riffer::Params::Param, Hash[Symbol, untyped], Array[String]) -> Hash[Symbol, untyped]
173
173
  def validate_nested_hash(param, value, errors)
174
- param.nested_params.validate(value)
174
+ nested = param.nested_params
175
+ return value unless nested
176
+ nested.validate(value)
175
177
  rescue Riffer::ValidationError => e
176
178
  e.message.split("; ").each do |msg|
177
179
  errors << "#{param.name}.#{msg}"
@@ -180,14 +182,16 @@ class Riffer::Params
180
182
  end
181
183
 
182
184
  #--
183
- #: (Riffer::Param, Array[untyped], Array[String]) -> Array[untyped]
185
+ #: (Riffer::Params::Param, Array[untyped], Array[String]) -> Array[untyped]
184
186
  def validate_nested_array_of_objects(param, value, errors)
187
+ nested = param.nested_params
188
+ return value unless nested
185
189
  value.map.with_index do |item, i|
186
190
  unless item.is_a?(Hash)
187
191
  errors << "#{param.name}[#{i}] must be an object"
188
192
  next item
189
193
  end
190
- param.nested_params.validate(item)
194
+ nested.validate(item)
191
195
  rescue Riffer::ValidationError => e
192
196
  e.message.split("; ").each do |msg|
193
197
  errors << "#{param.name}[#{i}].#{msg}"
@@ -197,14 +201,16 @@ class Riffer::Params
197
201
  end
198
202
 
199
203
  #--
200
- #: (Riffer::Param, Array[untyped], Array[String]) -> void
204
+ #: (Riffer::Params::Param, Array[untyped], Array[String]) -> void
201
205
  def validate_typed_array(param, value, errors)
202
- type_name = Riffer::Param::TYPE_MAPPINGS[param.item_type]
206
+ item_type = param.item_type
207
+ return unless item_type
208
+ type_name = Riffer::Params::Param::TYPE_MAPPINGS[item_type]
203
209
  value.each_with_index do |item, i|
204
- valid = if param.item_type == Riffer::Boolean || param.item_type == TrueClass || param.item_type == FalseClass
210
+ valid = if item_type == Riffer::Params::Boolean || item_type == TrueClass || item_type == FalseClass
205
211
  item == true || item == false
206
212
  else
207
- item.is_a?(param.item_type)
213
+ item.is_a?(item_type)
208
214
  end
209
215
  errors << "#{param.name}[#{i}] must be a #{type_name}" unless valid
210
216
  end
@@ -63,7 +63,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
63
63
  system: partitioned_messages[:system],
64
64
  messages: partitioned_messages[:conversation],
65
65
  **options.except(:tools, :structured_output)
66
- }
66
+ } #: Hash[Symbol, untyped]
67
67
 
68
68
  if tools && !tools.empty?
69
69
  params[:tool_config] = {
@@ -92,18 +92,17 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
92
92
  end
93
93
 
94
94
  #--
95
- #: (Hash[Symbol, untyped]) -> Aws::BedrockRuntime::Types::ConverseResponse
95
+ #: (Hash[Symbol, untyped]) -> Aws::BedrockRuntime::Client::_ConverseResponseSuccess
96
96
  def execute_generate(params)
97
97
  @client.converse(**params)
98
98
  end
99
99
 
100
100
  #--
101
- #: (Aws::BedrockRuntime::Types::ConverseResponse) -> Riffer::TokenUsage?
101
+ #: (Aws::BedrockRuntime::Client::_ConverseResponseSuccess) -> Riffer::Providers::TokenUsage?
102
102
  def extract_token_usage(response)
103
103
  usage = response.usage
104
- return nil unless usage
105
104
 
106
- Riffer::TokenUsage.new(
105
+ Riffer::Providers::TokenUsage.new(
107
106
  input_tokens: usage.input_tokens,
108
107
  output_tokens: usage.output_tokens,
109
108
  cache_creation_tokens: usage.cache_write_input_tokens,
@@ -112,7 +111,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
112
111
  end
113
112
 
114
113
  #--
115
- #: (Aws::BedrockRuntime::Types::ConverseResponse) -> String
114
+ #: (Aws::BedrockRuntime::Client::_ConverseResponseSuccess) -> String
116
115
  def extract_content(response)
117
116
  content_blocks = response.output&.message&.content
118
117
  return "" if content_blocks.nil? || content_blocks.empty?
@@ -127,12 +126,12 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
127
126
  end
128
127
 
129
128
  #--
130
- #: (Aws::BedrockRuntime::Types::ConverseResponse) -> Array[Riffer::Messages::Assistant::ToolCall]
129
+ #: (Aws::BedrockRuntime::Client::_ConverseResponseSuccess) -> Array[Riffer::Messages::Assistant::ToolCall]
131
130
  def extract_tool_calls(response)
132
131
  content_blocks = response.output&.message&.content
133
132
  return [] if content_blocks.nil? || content_blocks.empty?
134
133
 
135
- tool_calls = []
134
+ tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
136
135
 
137
136
  content_blocks.each do |block|
138
137
  if block.respond_to?(:tool_use) && block.tool_use
@@ -153,7 +152,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
153
152
  current_state = {
154
153
  text: nil,
155
154
  tool_call: nil
156
- }
155
+ } #: Hash[Symbol, untyped]
157
156
 
158
157
  @client.converse_stream(**params) do |stream|
159
158
  stream.on_event do |event|
@@ -196,7 +195,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
196
195
  end
197
196
 
198
197
  #--
199
- #: (Aws::BedrockRuntime::Types::ContentBlockStartEvent, state: Hash[Symbol, untyped], yielder: Enumerator[Riffer::StreamEvents::Base, void]) -> void
198
+ #: (Aws::BedrockRuntime::Types::ContentBlockStartEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
200
199
  def handle_content_block_start_tool_use(event, state:, yielder:)
201
200
  state[:tool_call] = {
202
201
  id: event.start.tool_use.tool_use_id,
@@ -206,7 +205,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
206
205
  end
207
206
 
208
207
  #--
209
- #: (Aws::BedrockRuntime::Types::ContentBlockDeltaEvent, state: Hash[Symbol, untyped], yielder: Enumerator[Riffer::StreamEvents::Base, void]) -> void
208
+ #: (Aws::BedrockRuntime::Types::ContentBlockDeltaEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
210
209
  def handle_content_block_delta_text_delta(event, state:, yielder:)
211
210
  delta_text = event.delta.text
212
211
  state[:text] ||= ""
@@ -215,7 +214,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
215
214
  end
216
215
 
217
216
  #--
218
- #: (Aws::BedrockRuntime::Types::ContentBlockDeltaEvent, state: Hash[Symbol, untyped], yielder: Enumerator[Riffer::StreamEvents::Base, void]) -> void
217
+ #: (Aws::BedrockRuntime::Types::ContentBlockDeltaEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
219
218
  def handle_content_block_delta_tool_use(event, state:, yielder:)
220
219
  input_delta = event.delta.tool_use.input
221
220
 
@@ -229,14 +228,14 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
229
228
  end
230
229
 
231
230
  #--
232
- #: (Aws::BedrockRuntime::Types::ContentBlockStopEvent, state: Hash[Symbol, untyped], yielder: Enumerator[Riffer::StreamEvents::Base, void]) -> void
231
+ #: (Aws::BedrockRuntime::Types::ContentBlockStopEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
233
232
  def handle_content_block_stop_text_delta(_event, state:, yielder:)
234
233
  yielder << Riffer::StreamEvents::TextDone.new(state[:text])
235
234
  state[:text] = nil
236
235
  end
237
236
 
238
237
  #--
239
- #: (Aws::BedrockRuntime::Types::ContentBlockStopEvent, state: Hash[Symbol, untyped], yielder: Enumerator[Riffer::StreamEvents::Base, void]) -> void
238
+ #: (Aws::BedrockRuntime::Types::ContentBlockStopEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
240
239
  def handle_content_block_stop_tool_use(_event, state:, yielder:)
241
240
  tool_call = state[:tool_call]
242
241
  yielder << Riffer::StreamEvents::ToolCallDone.new(
@@ -249,10 +248,10 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
249
248
  end
250
249
 
251
250
  #--
252
- #: (Aws::BedrockRuntime::Types::ConverseStreamMetadataEvent, state: Hash[Symbol, untyped], yielder: Enumerator[Riffer::StreamEvents::Base, void]) -> void
251
+ #: (Aws::BedrockRuntime::Types::ConverseStreamMetadataEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
253
252
  def handle_metadata_usage(event, state:, yielder:)
254
253
  yielder << Riffer::StreamEvents::TokenUsageDone.new(
255
- token_usage: Riffer::TokenUsage.new(
254
+ token_usage: Riffer::Providers::TokenUsage.new(
256
255
  input_tokens: event.usage.input_tokens,
257
256
  output_tokens: event.usage.output_tokens,
258
257
  cache_creation_tokens: event.usage.cache_write_input_tokens,
@@ -264,8 +263,8 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
264
263
  #--
265
264
  #: (Array[Riffer::Messages::Base]) -> Hash[Symbol, untyped]
266
265
  def partition_messages(messages)
267
- system_prompts = []
268
- conversation_messages = []
266
+ system_prompts = [] #: Array[Hash[Symbol, untyped]]
267
+ conversation_messages = [] #: Array[Hash[Symbol, untyped]]
269
268
 
270
269
  messages.each do |message|
271
270
  case message
@@ -291,7 +290,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
291
290
  #--
292
291
  #: (Riffer::Messages::Assistant) -> Hash[Symbol, untyped]
293
292
  def convert_assistant_to_bedrock_format(message)
294
- content = []
293
+ content = [] #: Array[Hash[Symbol, untyped]]
295
294
  content << {text: message.content} if message.content && !message.content.empty?
296
295
 
297
296
  message.tool_calls.each do |tc|
@@ -326,7 +325,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
326
325
  end
327
326
 
328
327
  #--
329
- #: (Riffer::FilePart) -> Hash[Symbol, untyped]
328
+ #: (Riffer::Messages::FilePart) -> Hash[Symbol, untyped]
330
329
  def convert_file_part_to_bedrock_format(file)
331
330
  format = bedrock_format(file.media_type)
332
331
 
@@ -26,7 +26,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
26
26
 
27
27
  api_key ||= Riffer.config.anthropic.api_key
28
28
 
29
- @client = Anthropic::Client.new(api_key: api_key, **options)
29
+ @client = ::Anthropic::Client.new(api_key: api_key, **options)
30
30
  end
31
31
 
32
32
  private
@@ -46,11 +46,11 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
46
46
  messages: partitioned_messages[:conversation],
47
47
  max_tokens: max_tokens,
48
48
  **options.except(:tools, :max_tokens, :structured_output, :web_search)
49
- }
49
+ } #: Hash[Symbol, untyped]
50
50
 
51
51
  params[:system] = partitioned_messages[:system] if partitioned_messages[:system]
52
52
 
53
- anthropic_tools = []
53
+ anthropic_tools = [] #: Array[Hash[Symbol, untyped]]
54
54
  anthropic_tools.concat(tools.map { |t| convert_tool_to_anthropic_format(t) }) if tools && !tools.empty?
55
55
 
56
56
  if web_search
@@ -83,12 +83,11 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
83
83
  end
84
84
 
85
85
  #--
86
- #: (Anthropic::Models::Message) -> Riffer::TokenUsage?
86
+ #: (Anthropic::Models::Message) -> Riffer::Providers::TokenUsage?
87
87
  def extract_token_usage(response)
88
88
  usage = response.usage
89
- return nil unless usage
90
89
 
91
- Riffer::TokenUsage.new(
90
+ Riffer::Providers::TokenUsage.new(
92
91
  input_tokens: usage.input_tokens,
93
92
  output_tokens: usage.output_tokens,
94
93
  cache_creation_tokens: usage.cache_creation_input_tokens,
@@ -105,7 +104,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
105
104
  text_content = ""
106
105
 
107
106
  content_blocks.each do |block|
108
- text_content = block.text if block.type.to_s == "text"
107
+ text_content = block.text if block.is_a?(::Anthropic::Models::TextBlock)
109
108
  end
110
109
 
111
110
  text_content
@@ -117,10 +116,10 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
117
116
  content_blocks = response.content
118
117
  return [] if content_blocks.nil? || content_blocks.empty?
119
118
 
120
- tool_calls = []
119
+ tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
121
120
 
122
121
  content_blocks.each do |block|
123
- if block.type.to_s == "tool_use"
122
+ if block.is_a?(::Anthropic::Models::ToolUseBlock)
124
123
  tool_calls << Riffer::Messages::Assistant::ToolCall.new(
125
124
  call_id: block.id,
126
125
  name: decode_tool_name(block.name, tools: @current_tools),
@@ -142,7 +141,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
142
141
  web_search_index: nil,
143
142
  web_search_json: nil,
144
143
  web_search_query: nil
145
- }
144
+ } #: Hash[Symbol, untyped]
146
145
 
147
146
  # Workaround for anthropics/anthropic-sdk-ruby#182: force identity
148
147
  # encoding so Net::HTTP/Zlib doesn't buffer SSE chunks until EOF.
@@ -154,24 +153,24 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
154
153
  begin
155
154
  stream.each do |event|
156
155
  case event
157
- when Anthropic::Models::RawContentBlockStartEvent
156
+ when ::Anthropic::Models::RawContentBlockStartEvent
158
157
  handle_raw_content_block_start(event, state: current_state)
159
- when Anthropic::Models::RawContentBlockDeltaEvent
158
+ when ::Anthropic::Models::RawContentBlockDeltaEvent
160
159
  handle_raw_content_block_delta(event, state: current_state)
161
- when Anthropic::Streaming::TextEvent
160
+ when ::Anthropic::Helpers::Streaming::TextEvent
162
161
  handle_text_event(event, state: current_state, yielder: yielder)
163
- when Anthropic::Streaming::ThinkingEvent
162
+ when ::Anthropic::Helpers::Streaming::ThinkingEvent
164
163
  handle_thinking_event(event, state: current_state, yielder: yielder)
165
- when Anthropic::Streaming::InputJsonEvent
164
+ when ::Anthropic::Helpers::Streaming::InputJsonEvent
166
165
  handle_input_json_event(event, state: current_state, yielder: yielder)
167
- when Anthropic::Streaming::ContentBlockStopEvent
168
- block_type = event.content_block&.type.to_s
169
- handle_content_block_stop_text(event, state: current_state, yielder: yielder) if block_type == "text" && current_state[:text]
170
- handle_content_block_stop_tool_use(event, state: current_state, yielder: yielder) if block_type == "tool_use"
171
- handle_content_block_stop_thinking(event, state: current_state, yielder: yielder) if block_type == "thinking" && current_state[:reasoning]
172
- handle_content_block_stop_server_tool_use(event, state: current_state, yielder: yielder) if block_type == "server_tool_use"
173
- handle_content_block_stop_web_search_result(event, state: current_state, yielder: yielder) if block_type == "web_search_tool_result"
174
- when Anthropic::Streaming::MessageStopEvent
166
+ when ::Anthropic::Helpers::Streaming::ContentBlockStopEvent
167
+ block = event.content_block
168
+ handle_content_block_stop_text(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::TextBlock) && current_state[:text]
169
+ handle_content_block_stop_tool_use(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::ToolUseBlock)
170
+ handle_content_block_stop_thinking(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::ThinkingBlock) && current_state[:reasoning]
171
+ handle_content_block_stop_server_tool_use(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::ServerToolUseBlock)
172
+ handle_content_block_stop_web_search_result(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::WebSearchToolResultBlock)
173
+ when ::Anthropic::Helpers::Streaming::MessageStopEvent
175
174
  handle_message_stop(event, accumulated_message: stream.accumulated_message, yielder: yielder)
176
175
  end
177
176
  end
@@ -292,7 +291,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
292
291
  return unless usage
293
292
 
294
293
  yielder << Riffer::StreamEvents::TokenUsageDone.new(
295
- token_usage: Riffer::TokenUsage.new(
294
+ token_usage: Riffer::Providers::TokenUsage.new(
296
295
  input_tokens: usage.input_tokens,
297
296
  output_tokens: usage.output_tokens,
298
297
  cache_creation_tokens: usage.cache_creation_input_tokens,
@@ -304,8 +303,8 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
304
303
  #--
305
304
  #: (Array[Riffer::Messages::Base]) -> Hash[Symbol, untyped]
306
305
  def partition_messages(messages)
307
- system_prompts = []
308
- conversation_messages = []
306
+ system_prompts = [] #: Array[Hash[Symbol, untyped]]
307
+ conversation_messages = [] #: Array[Hash[Symbol, untyped]]
309
308
 
310
309
  messages.each do |message|
311
310
  case message
@@ -342,7 +341,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
342
341
  #--
343
342
  #: (Riffer::Messages::Assistant) -> Hash[Symbol, untyped]
344
343
  def convert_assistant_to_anthropic_format(message)
345
- content = []
344
+ content = [] #: Array[Hash[Symbol, untyped]]
346
345
  content << {type: "text", text: message.content} if message.content && !message.content.empty?
347
346
 
348
347
  message.tool_calls.each do |tc|
@@ -358,7 +357,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
358
357
  end
359
358
 
360
359
  #--
361
- #: (Riffer::FilePart) -> Hash[Symbol, untyped]
360
+ #: (Riffer::Messages::FilePart) -> Hash[Symbol, untyped]
362
361
  def convert_file_part_to_anthropic_format(file)
363
362
  type = file.image? ? "image" : "document"
364
363
 
@@ -39,10 +39,10 @@ class Riffer::Providers::Base
39
39
  # Generates text using the provider.
40
40
  #
41
41
  #--
42
- #: (?prompt: String?, ?system: String?, ?messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?model: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::FilePart]?, **untyped) -> Riffer::Messages::Assistant
42
+ #: (?prompt: String?, ?system: String?, ?messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?model: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, **untyped) -> Riffer::Messages::Assistant
43
43
  def generate_text(prompt: nil, system: nil, messages: nil, model: nil, files: nil, **options)
44
44
  validate_input!(prompt: prompt, system: system, messages: messages)
45
- @current_tools = options[:tools] || []
45
+ @current_tools = options[:tools] || [] #: Array[singleton(Riffer::Tool)]
46
46
  messages = normalize_messages(prompt: prompt, system: system, messages: messages, files: files)
47
47
  validate_normalized_messages!(messages)
48
48
  messages = merge_consecutive_messages(messages)
@@ -65,10 +65,10 @@ class Riffer::Providers::Base
65
65
  # Streams text from the provider.
66
66
  #
67
67
  #--
68
- #: (?prompt: String?, ?system: String?, ?messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?model: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::FilePart]?, **untyped) -> Enumerator[Riffer::StreamEvents::Base, void]
68
+ #: (?prompt: String?, ?system: String?, ?messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?model: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, **untyped) -> Enumerator[Riffer::StreamEvents::Base, void]
69
69
  def stream_text(prompt: nil, system: nil, messages: nil, model: nil, files: nil, **options)
70
70
  validate_input!(prompt: prompt, system: system, messages: messages)
71
- @current_tools = options[:tools] || []
71
+ @current_tools = options[:tools] || [] #: Array[singleton(Riffer::Tool)]
72
72
  messages = normalize_messages(prompt: prompt, system: system, messages: messages, files: files)
73
73
  validate_normalized_messages!(messages)
74
74
  messages = merge_consecutive_messages(messages)
@@ -87,7 +87,7 @@ class Riffer::Providers::Base
87
87
  end
88
88
 
89
89
  #--
90
- #: (String, tools: Array[Riffer::Tool]) -> String
90
+ #: (String, tools: Array[singleton(Riffer::Tool)]) -> String
91
91
  def decode_tool_name(wire_name, tools:)
92
92
  tool = tools.find { |t| encode_tool_name(t.name) == wire_name }
93
93
  tool ? tool.name : wire_name
@@ -112,7 +112,7 @@ class Riffer::Providers::Base
112
112
  end
113
113
 
114
114
  #--
115
- #: (untyped) -> Riffer::TokenUsage?
115
+ #: (untyped) -> Riffer::Providers::TokenUsage?
116
116
  def extract_token_usage(response)
117
117
  raise NotImplementedError, "Subclasses must implement #extract_token_usage"
118
118
  end
@@ -166,7 +166,7 @@ class Riffer::Providers::Base
166
166
  end
167
167
 
168
168
  #--
169
- #: (prompt: String?, system: String?, messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?files: Array[Hash[Symbol, untyped] | Riffer::FilePart]?) -> Array[Riffer::Messages::Base]
169
+ #: (prompt: String?, system: String?, messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?) -> Array[Riffer::Messages::Base]
170
170
  def normalize_messages(prompt:, system:, messages:, files: nil)
171
171
  if messages && files && !files.empty?
172
172
  raise Riffer::ArgumentError, "cannot provide both files and messages; attach files to individual messages instead"
@@ -176,10 +176,11 @@ class Riffer::Providers::Base
176
176
  return messages.map { |msg| convert_to_message_object(msg) }
177
177
  end
178
178
 
179
- result = []
179
+ result = [] #: Array[Riffer::Messages::Base]
180
180
  result << Riffer::Messages::System.new(system) if system
181
181
  file_parts = (files || []).map { |f| convert_to_file_part(f) }
182
- result << Riffer::Messages::User.new(prompt, files: file_parts)
182
+ prompt_text = prompt #: String
183
+ result << Riffer::Messages::User.new(prompt_text, files: file_parts)
183
184
  result
184
185
  end
185
186
 
@@ -36,7 +36,7 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
36
36
  params = {
37
37
  model: model,
38
38
  contents: partitioned[:contents]
39
- }
39
+ } #: Hash[Symbol, untyped]
40
40
 
41
41
  params[:systemInstruction] = partitioned[:system_instruction] if partitioned[:system_instruction]
42
42
 
@@ -96,12 +96,12 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
96
96
  end
97
97
 
98
98
  #--
99
- #: (Hash[Symbol, untyped]) -> Riffer::TokenUsage?
99
+ #: (Hash[Symbol, untyped]) -> Riffer::Providers::TokenUsage?
100
100
  def extract_token_usage(response)
101
101
  usage = response[:usageMetadata]
102
102
  return nil unless usage
103
103
 
104
- Riffer::TokenUsage.new(
104
+ Riffer::Providers::TokenUsage.new(
105
105
  input_tokens: usage[:promptTokenCount] || 0,
106
106
  output_tokens: usage[:candidatesTokenCount] || 0
107
107
  )
@@ -114,6 +114,7 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
114
114
  body = params.except(:model)
115
115
 
116
116
  uri = URI("#{BASE_URI}/#{api_path(model, "streamGenerateContent")}?alt=sse")
117
+ host = uri.hostname #: String
117
118
  request = Net::HTTP::Post.new(uri)
118
119
  request["Content-Type"] = "application/json"
119
120
  request["x-goog-api-key"] = @api_key
@@ -126,7 +127,8 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
126
127
  buffer << chunk
127
128
 
128
129
  while (match = buffer.match(/\r?\n\r?\n/))
129
- frame = buffer.slice!(0, match.end(0)).strip
130
+ match_end = match.end(0) #: Integer
131
+ frame = buffer.slice!(0, match_end).to_s.strip
130
132
  next unless frame.start_with?("data: ")
131
133
 
132
134
  json_str = frame.delete_prefix("data: ").strip
@@ -155,7 +157,7 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
155
157
  usage = parsed[:usageMetadata]
156
158
  if usage && usage[:candidatesTokenCount]
157
159
  yielder << Riffer::StreamEvents::TokenUsageDone.new(
158
- token_usage: Riffer::TokenUsage.new(
160
+ token_usage: Riffer::Providers::TokenUsage.new(
159
161
  input_tokens: usage[:promptTokenCount] || 0,
160
162
  output_tokens: usage[:candidatesTokenCount] || 0
161
163
  )
@@ -164,7 +166,7 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
164
166
  end
165
167
  end
166
168
 
167
- Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, open_timeout: @open_timeout, read_timeout: @read_timeout) do |http|
169
+ Net::HTTP.start(host, uri.port, use_ssl: true, open_timeout: @open_timeout, read_timeout: @read_timeout) do |http|
168
170
  http.request(request) do |response|
169
171
  handle_api_error!(response) unless response.is_a?(Net::HTTPSuccess)
170
172
 
@@ -182,8 +184,8 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
182
184
  #--
183
185
  #: (Array[Riffer::Messages::Base]) -> Hash[Symbol, untyped]
184
186
  def partition_messages(messages)
185
- system_parts = []
186
- contents = []
187
+ system_parts = [] #: Array[Hash[Symbol, untyped]]
188
+ contents = [] #: Array[Hash[Symbol, untyped]]
187
189
 
188
190
  messages.each do |message|
189
191
  case message
@@ -212,7 +214,7 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
212
214
  end
213
215
  end
214
216
 
215
- result = {contents: contents}
217
+ result = {contents: contents} #: Hash[Symbol, untyped]
216
218
  result[:system_instruction] = {parts: system_parts} unless system_parts.empty?
217
219
  result
218
220
  end
@@ -220,7 +222,7 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
220
222
  #--
221
223
  #: (Riffer::Messages::Assistant) -> Hash[Symbol, untyped]
222
224
  def convert_assistant_to_gemini_format(message)
223
- parts = []
225
+ parts = [] #: Array[Hash[Symbol, untyped]]
224
226
  parts << {text: message.content} if message.content && !message.content.empty?
225
227
 
226
228
  message.tool_calls.each do |tc|
@@ -236,7 +238,7 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
236
238
  end
237
239
 
238
240
  #--
239
- #: (Riffer::FilePart) -> Hash[Symbol, untyped]
241
+ #: (Riffer::Messages::FilePart) -> Hash[Symbol, untyped]
240
242
  def convert_file_part_to_gemini_format(file)
241
243
  if file.url?
242
244
  raise Riffer::ArgumentError,
@@ -268,11 +270,12 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
268
270
  #: (String, Hash[Symbol, untyped]) -> Net::HTTPResponse
269
271
  def post_request(path, body)
270
272
  uri = URI("#{BASE_URI}/#{path}")
273
+ host = uri.hostname #: String
271
274
  request = Net::HTTP::Post.new(uri)
272
275
  request["Content-Type"] = "application/json"
273
276
  request["x-goog-api-key"] = @api_key
274
277
  request.body = body.to_json
275
- Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, open_timeout: @open_timeout, read_timeout: @read_timeout) { |http| http.request(request) }
278
+ Net::HTTP.start(host, uri.port, use_ssl: true, open_timeout: @open_timeout, read_timeout: @read_timeout) { |http| http.request(request) }
276
279
  end
277
280
 
278
281
  #--