riffer 0.31.0 → 0.32.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 (213) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/code-style.md +63 -4
  3. data/.agents/rbs-inline.md +1 -6
  4. data/.release-please-manifest.json +1 -1
  5. data/AGENTS.md +1 -2
  6. data/CHANGELOG.md +18 -0
  7. data/docs/08_MESSAGES.md +1 -1
  8. data/docs/14_MCP.md +50 -5
  9. data/docs/providers/02_AMAZON_BEDROCK.md +14 -0
  10. data/lib/riffer/agent/config.rb +42 -47
  11. data/lib/riffer/agent/context.rb +70 -50
  12. data/lib/riffer/agent/response.rb +4 -20
  13. data/lib/riffer/agent/run.rb +28 -67
  14. data/lib/riffer/agent/serializer.rb +22 -81
  15. data/lib/riffer/agent/session/repair.rb +14 -40
  16. data/lib/riffer/agent/session.rb +25 -67
  17. data/lib/riffer/agent/structured_output/result.rb +3 -11
  18. data/lib/riffer/agent/structured_output.rb +5 -13
  19. data/lib/riffer/agent.rb +74 -192
  20. data/lib/riffer/config.rb +34 -101
  21. data/lib/riffer/evals/evaluator.rb +7 -27
  22. data/lib/riffer/evals/evaluator_runner.rb +11 -19
  23. data/lib/riffer/evals/judge.rb +4 -25
  24. data/lib/riffer/evals/result.rb +1 -18
  25. data/lib/riffer/evals/run_result.rb +0 -11
  26. data/lib/riffer/evals/scenario_result.rb +0 -14
  27. data/lib/riffer/evals.rb +0 -6
  28. data/lib/riffer/guardrail.rb +4 -27
  29. data/lib/riffer/guardrails/modification.rb +0 -10
  30. data/lib/riffer/guardrails/result.rb +3 -30
  31. data/lib/riffer/guardrails/runner.rb +5 -22
  32. data/lib/riffer/guardrails/tripwire.rb +1 -19
  33. data/lib/riffer/guardrails.rb +2 -4
  34. data/lib/riffer/helpers/call_or_value.rb +4 -3
  35. data/lib/riffer/helpers/class_name_converter.rb +3 -1
  36. data/lib/riffer/helpers/dependencies.rb +5 -7
  37. data/lib/riffer/helpers.rb +0 -5
  38. data/lib/riffer/mcp/authenticated_tool.rb +9 -9
  39. data/lib/riffer/mcp/client.rb +12 -17
  40. data/lib/riffer/mcp/manifest.rb +13 -10
  41. data/lib/riffer/mcp/registration.rb +2 -11
  42. data/lib/riffer/mcp/registry.rb +44 -52
  43. data/lib/riffer/mcp/search_tool.rb +53 -0
  44. data/lib/riffer/mcp/tool_factory.rb +13 -18
  45. data/lib/riffer/mcp.rb +12 -17
  46. data/lib/riffer/messages/assistant.rb +2 -9
  47. data/lib/riffer/messages/base.rb +46 -16
  48. data/lib/riffer/messages/file_part.rb +32 -24
  49. data/lib/riffer/messages/system.rb +0 -5
  50. data/lib/riffer/messages/tool.rb +0 -10
  51. data/lib/riffer/messages/user.rb +0 -10
  52. data/lib/riffer/messages.rb +0 -7
  53. data/lib/riffer/params/boolean.rb +2 -4
  54. data/lib/riffer/params/param.rb +28 -39
  55. data/lib/riffer/params.rb +9 -21
  56. data/lib/riffer/providers/amazon_bedrock.rb +42 -28
  57. data/lib/riffer/providers/anthropic.rb +4 -9
  58. data/lib/riffer/providers/azure_open_ai.rb +3 -19
  59. data/lib/riffer/providers/base.rb +13 -26
  60. data/lib/riffer/providers/gemini.rb +4 -4
  61. data/lib/riffer/providers/mock.rb +6 -26
  62. data/lib/riffer/providers/open_ai.rb +6 -8
  63. data/lib/riffer/providers/open_router.rb +4 -10
  64. data/lib/riffer/providers/repository.rb +4 -3
  65. data/lib/riffer/providers/token_usage.rb +9 -20
  66. data/lib/riffer/providers.rb +0 -8
  67. data/lib/riffer/runner/fibers.rb +10 -16
  68. data/lib/riffer/runner/sequential.rb +1 -4
  69. data/lib/riffer/runner/threaded.rb +3 -14
  70. data/lib/riffer/runner.rb +2 -15
  71. data/lib/riffer/skills/activate_tool.rb +2 -11
  72. data/lib/riffer/skills/adapter.rb +4 -22
  73. data/lib/riffer/skills/backend.rb +7 -21
  74. data/lib/riffer/skills/config.rb +10 -31
  75. data/lib/riffer/skills/context.rb +5 -20
  76. data/lib/riffer/skills/filesystem_backend.rb +7 -25
  77. data/lib/riffer/skills/frontmatter.rb +10 -28
  78. data/lib/riffer/skills/markdown_adapter.rb +2 -9
  79. data/lib/riffer/skills/xml_adapter.rb +2 -8
  80. data/lib/riffer/stream_events/base.rb +1 -6
  81. data/lib/riffer/stream_events/guardrail_modification.rb +1 -8
  82. data/lib/riffer/stream_events/guardrail_tripwire.rb +1 -8
  83. data/lib/riffer/stream_events/interrupt.rb +4 -7
  84. data/lib/riffer/stream_events/reasoning_delta.rb +2 -4
  85. data/lib/riffer/stream_events/reasoning_done.rb +2 -4
  86. data/lib/riffer/stream_events/skill_activation.rb +2 -4
  87. data/lib/riffer/stream_events/text_delta.rb +0 -2
  88. data/lib/riffer/stream_events/text_done.rb +1 -3
  89. data/lib/riffer/stream_events/token_usage_done.rb +1 -8
  90. data/lib/riffer/stream_events/tool_call_delta.rb +2 -3
  91. data/lib/riffer/stream_events/tool_call_done.rb +1 -3
  92. data/lib/riffer/stream_events/web_search_done.rb +1 -3
  93. data/lib/riffer/stream_events/web_search_status.rb +2 -3
  94. data/lib/riffer/stream_events.rb +0 -10
  95. data/lib/riffer/tool.rb +6 -13
  96. data/lib/riffer/tools/response.rb +8 -4
  97. data/lib/riffer/tools/runtime/fibers.rb +0 -3
  98. data/lib/riffer/tools/runtime/inline.rb +1 -4
  99. data/lib/riffer/tools/runtime/threaded.rb +0 -2
  100. data/lib/riffer/tools/runtime.rb +5 -38
  101. data/lib/riffer/tools/toolable.rb +5 -16
  102. data/lib/riffer/tools.rb +0 -4
  103. data/lib/riffer/version.rb +1 -1
  104. data/lib/riffer.rb +7 -8
  105. data/sig/generated/riffer/agent/config.rbs +29 -46
  106. data/sig/generated/riffer/agent/context.rbs +40 -48
  107. data/sig/generated/riffer/agent/response.rbs +4 -20
  108. data/sig/generated/riffer/agent/run.rbs +12 -61
  109. data/sig/generated/riffer/agent/serializer.rbs +21 -80
  110. data/sig/generated/riffer/agent/session/repair.rbs +12 -40
  111. data/sig/generated/riffer/agent/session.rbs +25 -67
  112. data/sig/generated/riffer/agent/structured_output/result.rbs +2 -10
  113. data/sig/generated/riffer/agent/structured_output.rbs +5 -12
  114. data/sig/generated/riffer/agent.rbs +57 -186
  115. data/sig/generated/riffer/config.rbs +34 -100
  116. data/sig/generated/riffer/evals/evaluator.rbs +7 -27
  117. data/sig/generated/riffer/evals/evaluator_runner.rbs +9 -19
  118. data/sig/generated/riffer/evals/judge.rbs +4 -24
  119. data/sig/generated/riffer/evals/result.rbs +1 -17
  120. data/sig/generated/riffer/evals/run_result.rbs +0 -10
  121. data/sig/generated/riffer/evals/scenario_result.rbs +0 -13
  122. data/sig/generated/riffer/evals.rbs +0 -6
  123. data/sig/generated/riffer/guardrail.rbs +4 -27
  124. data/sig/generated/riffer/guardrails/modification.rbs +0 -10
  125. data/sig/generated/riffer/guardrails/result.rbs +3 -30
  126. data/sig/generated/riffer/guardrails/runner.rbs +5 -22
  127. data/sig/generated/riffer/guardrails/tripwire.rbs +1 -19
  128. data/sig/generated/riffer/guardrails.rbs +2 -4
  129. data/sig/generated/riffer/helpers/call_or_value.rbs +4 -3
  130. data/sig/generated/riffer/helpers/class_name_converter.rbs +1 -1
  131. data/sig/generated/riffer/helpers/dependencies.rbs +3 -7
  132. data/sig/generated/riffer/helpers.rbs +0 -5
  133. data/sig/generated/riffer/mcp/authenticated_tool.rbs +5 -4
  134. data/sig/generated/riffer/mcp/client.rbs +10 -16
  135. data/sig/generated/riffer/mcp/manifest.rbs +9 -9
  136. data/sig/generated/riffer/mcp/registration.rbs +2 -10
  137. data/sig/generated/riffer/mcp/registry.rbs +11 -18
  138. data/sig/generated/riffer/mcp/search_tool.rbs +26 -0
  139. data/sig/generated/riffer/mcp/tool_factory.rbs +10 -15
  140. data/sig/generated/riffer/mcp.rbs +10 -17
  141. data/sig/generated/riffer/messages/assistant.rbs +2 -8
  142. data/sig/generated/riffer/messages/base.rbs +11 -16
  143. data/sig/generated/riffer/messages/file_part.rbs +13 -23
  144. data/sig/generated/riffer/messages/system.rbs +0 -4
  145. data/sig/generated/riffer/messages/tool.rbs +0 -9
  146. data/sig/generated/riffer/messages/user.rbs +0 -9
  147. data/sig/generated/riffer/messages.rbs +0 -7
  148. data/sig/generated/riffer/params/boolean.rbs +2 -4
  149. data/sig/generated/riffer/params/param.rbs +21 -39
  150. data/sig/generated/riffer/params.rbs +9 -21
  151. data/sig/generated/riffer/providers/amazon_bedrock.rbs +21 -25
  152. data/sig/generated/riffer/providers/anthropic.rbs +2 -7
  153. data/sig/generated/riffer/providers/azure_open_ai.rbs +3 -18
  154. data/sig/generated/riffer/providers/base.rbs +9 -25
  155. data/sig/generated/riffer/providers/gemini.rbs +0 -2
  156. data/sig/generated/riffer/providers/mock.rbs +6 -26
  157. data/sig/generated/riffer/providers/open_ai.rbs +1 -5
  158. data/sig/generated/riffer/providers/open_router.rbs +4 -10
  159. data/sig/generated/riffer/providers/repository.rbs +2 -3
  160. data/sig/generated/riffer/providers/token_usage.rbs +6 -16
  161. data/sig/generated/riffer/providers.rbs +0 -8
  162. data/sig/generated/riffer/runner/fibers.rbs +8 -15
  163. data/sig/generated/riffer/runner/sequential.rbs +1 -3
  164. data/sig/generated/riffer/runner/threaded.rbs +3 -13
  165. data/sig/generated/riffer/runner.rbs +2 -14
  166. data/sig/generated/riffer/skills/activate_tool.rbs +2 -11
  167. data/sig/generated/riffer/skills/adapter.rbs +4 -22
  168. data/sig/generated/riffer/skills/backend.rbs +7 -21
  169. data/sig/generated/riffer/skills/config.rbs +10 -31
  170. data/sig/generated/riffer/skills/context.rbs +5 -20
  171. data/sig/generated/riffer/skills/filesystem_backend.rbs +7 -24
  172. data/sig/generated/riffer/skills/frontmatter.rbs +10 -27
  173. data/sig/generated/riffer/skills/markdown_adapter.rbs +2 -9
  174. data/sig/generated/riffer/skills/xml_adapter.rbs +2 -8
  175. data/sig/generated/riffer/stream_events/base.rbs +1 -6
  176. data/sig/generated/riffer/stream_events/guardrail_modification.rbs +1 -8
  177. data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +1 -8
  178. data/sig/generated/riffer/stream_events/interrupt.rbs +4 -7
  179. data/sig/generated/riffer/stream_events/reasoning_delta.rbs +2 -4
  180. data/sig/generated/riffer/stream_events/reasoning_done.rbs +2 -4
  181. data/sig/generated/riffer/stream_events/skill_activation.rbs +2 -4
  182. data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
  183. data/sig/generated/riffer/stream_events/text_done.rbs +1 -3
  184. data/sig/generated/riffer/stream_events/token_usage_done.rbs +1 -7
  185. data/sig/generated/riffer/stream_events/tool_call_delta.rbs +2 -3
  186. data/sig/generated/riffer/stream_events/tool_call_done.rbs +1 -3
  187. data/sig/generated/riffer/stream_events/web_search_done.rbs +1 -3
  188. data/sig/generated/riffer/stream_events/web_search_status.rbs +2 -3
  189. data/sig/generated/riffer/stream_events.rbs +0 -10
  190. data/sig/generated/riffer/tool.rbs +5 -12
  191. data/sig/generated/riffer/tools/response.rbs +6 -4
  192. data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -3
  193. data/sig/generated/riffer/tools/runtime/inline.rbs +1 -3
  194. data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -2
  195. data/sig/generated/riffer/tools/runtime.rbs +5 -37
  196. data/sig/generated/riffer/tools/toolable.rbs +4 -14
  197. data/sig/generated/riffer/tools.rbs +0 -4
  198. data/sig/generated/riffer.rbs +5 -4
  199. data/sig/manual/riffer/agent/session/repair.rbs +5 -0
  200. data/sig/manual/riffer/evals/evaluator_runner.rbs +5 -0
  201. data/sig/manual/riffer/helpers/class_name_converter.rbs +5 -0
  202. data/sig/manual/riffer/helpers/dependencies.rbs +5 -0
  203. data/sig/manual/riffer/mcp/authenticated_tool.rbs +5 -0
  204. data/sig/manual/riffer/mcp/registry.rbs +5 -0
  205. data/sig/manual/riffer/mcp/tool_factory.rbs +5 -0
  206. data/sig/manual/riffer/mcp.rbs +5 -0
  207. data/sig/manual/riffer/providers/repository.rbs +5 -0
  208. data/sig/manual/riffer.rbs +5 -0
  209. metadata +17 -9
  210. data/.agents/rdoc.md +0 -69
  211. data/lib/riffer/messages/converter.rb +0 -90
  212. data/sig/generated/riffer/messages/converter.rbs +0 -33
  213. data/sig/manual/riffer/tools/toolable.rbs +0 -6
