riffer 0.28.0 → 0.29.1

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 (158) 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 +53 -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 +26 -0
  9. data/README.md +17 -18
  10. data/Steepfile +8 -1
  11. data/docs/03_AGENTS.md +34 -3
  12. data/docs/04_AGENT_LIFECYCLE.md +87 -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 +17 -17
  16. data/docs/08_MESSAGES.md +25 -32
  17. data/docs/09_STREAM_EVENTS.md +1 -1
  18. data/docs/10_CONFIGURATION.md +7 -18
  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 +127 -0
  26. data/lib/riffer/agent/response.rb +2 -0
  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 +270 -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 +236 -923
  33. data/lib/riffer/config.rb +14 -7
  34. data/lib/riffer/evals/evaluator.rb +18 -3
  35. data/lib/riffer/evals/judge.rb +7 -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 +3 -1
  43. data/lib/riffer/mcp/registration.rb +6 -3
  44. data/lib/riffer/mcp/registry.rb +6 -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} +7 -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 +37 -31
  56. data/lib/riffer/providers/anthropic.rb +39 -36
  57. data/lib/riffer/providers/base.rb +12 -9
  58. data/lib/riffer/providers/gemini.rb +19 -12
  59. data/lib/riffer/providers/mock.rb +45 -13
  60. data/lib/riffer/providers/open_ai.rb +34 -29
  61. data/lib/riffer/providers/open_router.rb +325 -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 +6 -3
  66. data/lib/riffer/runner/sequential.rb +1 -1
  67. data/lib/riffer/runner/threaded.rb +3 -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 +6 -1
  71. data/lib/riffer/skills/context.rb +6 -3
  72. data/lib/riffer/skills/filesystem_backend.rb +10 -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 +1 -1
  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/tools/response.rb +2 -0
  80. data/lib/riffer/{tool_runtime → tools/runtime}/fibers.rb +2 -2
  81. data/lib/riffer/{tool_runtime → tools/runtime}/inline.rb +1 -1
  82. data/lib/riffer/{tool_runtime → tools/runtime}/threaded.rb +2 -2
  83. data/lib/riffer/{tool_runtime.rb → tools/runtime.rb} +11 -9
  84. data/lib/riffer/{toolable.rb → tools/toolable.rb} +19 -9
  85. data/lib/riffer/version.rb +1 -1
  86. data/lib/riffer.rb +4 -1
  87. data/sig/_private/anthropic.rbs +16 -0
  88. data/sig/_private/async.rbs +24 -0
  89. data/sig/_private/aws-sdk-core/seahorse_request_context.rbs +7 -0
  90. data/sig/_private/aws-sdk-core/static_token_provider.rbs +5 -0
  91. data/sig/_private/mcp.rbs +22 -0
  92. data/sig/_private/openai.rbs +29 -0
  93. data/sig/_private/riffer/providers/amazon_bedrock.rbs +4 -0
  94. data/sig/_private/riffer/providers/anthropic.rbs +4 -0
  95. data/sig/_private/riffer/providers/open_ai.rbs +4 -0
  96. data/sig/_private/riffer/providers/open_router.rbs +4 -0
  97. data/sig/_private/zeitwerk.rbs +12 -0
  98. data/sig/generated/riffer/agent/config.rbs +119 -0
  99. data/sig/generated/riffer/agent/context.rbs +93 -0
  100. data/sig/generated/riffer/agent/response.rbs +2 -0
  101. data/sig/generated/riffer/agent/run.rbs +144 -0
  102. data/sig/generated/riffer/agent/session/repair.rbs +51 -0
  103. data/sig/generated/riffer/agent/session.rbs +147 -0
  104. data/sig/generated/riffer/{structured_output → agent/structured_output}/result.rbs +2 -2
  105. data/sig/generated/riffer/{structured_output.rbs → agent/structured_output.rbs} +6 -6
  106. data/sig/generated/riffer/agent.rbs +145 -342
  107. data/sig/generated/riffer/config.rbs +17 -5
  108. data/sig/generated/riffer/evals/evaluator.rbs +8 -0
  109. data/sig/generated/riffer/evals/judge.rbs +10 -2
  110. data/sig/generated/riffer/helpers/call_or_value.rbs +9 -0
  111. data/sig/generated/riffer/helpers.rbs +0 -1
  112. data/sig/generated/riffer/mcp/client.rbs +2 -0
  113. data/sig/generated/riffer/mcp/registration.rbs +6 -0
  114. data/sig/generated/riffer/mcp/registry.rbs +4 -0
  115. data/sig/generated/riffer/messages/assistant.rbs +7 -3
  116. data/sig/generated/riffer/messages/base.rbs +18 -0
  117. data/sig/generated/riffer/messages/converter.rbs +4 -4
  118. data/sig/generated/riffer/{file_part.rbs → messages/file_part.rbs} +7 -5
  119. data/sig/generated/riffer/messages/user.rbs +4 -4
  120. data/sig/generated/riffer/params/boolean.rbs +10 -0
  121. data/sig/generated/riffer/{param.rbs → params/param.rbs} +3 -3
  122. data/sig/generated/riffer/params.rbs +15 -15
  123. data/sig/generated/riffer/providers/amazon_bedrock.rbs +22 -22
  124. data/sig/generated/riffer/providers/anthropic.rbs +12 -12
  125. data/sig/generated/riffer/providers/base.rbs +12 -10
  126. data/sig/generated/riffer/providers/gemini.rbs +10 -4
  127. data/sig/generated/riffer/providers/mock.rbs +31 -5
  128. data/sig/generated/riffer/providers/open_ai.rbs +10 -10
  129. data/sig/generated/riffer/providers/open_router.rbs +85 -0
  130. data/sig/generated/riffer/{token_usage.rbs → providers/token_usage.rbs} +5 -5
  131. data/sig/generated/riffer/providers.rbs +1 -0
  132. data/sig/generated/riffer/runner/fibers.rbs +4 -2
  133. data/sig/generated/riffer/runner/sequential.rbs +2 -2
  134. data/sig/generated/riffer/runner/threaded.rbs +4 -2
  135. data/sig/generated/riffer/runner.rbs +2 -2
  136. data/sig/generated/riffer/skills/activate_tool.rbs +4 -3
  137. data/sig/generated/riffer/skills/config.rbs +9 -1
  138. data/sig/generated/riffer/skills/context.rbs +6 -2
  139. data/sig/generated/riffer/skills/filesystem_backend.rbs +4 -0
  140. data/sig/generated/riffer/stream_events/token_usage_done.rbs +3 -3
  141. data/sig/generated/riffer/tool.rbs +5 -5
  142. data/sig/generated/riffer/tools/response.rbs +2 -0
  143. data/sig/generated/riffer/{tool_runtime → tools/runtime}/fibers.rbs +3 -3
  144. data/sig/generated/riffer/{tool_runtime → tools/runtime}/inline.rbs +2 -2
  145. data/sig/generated/riffer/{tool_runtime → tools/runtime}/threaded.rbs +3 -3
  146. data/sig/generated/riffer/{tool_runtime.rbs → tools/runtime.rbs} +14 -12
  147. data/sig/generated/riffer/{toolable.rbs → tools/toolable.rbs} +18 -6
  148. data/sig/generated/riffer.rbs +2 -0
  149. data/sig/manifest.yaml +3 -0
  150. data/sig/manual/riffer/agent/run.rbs +5 -0
  151. data/sig/manual/riffer/helpers/call_or_value.rbs +5 -0
  152. data/sig/manual/riffer/tools/toolable.rbs +6 -0
  153. metadata +59 -33
  154. data/lib/riffer/core.rb +0 -28
  155. data/lib/riffer/helpers/validations.rb +0 -18
  156. data/sig/generated/riffer/boolean.rbs +0 -10
  157. data/sig/generated/riffer/core.rbs +0 -19
  158. 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,18 @@ 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]) -> untyped
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
+ #: (untyped) -> Riffer::Providers::TokenUsage?
102
102
  def extract_token_usage(response)