@@ -3,35 +3,17 @@
3
3
 
4
4
  require "json"
5
5
 
6
- # Base class for all LLM providers in the Riffer framework.
7
- #
8
- # Provides a template-method flow for text generation and streaming.
9
- # Subclasses implement five hook methods; the base class orchestrates them.
10
- #
11
- # ==== Hook methods
12
- #
13
- # [build_request_params] convert messages, tools, and options into SDK params
14
- # [execute_generate] call the SDK and return the raw response
15
- # [execute_stream] call the streaming SDK, mapping events to the yielder
16
- # [extract_token_usage] pull token counts from the SDK response
17
- # [extract_content] extract text content from the SDK response
18
- # [extract_tool_calls] extract tool calls from the SDK response
6
+ # Base class for all LLM providers. A template-method flow: subclasses implement
7
+ # the hooks (+build_request_params+, +execute_generate+, +execute_stream+,
8
+ # +extract_token_usage+, +extract_content+, +extract_tool_calls+) and the base
9
+ # class orchestrates them.
19
10
  class Riffer::Providers::Base
20
11
  # @rbs @current_tools: Array[singleton(Riffer::Tool)]
21
12
 
22
- include Riffer::Helpers::Dependencies
23
- include Riffer::Messages::Converter
24
-
25
13
  WIRE_SEPARATOR = "__" #: String
26
14
 
27
- # Returns the preferred skill adapter for this provider.
28
- #
29
- # Override in subclasses for provider-specific formats. Subclasses may
30
- # introspect +model+ (the resolved model identifier, e.g. the part after
31
- # +provider/+) to pick an adapter that matches the underlying model
32
- # family — useful for proxy providers like Amazon Bedrock that host
33
- # models from multiple vendors.
34
- #
15
+ # Returns the preferred skill adapter for this provider; override in
16
+ # subclasses (optionally introspecting +model+) for provider-specific formats.
35
17
  #--
36
18
  #: (?String?) -> singleton(Riffer::Skills::Adapter)
37
19
  def self.skills_adapter(model = nil)
@@ -82,6 +64,11 @@ class Riffer::Providers::Base
82
64
 
83
65
  private
84
66
 
67
+ #: (String) -> true
68
+ def depends_on(gem_name)
69
+ Riffer::Helpers::Dependencies.depends_on(gem_name)
70
+ end
71
+
85
72
  #--
86
73
  #: (String) -> String
87
74
  def encode_tool_name(name)
@@ -175,12 +162,12 @@ class Riffer::Providers::Base
175
162
  end
176
163
 
177
164
  if messages
178
- return messages.map { |msg| convert_to_message_object(msg) }
165
+ return messages.map { |msg| Riffer::Messages::Base.from_hash(msg) }
179
166
  end