103
- usage = response.usage
104
- return nil unless usage
103
+ typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
104
+ usage = typed_response.usage
105
105
 
106
- Riffer::TokenUsage.new(
106
+ Riffer::Providers::TokenUsage.new(
107
107
  input_tokens: usage.input_tokens,
108
108
  output_tokens: usage.output_tokens,
109
109
  cache_creation_tokens: usage.cache_write_input_tokens,
@@ -112,9 +112,10 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
112
112
  end
113
113
 
114
114
  #--
115
- #: (Aws::BedrockRuntime::Types::ConverseResponse) -> String
115
+ #: (untyped) -> String
116
116
  def extract_content(response)
117
- content_blocks = response.output&.message&.content
117
+ typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
118
+ content_blocks = typed_response.output&.message&.content
118
119
  return "" if content_blocks.nil? || content_blocks.empty?
119
120
 
120
121
  text_content = ""
@@ -127,12 +128,13 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
127
128
  end
128
129
 
129
130
  #--
130
- #: (Aws::BedrockRuntime::Types::ConverseResponse) -> Array[Riffer::Messages::Assistant::ToolCall]
131
+ #: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
131
132
  def extract_tool_calls(response)
132
- content_blocks = response.output&.message&.content
133
+ typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
134
+ content_blocks = typed_response.output&.message&.content
133
135
  return [] if content_blocks.nil? || content_blocks.empty?
134
136
 
135
- tool_calls = []
137
+ tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
136
138
 
137
139
  content_blocks.each do |block|
138
140
  if block.respond_to?(:tool_use) && block.tool_use
@@ -153,7 +155,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
153
155
  current_state = {
154
156
  text: nil,
155
157
  tool_call: nil
156
- }
158
+ } #: Hash[Symbol, untyped]
157
159
 
158
160
  @client.converse_stream(**params) do |stream|
159
161
  stream.on_event do |event|
@@ -196,28 +198,31 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
196
198
  end
197
199
 
198
200
  #--
199
- #: (Aws::BedrockRuntime::Types::ContentBlockStartEvent, state: Hash[Symbol, untyped], yielder: Enumerator[Riffer::StreamEvents::Base, void]) -> void
201
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
200
202
  def handle_content_block_start_tool_use(event, state:, yielder:)
203
+ typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockStartEvent
201
204
  state[:tool_call] = {
202
- id: event.start.tool_use.tool_use_id,
203
- name: decode_tool_name(event.start.tool_use.name, tools: @current_tools),
205
+ id: typed_event.start.tool_use.tool_use_id,
206
+ name: decode_tool_name(typed_event.start.tool_use.name, tools: @current_tools),
204
207
  arguments: ""
205
208
  }
206
209
  end
207
210
 
208
211
  #--
209
- #: (Aws::BedrockRuntime::Types::ContentBlockDeltaEvent, state: Hash[Symbol, untyped], yielder: Enumerator[Riffer::StreamEvents::Base, void]) -> void
212
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
210
213
  def handle_content_block_delta_text_delta(event, state:, yielder:)
211
- delta_text = event.delta.text
214
+ typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockDeltaEvent
215
+ delta_text = typed_event.delta.text
212
216
  state[:text] ||= ""
213
217
  state[:text] += delta_text
214
218
  yielder << Riffer::StreamEvents::TextDelta.new(delta_text)
215
219
  end
216
220
 
217
221
  #--
218
- #: (Aws::BedrockRuntime::Types::ContentBlockDeltaEvent, state: Hash[Symbol, untyped], yielder: Enumerator[Riffer::StreamEvents::Base, void]) -> void
222
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
219
223
  def handle_content_block_delta_tool_use(event, state:, yielder:)
220
- input_delta = event.delta.tool_use.input
224
+ typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockDeltaEvent
225
+ input_delta = typed_event.delta.tool_use.input
221
226
 
222
227
  state[:tool_call][:arguments] += input_delta
223
228
 
@@ -229,14 +234,14 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
229
234
  end
230
235
 
231
236
  #--
232
- #: (Aws::BedrockRuntime::Types::ContentBlockStopEvent, state: Hash[Symbol, untyped], yielder: Enumerator[Riffer::StreamEvents::Base, void]) -> void
237
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
233
238
  def handle_content_block_stop_text_delta(_event, state:, yielder:)
234
239
  yielder << Riffer::StreamEvents::TextDone.new(state[:text])
235
240
  state[:text] = nil
236
241
  end
237
242
 
238
243
  #--
239
- #: (Aws::BedrockRuntime::Types::ContentBlockStopEvent, state: Hash[Symbol, untyped], yielder: Enumerator[Riffer::StreamEvents::Base, void]) -> void
244
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
240
245
  def handle_content_block_stop_tool_use(_event, state:, yielder:)
241
246
  tool_call = state[:tool_call]
242
247
  yielder << Riffer::StreamEvents::ToolCallDone.new(
@@ -249,14 +254,15 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
249
254
  end
250
255
 
251
256
  #--
252
- #: (Aws::BedrockRuntime::Types::ConverseStreamMetadataEvent, state: Hash[Symbol, untyped], yielder: Enumerator[Riffer::StreamEvents::Base, void]) -> void
257
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
253
258
  def handle_metadata_usage(event, state:, yielder:)
259
+ typed_event = event #: Aws::BedrockRuntime::Types::ConverseStreamMetadataEvent
254
260
  yielder << Riffer::StreamEvents::TokenUsageDone.new(
255
- token_usage: Riffer::TokenUsage.new(
256
- input_tokens: event.usage.input_tokens,
257
- output_tokens: event.usage.output_tokens,
258
- cache_creation_tokens: event.usage.cache_write_input_tokens,
259
- cache_read_tokens: event.usage.cache_read_input_tokens
261
+ token_usage: Riffer::Providers::TokenUsage.new(
262
+ input_tokens: typed_event.usage.input_tokens,
263
+ output_tokens: typed_event.usage.output_tokens,
264
+ cache_creation_tokens: typed_event.usage.cache_write_input_tokens,
265
+ cache_read_tokens: typed_event.usage.cache_read_input_tokens
260
266
  )
261
267
  )
262
268
  end
@@ -264,8 +270,8 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
264
270
  #--
265
271
  #: (Array[Riffer::Messages::Base]) -> Hash[Symbol, untyped]
266
272
  def partition_messages(messages)
267
- system_prompts = []
268
- conversation_messages = []
273
+ system_prompts = [] #: Array[Hash[Symbol, untyped]]
274
+ conversation_messages = [] #: Array[Hash[Symbol, untyped]]
269
275
 
270
276
  messages.each do |message|
271
277
  case message
@@ -291,7 +297,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
291
297
  #--
292
298
  #: (Riffer::Messages::Assistant) -> Hash[Symbol, untyped]
293
299
  def convert_assistant_to_bedrock_format(message)
294
- content = []
300
+ content = [] #: Array[Hash[Symbol, untyped]]
295
301
  content << {text: message.content} if message.content && !message.content.empty?
296
302
 
297
303
  message.tool_calls.each do |tc|
@@ -326,7 +332,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
326
332
  end
327
333
 
328
334
  #--
329
- #: (Riffer::FilePart) -> Hash[Symbol, untyped]
335
+ #: (Riffer::Messages::FilePart) -> Hash[Symbol, untyped]
330
336
  def convert_file_part_to_bedrock_format(file)
331
337
  format = bedrock_format(file.media_type)
332
338
 
@@ -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
@@ -77,18 +77,18 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
77
77
  end
78
78
 
79
79
  #--
80
- #: (Hash[Symbol, untyped]) -> Anthropic::Models::Message
80
+ #: (Hash[Symbol, untyped]) -> untyped
81
81
  def execute_generate(params)
82
82
  @client.messages.create(**params)
83
83
  end
84
84
 
85
85
  #--
86
- #: (Anthropic::Models::Message) -> Riffer::TokenUsage?
86
+ #: (untyped) -> Riffer::Providers::TokenUsage?
87
87
  def extract_token_usage(response)
88
- usage = response.usage
89
- return nil unless usage
88
+ message = response #: Anthropic::Models::Message
89
+ usage = message.usage
90
90
 
91
- Riffer::TokenUsage.new(
91
+ Riffer::Providers::TokenUsage.new(
92
92
  input_tokens: usage.input_tokens,
93
93
  output_tokens: usage.output_tokens,
94
94
  cache_creation_tokens: usage.cache_creation_input_tokens,
@@ -97,30 +97,32 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
97
97
  end
98
98
 
99
99
  #--
100
- #: (Anthropic::Models::Message) -> String
100
+ #: (untyped) -> String
101
101
  def extract_content(response)
102
- content_blocks = response.content
102
+ message = response #: Anthropic::Models::Message
103
+ content_blocks = message.content
103
104
  return "" if content_blocks.nil? || content_blocks.empty?
104
105
 
105
106
  text_content = ""
106
107
 
107
108
  content_blocks.each do |block|
108
- text_content = block.text if block.type.to_s == "text"
109
+ text_content = block.text if block.is_a?(::Anthropic::Models::TextBlock)
109
110
  end
110
111
 
111
112
  text_content
112
113
  end
113
114
 
114
115
  #--
115
- #: (Anthropic::Models::Message) -> Array[Riffer::Messages::Assistant::ToolCall]
116
+ #: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
116
117
  def extract_tool_calls(response)
117
- content_blocks = response.content
118
+ message = response #: Anthropic::Models::Message
119
+ content_blocks = message.content
118
120
  return [] if content_blocks.nil? || content_blocks.empty?
119
121
 
120
- tool_calls = []
122
+ tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
121
123
 
122
124
  content_blocks.each do |block|
123
- if block.type.to_s == "tool_use"
125
+ if block.is_a?(::Anthropic::Models::ToolUseBlock)
124
126
  tool_calls << Riffer::Messages::Assistant::ToolCall.new(
125
127
  call_id: block.id,
126
128
  name: decode_tool_name(block.name, tools: @current_tools),
@@ -142,7 +144,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
142
144
  web_search_index: nil,
143
145
  web_search_json: nil,
144
146
  web_search_query: nil
145
- }
147
+ } #: Hash[Symbol, untyped]
146
148
 
147
149
  # Workaround for anthropics/anthropic-sdk-ruby#182: force identity
148
150
  # encoding so Net::HTTP/Zlib doesn't buffer SSE chunks until EOF.
@@ -154,24 +156,24 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
154
156
  begin
155
157
  stream.each do |event|
156
158
  case event
157
- when Anthropic::Models::RawContentBlockStartEvent
159
+ when ::Anthropic::Models::RawContentBlockStartEvent
158
160
  handle_raw_content_block_start(event, state: current_state)
159
- when Anthropic::Models::RawContentBlockDeltaEvent
161
+ when ::Anthropic::Models::RawContentBlockDeltaEvent
160
162
  handle_raw_content_block_delta(event, state: current_state)
161
- when Anthropic::Streaming::TextEvent
163
+ when ::Anthropic::Helpers::Streaming::TextEvent
162
164
  handle_text_event(event, state: current_state, yielder: yielder)
163
- when Anthropic::Streaming::ThinkingEvent
165
+ when ::Anthropic::Helpers::Streaming::ThinkingEvent
164
166
  handle_thinking_event(event, state: current_state, yielder: yielder)
165
- when Anthropic::Streaming::InputJsonEvent
167
+ when ::Anthropic::Helpers::Streaming::InputJsonEvent
166
168
  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
169
+ when ::Anthropic::Helpers::Streaming::ContentBlockStopEvent
170
+ block = event.content_block
171
+ handle_content_block_stop_text(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::TextBlock) && current_state[:text]
172
+ handle_content_block_stop_tool_use(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::ToolUseBlock)
173
+ handle_content_block_stop_thinking(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::ThinkingBlock) && current_state[:reasoning]
174
+ handle_content_block_stop_server_tool_use(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::ServerToolUseBlock)
175
+ handle_content_block_stop_web_search_result(event, state: current_state, yielder: yielder) if block.is_a?(::Anthropic::Models::WebSearchToolResultBlock)
176
+ when ::Anthropic::Helpers::Streaming::MessageStopEvent
175
177
  handle_message_stop(event, accumulated_message: stream.accumulated_message, yielder: yielder)
176
178
  end
177
179
  end
@@ -286,13 +288,14 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
286
288
  end
287
289
 
288
290
  #--
289
- #: (untyped, accumulated_message: Anthropic::Models::Message?, yielder: Enumerator::Yielder) -> void
291
+ #: (untyped, accumulated_message: untyped, yielder: Enumerator::Yielder) -> void
290
292
  def handle_message_stop(_event, accumulated_message:, yielder:)
291
- usage = accumulated_message&.usage
293
+ message = accumulated_message #: Anthropic::Models::Message?
294
+ usage = message&.usage
292
295
  return unless usage
293
296
 
294
297
  yielder << Riffer::StreamEvents::TokenUsageDone.new(
295
- token_usage: Riffer::TokenUsage.new(
298
+ token_usage: Riffer::Providers::TokenUsage.new(
296
299
  input_tokens: usage.input_tokens,
297
300
  output_tokens: usage.output_tokens,
298
301
  cache_creation_tokens: usage.cache_creation_input_tokens,
@@ -304,8 +307,8 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
304
307
  #--
305
308
  #: (Array[Riffer::Messages::Base]) -> Hash[Symbol, untyped]
306
309
  def partition_messages(messages)
307
- system_prompts = []
308
- conversation_messages = []
310
+ system_prompts = [] #: Array[Hash[Symbol, untyped]]
311
+ conversation_messages = [] #: Array[Hash[Symbol, untyped]]
309
312
 
310
313
  messages.each do |message|
311
314
  case message
@@ -342,7 +345,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
342
345
  #--
343
346
  #: (Riffer::Messages::Assistant) -> Hash[Symbol, untyped]
344
347
  def convert_assistant_to_anthropic_format(message)
345
- content = []
348
+ content = [] #: Array[Hash[Symbol, untyped]]
346
349
  content << {type: "text", text: message.content} if message.content && !message.content.empty?
347
350
 
348
351
  message.tool_calls.each do |tc|
@@ -358,7 +361,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
358
361
  end
359
362
 
360
363
  #--
361
- #: (Riffer::FilePart) -> Hash[Symbol, untyped]
364
+ #: (Riffer::Messages::FilePart) -> Hash[Symbol, untyped]
362
365
  def convert_file_part_to_anthropic_format(file)
363
366
  type = file.image? ? "image" : "document"
364
367
 
@@ -17,6 +17,8 @@ require "json"
17
17
  # [extract_content] extract text content from the SDK response
18
18
  # [extract_tool_calls] extract tool calls from the SDK response
19
19
  class Riffer::Providers::Base
20
+ # @rbs @current_tools: Array[singleton(Riffer::Tool)]
21
+
20
22
  include Riffer::Helpers::Dependencies
21
23
  include Riffer::Messages::Converter
22
24
 
@@ -39,10 +41,10 @@ class Riffer::Providers::Base
39
41
  # Generates text using the provider.
40
42
  #
41
43
  #--
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
44
+ #: (?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
45
  def generate_text(prompt: nil, system: nil, messages: nil, model: nil, files: nil, **options)
44
46
  validate_input!(prompt: prompt, system: system, messages: messages)
45
- @current_tools = options[:tools] || []
47
+ @current_tools = options[:tools] || [] #: Array[singleton(Riffer::Tool)]
46
48
  messages = normalize_messages(prompt: prompt, system: system, messages: messages, files: files)
47
49
  validate_normalized_messages!(messages)
48
50
  messages = merge_consecutive_messages(messages)
@@ -65,10 +67,10 @@ class Riffer::Providers::Base
65
67
  # Streams text from the provider.
66
68
  #
67
69
  #--
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]
70
+ #: (?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
71
  def stream_text(prompt: nil, system: nil, messages: nil, model: nil, files: nil, **options)
70
72
  validate_input!(prompt: prompt, system: system, messages: messages)
71
- @current_tools = options[:tools] || []
73
+ @current_tools = options[:tools] || [] #: Array[singleton(Riffer::Tool)]
72
74
  messages = normalize_messages(prompt: prompt, system: system, messages: messages, files: files)
73
75
  validate_normalized_messages!(messages)
74
76
  messages = merge_consecutive_messages(messages)
@@ -87,7 +89,7 @@ class Riffer::Providers::Base
87
89
  end
88
90
 
89
91
  #--
90
- #: (String, tools: Array[Riffer::Tool]) -> String
92
+ #: (String, tools: Array[singleton(Riffer::Tool)]) -> String
91
93
  def decode_tool_name(wire_name, tools:)
92
94
  tool = tools.find { |t| encode_tool_name(t.name) == wire_name }
93
95
  tool ? tool.name : wire_name
@@ -112,7 +114,7 @@ class Riffer::Providers::Base
112
114
  end
113
115
 
114
116
  #--
115
- #: (untyped) -> Riffer::TokenUsage?
117
+ #: (untyped) -> Riffer::Providers::TokenUsage?
116
118
  def extract_token_usage(response)
117
119
  raise NotImplementedError, "Subclasses must implement #extract_token_usage"
118
120
  end
@@ -166,7 +168,7 @@ class Riffer::Providers::Base
166
168
  end
167
169
 
168
170
  #--
169
- #: (prompt: String?, system: String?, messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?files: Array[Hash[Symbol, untyped] | Riffer::FilePart]?) -> Array[Riffer::Messages::Base]
171
+ #: (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
172
  def normalize_messages(prompt:, system:, messages:, files: nil)
171
173
  if messages && files && !files.empty?
172
174
  raise Riffer::ArgumentError, "cannot provide both files and messages; attach files to individual messages instead"
@@ -176,10 +178,11 @@ class Riffer::Providers::Base
176
178
  return messages.map { |msg| convert_to_message_object(msg) }
177
179
  end
178
180
 
179
- result = []
181
+ result = [] #: Array[Riffer::Messages::Base]
180
182
  result << Riffer::Messages::System.new(system) if system
181
183
  file_parts = (files || []).map { |f| convert_to_file_part(f) }
182
- result << Riffer::Messages::User.new(prompt, files: file_parts)
184
+ prompt_text = prompt #: String
185
+ result << Riffer::Messages::User.new(prompt_text, files: file_parts)
183
186
  result
184
187
  end
185
188