180
167
 
181
168
  result = [] #: Array[Riffer::Messages::Base]
182
169
  result << Riffer::Messages::System.new(system) if system
183
- file_parts = (files || []).map { |f| convert_to_file_part(f) }
170
+ file_parts = (files || []).map { |f| Riffer::Messages::FilePart.from_hash(f) }
184
171
  prompt_text = prompt #: String
185
172
  result << Riffer::Messages::User.new(prompt_text, files: file_parts)
186
173
  result
@@ -17,8 +17,6 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
17
17
  DEFAULT_OPEN_TIMEOUT = 10 #: Integer
18
18
  DEFAULT_READ_TIMEOUT = 60 #: Integer
19
19
 
20
- # Initializes the Gemini provider.
21
- #
22
20
  #--
23
21
  #: (?api_key: String?, ?open_timeout: Integer?, ?read_timeout: Integer?, **untyped) -> void
24
22
  def initialize(api_key: nil, open_timeout: nil, read_timeout: nil, **options)
@@ -107,7 +105,8 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
107
105
 
108
106
  Riffer::Providers::TokenUsage.new(
109
107
  input_tokens: usage[:promptTokenCount] || 0,
110
- output_tokens: usage[:candidatesTokenCount] || 0
108
+ output_tokens: usage[:candidatesTokenCount] || 0,
109
+ cache_read_tokens: usage[:cachedContentTokenCount]
111
110
  )
112
111
  end
113
112
 
@@ -163,7 +162,8 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
163
162
  yielder << Riffer::StreamEvents::TokenUsageDone.new(
164
163
  token_usage: Riffer::Providers::TokenUsage.new(
165
164
  input_tokens: usage[:promptTokenCount] || 0,
166
- output_tokens: usage[:candidatesTokenCount] || 0
165
+ output_tokens: usage[:candidatesTokenCount] || 0,
166
+ cache_read_tokens: usage[:cachedContentTokenCount]
167
167
  )
168
168
  )
169
169
  end
@@ -1,22 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Mock provider for mocking LLM responses in tests.
5
- #
6
- # No external gems required.
4
+ # Mock provider for mocking LLM responses in tests; no external gems required.
7
5
  class Riffer::Providers::Mock < Riffer::Providers::Base
8
6
  # @rbs @responses: Array[Hash[Symbol, untyped]]
9
7
  # @rbs @current_index: Integer
10
8
  # @rbs @stubbed_responses: Array[Hash[Symbol, untyped]]
11
9
 
12
- # Returns the preferred skill adapter for the given mock model.
13
- #
14
- # Mock is used to stand in for any real provider in tests, so the model
15
- # string itself is the only signal we have. When the model name contains
16
- # +claude+ (e.g. +mock/claude-sonnet-4-6+), pick the XML adapter to
17
- # mirror what a real Claude-backed provider would do; otherwise fall
18
- # back to Markdown.
19
- #
10
+ # Returns the skill adapter for the mock model — XML when the model name
11
+ # contains +claude+ (mirroring a real Claude provider), else Markdown.
20
12
  #--
21
13
  #: (?String?) -> singleton(Riffer::Skills::Adapter)
22
14
  def self.skills_adapter(model = nil)
@@ -27,13 +19,8 @@ class Riffer::Providers::Mock < Riffer::Providers::Base
27
19
  # Array of recorded method calls for assertions.
28
20
  attr_reader :calls #: Array[Hash[Symbol, untyped]]
29
21
 
30
- # Initializes the mock provider.
31
- #
32
- # +responses:+ accepts an array of response hashes in the same shape
33
- # +#stub_response+ takes — raw +tool_calls:+ hashes are normalised to
34
- # +Riffer::Messages::Assistant::ToolCall+ instances. This is the canonical
35
- # way to pre-configure canned LLM responses on an agent via
36
- # +provider_options responses: [...]+.
22
+ # +responses:+ pre-configures canned responses (same shape as
23
+ # +#stub_response+), typically set via +provider_options responses: [...]+.
37
24
  #
38
25
  # Riffer::Providers::Mock.new(responses: [
39
26
  # {content: "", tool_calls: [{name: "tool_a", arguments: "{}"}]},
@@ -49,9 +36,7 @@ class Riffer::Providers::Mock < Riffer::Providers::Base
49
36
  @stubbed_responses = []
50
37
  end
51
38
 
52
- # Stubs the next response from the provider.
53
- #
54
- # Can be called multiple times to queue responses.
39
+ # Stubs the next response; call repeatedly to queue several.
55
40
  #
56
41
  # provider.stub_response("Hello")
57
42
  # provider.stub_response("", tool_calls: [{name: "my_tool", arguments: '{"key":"value"}'}])
@@ -73,11 +58,6 @@ class Riffer::Providers::Mock < Riffer::Providers::Base
73
58
 
74
59
  private
75
60
 
76
- # Normalises a response hash into Mock's internal format. Accepts the
77
- # +#stub_response+ kwargs shape (+content:+, +tool_calls:+, +token_usage:+)
78
- # or a pre-built hash with already-converted ToolCall instances. Raw
79
- # +tool_calls:+ hashes are wrapped in +Riffer::Messages::Assistant::ToolCall+.
80
- #
81
61
  #--
82
62
  #: (Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
83
63
  def normalize_response(response)
@@ -1,14 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # OpenAI provider for GPT models.
5
- #
6
- # Requires the +openai+ gem to be installed.
4
+ # OpenAI provider for GPT models. Requires the +openai+ gem.
7
5
  class Riffer::Providers::OpenAI < Riffer::Providers::Base
8
6
  WEB_SEARCH_TOOL_TYPE = "web_search_preview" #: String
9
7
 
10
- # Initializes the OpenAI provider.
11
- #
12
8
  #--
13
9
  #: (**untyped) -> void
14
10
  def initialize(**options)
@@ -80,7 +76,8 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
80
76
 
81
77
  Riffer::Providers::TokenUsage.new(
82
78
  input_tokens: usage.input_tokens,
83
- output_tokens: usage.output_tokens
79
+ output_tokens: usage.output_tokens,
80
+ cache_read_tokens: usage.input_tokens_details&.cached_tokens
84
81
  )
85
82
  end
86
83
 
@@ -229,7 +226,8 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
229
226
  yielder << Riffer::StreamEvents::TokenUsageDone.new(
230
227
  token_usage: Riffer::Providers::TokenUsage.new(
231
228
  input_tokens: usage.input_tokens,
232
- output_tokens: usage.output_tokens
229
+ output_tokens: usage.output_tokens,
230
+ cache_read_tokens: usage.input_tokens_details&.cached_tokens
233
231
  )
234
232
  )
235
233
  end
@@ -251,7 +249,7 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
251
249
  yielder << Riffer::StreamEvents::WebSearchStatus.new("open_page", url: action.url)
252
250
  when ::OpenAI::Models::Responses::ResponseFunctionWebSearch::Action::Search
253
251
  sources = (action.sources || []).map { |s| {title: nil, url: s.url} }
254
- yielder << Riffer::StreamEvents::WebSearchDone.new(action.query, sources: sources)
252
+ yielder << Riffer::StreamEvents::WebSearchDone.new(action.query || "", sources: sources)
255
253
  end
256
254
  end
257
255
 
@@ -3,19 +3,13 @@
3
3
 
4
4
  require "json"
5
5
 
6
- # OpenRouter provider for the OpenRouter unified gateway (https://openrouter.ai).
7
- #
8
- # Requires the +openai+ gem to be installed. OpenRouter exposes an
9
- # OpenAI-compatible Chat Completions endpoint, so this provider reuses
10
- # the OpenAI Ruby SDK with a +base_url+ override.
11
- #
12
- # The +api_key+ falls back to <tt>Riffer.config.openrouter.api_key</tt>
13
- # and then to +OPENROUTER_API_KEY+.
6
+ # OpenRouter provider (https://openrouter.ai). Requires the +openai+ gem —
7
+ # OpenRouter exposes an OpenAI-compatible endpoint, so this reuses the OpenAI
8
+ # SDK with a +base_url+ override. +api_key+ falls back to config, then
9
+ # +OPENROUTER_API_KEY+.
14
10
  class Riffer::Providers::OpenRouter < Riffer::Providers::Base
15
11
  BASE_URL = "https://openrouter.ai/api/v1" #: String
16
12
 
17
- # Initializes the OpenRouter provider.
18
- #
19
13
  #--
20
14
  #: (?api_key: String?, **untyped) -> void
21
15
  def initialize(api_key: nil, **options)
@@ -2,8 +2,9 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  # Registry for finding provider classes by identifier.
5
- class Riffer::Providers::Repository
6
- # Mapping of provider identifiers to provider class lambdas.
5
+ module Riffer::Providers::Repository
6
+ extend self
7
+
7
8
  REPO = {
8
9
  amazon_bedrock: -> { Riffer::Providers::AmazonBedrock },
9
10
  anthropic: -> { Riffer::Providers::Anthropic },
@@ -18,7 +19,7 @@ class Riffer::Providers::Repository
18
19
  #
19
20
  #--
20
21
  #: ((String | Symbol)) -> singleton(Riffer::Providers::Base)?
21
- def self.find(identifier)
22
+ def find(identifier)
22
23
  REPO.fetch(identifier.to_sym, nil)&.call
23
24
  end
24
25
  end
@@ -2,14 +2,6 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  # Represents token usage data from an LLM API call.
5
- #
6
- # Tracks input tokens, output tokens, and optional cache statistics.
7
- #
8
- # token_usage = Riffer::Providers::TokenUsage.new(input_tokens: 100, output_tokens: 50)
9
- # token_usage.total_tokens # => 150
10
- #
11
- # combined = token_usage1 + token_usage2 # Combine multiple token usage objects
12
- #
13
5
  class Riffer::Providers::TokenUsage
14
6
  # Number of tokens in the input/prompt.
15
7
  attr_reader :input_tokens #: Integer
@@ -17,18 +9,18 @@ class Riffer::Providers::TokenUsage
17
9
  # Number of tokens in the output/response.
18
10
  attr_reader :output_tokens #: Integer
19
11
 
20
- # Number of tokens written to cache (Anthropic-specific).
21
- attr_reader :cache_creation_tokens #: Integer?
12
+ # Number of tokens written to cache, when the provider reports it.
13
+ attr_reader :cache_write_tokens #: Integer?
22
14
 
23
- # Number of tokens read from cache (Anthropic-specific).
15
+ # Number of tokens read from cache, when the provider reports it.
24
16
  attr_reader :cache_read_tokens #: Integer?
25
17
 
26
18
  #--
27
- #: (input_tokens: Integer, output_tokens: Integer, ?cache_creation_tokens: Integer?, ?cache_read_tokens: Integer?) -> void
28
- def initialize(input_tokens:, output_tokens:, cache_creation_tokens: nil, cache_read_tokens: nil)
19
+ #: (input_tokens: Integer, output_tokens: Integer, ?cache_write_tokens: Integer?, ?cache_read_tokens: Integer?) -> void
20
+ def initialize(input_tokens:, output_tokens:, cache_write_tokens: nil, cache_read_tokens: nil)
29
21
  @input_tokens = input_tokens
30
22
  @output_tokens = output_tokens
31
- @cache_creation_tokens = cache_creation_tokens
23
+ @cache_write_tokens = cache_write_tokens
32
24
  @cache_read_tokens = cache_read_tokens
33
25
  end
34
26
 
@@ -48,20 +40,17 @@ class Riffer::Providers::TokenUsage
48
40
  Riffer::Providers::TokenUsage.new(
49
41
  input_tokens: input_tokens + other.input_tokens,
50
42
  output_tokens: output_tokens + other.output_tokens,
51
- cache_creation_tokens: add_nullable(cache_creation_tokens, other.cache_creation_tokens),
43
+ cache_write_tokens: add_nullable(cache_write_tokens, other.cache_write_tokens),
52
44
  cache_read_tokens: add_nullable(cache_read_tokens, other.cache_read_tokens)
53
45
  )
54
46
  end
55
47
 
56
- # Converts the token usage to a hash representation.
57
- #
58
- # Cache tokens are omitted if nil.
59
- #
48
+ # Converts the token usage to a hash; cache tokens are omitted when nil.
60
49
  #--
61
50
  #: () -> Hash[Symbol, Integer]
62
51
  def to_h
63
52
  hash = {input_tokens: input_tokens, output_tokens: output_tokens}
64
- hash[:cache_creation_tokens] = cache_creation_tokens if cache_creation_tokens
53
+ hash[:cache_write_tokens] = cache_write_tokens if cache_write_tokens
65
54
  hash[:cache_read_tokens] = cache_read_tokens if cache_read_tokens
66
55
  hash
67
56
  end
@@ -1,13 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Namespace for LLM provider adapters in the Riffer framework.
5
- #
6
- # Providers connect Riffer to LLM services:
7
- # - Riffer::Providers::OpenAI - OpenAI GPT models
8
- # - Riffer::Providers::AzureOpenAI - Azure OpenAI GPT models
9
- # - Riffer::Providers::AmazonBedrock - AWS Bedrock models
10
- # - Riffer::Providers::OpenRouter - OpenRouter unified gateway
11
- # - Riffer::Providers::Mock - Mock provider for testing
12
4
  module Riffer::Providers
13
5
  end
@@ -2,25 +2,12 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  # Processes items concurrently using fibers via the +async+ gem.
5
- #
6
- # All items run as fibers simultaneously by default. When
7
- # +max_concurrency+ is set, an <tt>Async::Semaphore</tt> limits how many
8
- # fibers execute at once.
9
- #
10
- # If multiple fibers raise, only the first exception is re-raised
11
- # after all fibers finish; subsequent errors are discarded.
12
- #
13
- # runner = Riffer::Runner::Fibers.new
14
- # runner.map(items) { |item| expensive_operation(item) }
15
- #
5
+ # +max_concurrency+ caps simultaneous fibers via an <tt>Async::Semaphore</tt>.
6
+ # If multiple fibers raise, only the first exception is re-raised after all
7
+ # finish.
16
8
  class Riffer::Runner::Fibers < Riffer::Runner
17
9
  # @rbs @max_concurrency: Integer?
18
10
 
19
- include Riffer::Helpers::Dependencies
20
-
21
- # [max_concurrency] maximum number of fibers to run simultaneously.
22
- # When +nil+, all fibers run without limit.
23
- #
24
11
  #--
25
12
  #: (?max_concurrency: Integer?) -> void
26
13
  def initialize(max_concurrency: nil)
@@ -62,4 +49,11 @@ class Riffer::Runner::Fibers < Riffer::Runner
62
49
 
63
50
  results
64
51
  end
52
+
53
+ private
54
+
55
+ #: (String) -> true
56
+ def depends_on(gem_name)
57
+ Riffer::Helpers::Dependencies.depends_on(gem_name)
58
+ end
65
59
  end
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Processes items sequentially in the current thread.
5
- #
6
- # This is the default runner used when no concurrency is needed.
7
- #
4
+ # Processes items sequentially in the current thread — the default runner.
8
5
  class Riffer::Runner::Sequential < Riffer::Runner
9
6
  #--
10
7
  #: (Array[untyped], context: Riffer::Agent::Context?) { (untyped) -> untyped } -> Array[untyped]
@@ -1,25 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Processes items concurrently using a thread pool.
5
- #
6
- # Maintains up to +max_concurrency+ worker threads that pull items from
7
- # a shared queue. When a worker finishes one item it immediately picks
8
- # up the next, so a single slow item does not block other workers.
9
- #
10
- # If multiple workers raise, only the first exception is re-raised
11
- # after all workers finish; subsequent errors are discarded.
12
- #
13
- # runner = Riffer::Runner::Threaded.new(max_concurrency: 3)
14
- # runner.map(items) { |item| expensive_operation(item) }
15
- #
4
+ # Processes items concurrently using a thread pool of up to +max_concurrency+
5
+ # workers pulling from a shared queue, so a slow item doesn't block others. If
6
+ # multiple workers raise, only the first exception is re-raised after all finish.
16
7
  class Riffer::Runner::Threaded < Riffer::Runner
17
8
  # @rbs @max_concurrency: Integer
18
9
 
19
10
  DEFAULT_MAX_CONCURRENCY = 5 #: Integer
20
11
 
21
- # [max_concurrency] maximum number of threads to run simultaneously.
22
- #
23
12
  #--
24
13
  #: (?max_concurrency: Integer) -> void
25
14
  def initialize(max_concurrency: DEFAULT_MAX_CONCURRENCY)
data/lib/riffer/runner.rb CHANGED
@@ -1,23 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Generic concurrency primitive for batch execution.
5
- #
6
- # Subclasses implement +map+ to control how items are processed
7
- # (sequentially, threaded, etc.).
8
- #
9
- # runner = Riffer::Runner::Sequential.new
10
- # runner.map([1, 2, 3], context: ctx) { |n| n * 2 }
11
- # # => [2, 4, 6]
12
- #
4
+ # Generic concurrency primitive for batch execution. Subclasses implement
5
+ # +map+ to control how items are processed.
13
6
  class Riffer::Runner
14
7
  # Maps over items using the provided block.
15
- #
16
- # [items] the items to process.
17
- # [context] context hash forwarded from the agent.
18
- #
19
- # Raises NotImplementedError if not implemented by subclass.
20
- #
21
8
  #--
22
9
  #: (Array[untyped], context: Riffer::Agent::Context?) { (untyped) -> untyped } -> Array[untyped]
23
10
  def map(items, context:, &block)
@@ -1,12 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Tool for the LLM to activate a skill and receive its full instructions.
5
- #
6
- # Registered automatically when an agent has skills configured.
7
- # The LLM calls this when a task matches an available skill's description.
8
- #
9
- # See Riffer::Skills::Context.
4
+ # Tool the LLM calls to activate a skill and receive its instructions;
5
+ # registered automatically when an agent has skills configured.
10
6
  class Riffer::Skills::ActivateTool < Riffer::Tool
11
7
  identifier "skill_activate"
12
8
  description "Activates a skill and returns its instructions. " \
@@ -18,11 +14,6 @@ class Riffer::Skills::ActivateTool < Riffer::Tool
18
14
  end
19
15
 
20
16
  # Activates a skill by name and returns its body.
21
- #
22
- # [context] the agent's +Riffer::Agent::Context+, exposing +#skills+
23
- # (a +Riffer::Skills::Context+).
24
- # [name] the skill name to activate.
25
- #
26
17
  #--
27
18
  #: (context: Riffer::Agent::Context?, name: String) -> Riffer::Tools::Response
28
19
  def call(context:, name:)
@@ -1,27 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Base class defining the interface for skill adapters.
5
- #
6
- # A skill adapter encapsulates the provider-specific catalog rendering for
7
- # skills — how the available-skills section appears in the system prompt.
8
- #
9
- # Subclass and override +render_catalog+. The activation tool is set at
10
- # construction time and is exposed via +#skill_activate_tool+ for use in
11
- # rendered output.
12
- #
13
- # See Riffer::Skills::MarkdownAdapter for the default implementation.
14
- # See Riffer::Skills::XmlAdapter for the Anthropic/Claude variant.
4
+ # Interface for skill adapters provider-specific rendering of the
5
+ # available-skills section in the system prompt. Subclass and override
6
+ # +render_catalog+; the activation tool is exposed via +#skill_activate_tool+
7
+ # for the rendered output.
15
8
  class Riffer::Skills::Adapter
16
9
  # The activation tool class for this adapter.
17
10
  attr_reader :skill_activate_tool #: singleton(Riffer::Tool)
18
11
 
19
- # Creates a new adapter.
20
- #
21
- # [skill_activate_tool] the activation tool class — referenced by name
22
- # in the rendered catalog so the LLM knows which
23
- # tool to call.
24
- #
25
12
  #--
26
13
  #: (skill_activate_tool: singleton(Riffer::Tool)) -> void
27
14
  def initialize(skill_activate_tool:)
@@ -29,11 +16,6 @@ class Riffer::Skills::Adapter
29
16
  end
30
17
 
31
18
  # Renders a skill catalog section for the system prompt.
32
- #
33
- # [skills] array of Frontmatter objects to render.
34
- #
35
- # Raises NotImplementedError if not implemented by subclass.
36
- #
37
19
  #--
38
20
  #: (Array[Riffer::Skills::Frontmatter]) -> String
39
21
  def render_catalog(skills)
@@ -1,38 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Base class defining the interface for skill storage backends.
5
- #
6
- # Subclass and implement +list_skills+ and +read_skill+ to provide
7
- # custom skill storage (database, S3, etc.).
8
- #
9
- # Use Riffer::Skills::Frontmatter.parse to parse raw SKILL.md content.
10
- #
11
- # See Riffer::Skills::FilesystemBackend for the built-in implementation.
4
+ # Interface for skill storage backends. Subclass and implement +list_skills+
5
+ # and +read_skill+ for custom storage (database, S3, etc.); use
6
+ # Riffer::Skills::Frontmatter.parse on raw SKILL.md content.
12
7
  class Riffer::Skills::Backend
13
8
  SKILL_FILENAME = "SKILL.md" #: String
14
9
 
15
10
  def initialize = nil
16
11
 
17
- # Returns frontmatter for all available skills.
18
- #
19
- # Called once at the start of generate/stream.
20
- #
21
- # Raises NotImplementedError if not implemented by subclass.
22
- #
12
+ # Returns frontmatter for all available skills; called once at the start of
13
+ # generate/stream.
23
14
  #--
24
15
  #: () -> Array[Riffer::Skills::Frontmatter]
25
16
  def list_skills
26
17
  raise NotImplementedError, "#{self.class} must implement #list_skills"
27
18
  end
28
19
 
29
- # Returns the full SKILL.md body (without frontmatter) for a skill.
30
- #
31
- # [name] the skill name to read.
32
- #
33
- # Raises NotImplementedError if not implemented by subclass.
34
- # Raises Riffer::ArgumentError if skill not found.
35
- #
20
+ # Returns the full SKILL.md body (without frontmatter) for a skill. Raises
21
+ # Riffer::ArgumentError if the skill is not found.
36
22
  #--
37
23
  #: (String) -> String
38
24
  def read_skill(name)
@@ -3,8 +3,6 @@
3
3
 
4
4
  # Configuration object for the skills block DSL.
5
5
  #
6
- # Used inside the +skills+ block on Riffer::Agent:
7
- #
8
6
  # skills do
9
7
  # backend Riffer::Skills::FilesystemBackend.new(".skills")
10
8
  # adapter Riffer::Skills::XmlAdapter
@@ -16,8 +14,6 @@ class Riffer::Skills::Config
16
14
  # @rbs @activate: (Array[String] | Proc)?
17
15
  # @rbs @activate_tool: singleton(Riffer::Tool)?
18
16
 
19
- # Creates a new Config with all options unset.
20
- #
21
17
  #--
22
18
  #: () -> void
23
19
  def initialize
@@ -27,11 +23,7 @@ class Riffer::Skills::Config
27
23
  @activate_tool = nil
28
24
  end
29
25
 
30
- # Gets or sets the skills backend.
31
- #
32
- # Accepts a Riffer::Skills::Backend instance, or a Proc that receives
33
- # +context+ and returns a Backend.
34
- #
26
+ # Gets or sets the skills backend (a Backend or a +context+-resolved Proc).
35
27
  #--
36
28
  #: (?(Riffer::Skills::Backend | Proc)?) -> (Riffer::Skills::Backend | Proc)?
37
29
  def backend(value = nil)
@@ -39,11 +31,8 @@ class Riffer::Skills::Config
39
31
  @backend = value
40
32
  end
41
33
 
42
- # Gets or sets a custom skill adapter class.
43
- #
44
- # Must be a subclass of Riffer::Skills::Adapter.
45
- # Defaults to the provider's preferred adapter.
46
- #
34
+ # Gets or sets a custom skill adapter class; defaults to the provider's
35
+ # preferred adapter.
47
36
  #--
48
37
  #: (?singleton(Riffer::Skills::Adapter)?) -> singleton(Riffer::Skills::Adapter)?
49
38
  def adapter(value = nil)
@@ -51,14 +40,9 @@ class Riffer::Skills::Config
51
40
  @adapter = value
52
41
  end
53
42
 
54
- # Gets or sets skill names to activate at startup.
55
- #
56
- # Activated skills have their full body included in the system prompt
57
- # without requiring a tool call.
58
- #
59
- # Accepts an array of skill name strings or a Proc that receives
60
- # +context+ and returns an array of names.
61
- #
43
+ # Gets or sets skill names to activate at startup (an array or a
44
+ # +context+-resolved Proc); activated skills' bodies are included in the
45
+ # system prompt without a tool call.
62
46
  #--
63
47
  #: (?(Array[String] | Proc)?) -> (Array[String] | Proc)?
64
48
  def activate(value = nil)
@@ -66,15 +50,10 @@ class Riffer::Skills::Config
66
50
  @activate = value
67
51
  end
68
52
 
69
- # Gets or sets the per-agent override for the skill activation tool class.
70
- #
71
- # Returns the configured override when set, or +nil+ when unset. The
72
- # global fallback to <tt>Riffer.config.skills.default_activate_tool</tt>
73
- # is applied by the agent at resolution time (see
74
- # Riffer::Agent#resolve_tools), not by this getter.
75
- #
76
- # The override must be a subclass of Riffer::Tool.
77
- #
53
+ # Gets or sets the per-agent skill activation tool override, or +nil+ when
54
+ # unset — the global fallback to <tt>Riffer.config.skills.default_activate_tool</tt>
55
+ # is applied by the agent at resolution, not here. Raises Riffer::ArgumentError
56
+ # on an invalid value.
78
57
  #--
79
58
  #: (?singleton(Riffer::Tool)?) -> singleton(Riffer::Tool)?
80
59
  def activate_tool(value = nil)