riffer 0.48.0 → 0.49.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 (258) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/rules/comments.md +2 -4
  3. data/.claude/rules/rbs-inline.md +2 -12
  4. data/.release-please-manifest.json +1 -1
  5. data/.rubocop.yml +5 -0
  6. data/CHANGELOG.md +19 -0
  7. data/docs/AGENTS.md +3 -5
  8. data/docs/AGENT_LIFECYCLE.md +13 -16
  9. data/docs/CONFIGURATION.md +22 -33
  10. data/docs/TOOL_ADVANCED.md +1 -3
  11. data/docs/TRACING.md +1 -1
  12. data/docs/providers/AMAZON_BEDROCK.md +31 -0
  13. data/docs/providers/OPENROUTER.md +18 -1
  14. data/docs-site/build.rb +0 -6
  15. data/docs-site/check.rb +1 -5
  16. data/lib/riffer/agent/config.rb +4 -45
  17. data/lib/riffer/agent/context.rb +2 -26
  18. data/lib/riffer/agent/outcome.rb +0 -20
  19. data/lib/riffer/agent/response.rb +0 -38
  20. data/lib/riffer/agent/run.rb +11 -43
  21. data/lib/riffer/agent/serializer.rb +10 -39
  22. data/lib/riffer/agent/session/repair.rb +3 -15
  23. data/lib/riffer/agent/session.rb +25 -38
  24. data/lib/riffer/agent/structured_output/result.rb +0 -8
  25. data/lib/riffer/agent/structured_output.rb +0 -7
  26. data/lib/riffer/agent.rb +10 -130
  27. data/lib/riffer/config/amazon_bedrock.rb +30 -0
  28. data/lib/riffer/config/anthropic.rb +21 -0
  29. data/lib/riffer/config/azure_open_ai.rb +30 -0
  30. data/lib/riffer/config/evals.rb +18 -0
  31. data/lib/riffer/config/files.rb +61 -0
  32. data/lib/riffer/config/gemini.rb +21 -0
  33. data/lib/riffer/config/mcp.rb +31 -0
  34. data/lib/riffer/config/open_ai.rb +30 -0
  35. data/lib/riffer/config/open_router.rb +21 -0
  36. data/lib/riffer/config/pricing/rates.rb +36 -0
  37. data/lib/riffer/config/pricing.rb +64 -0
  38. data/lib/riffer/config/skills.rb +38 -0
  39. data/lib/riffer/config/tracing.rb +43 -0
  40. data/lib/riffer/config.rb +3 -362
  41. data/lib/riffer/evals/evaluator.rb +1 -29
  42. data/lib/riffer/evals/evaluator_runner.rb +0 -15
  43. data/lib/riffer/evals/judge.rb +0 -8
  44. data/lib/riffer/evals/result.rb +1 -11
  45. data/lib/riffer/evals/run_result.rb +0 -12
  46. data/lib/riffer/evals/scenario_result.rb +0 -19
  47. data/lib/riffer/files/downloader.rb +2 -3
  48. data/lib/riffer/files/resolver.rb +2 -7
  49. data/lib/riffer/guardrail.rb +2 -22
  50. data/lib/riffer/guardrails/modification.rb +0 -8
  51. data/lib/riffer/guardrails/result.rb +0 -17
  52. data/lib/riffer/guardrails/runner.rb +2 -11
  53. data/lib/riffer/guardrails/tripwire.rb +0 -8
  54. data/lib/riffer/guardrails.rb +0 -2
  55. data/lib/riffer/helpers/boolean.rb +0 -4
  56. data/lib/riffer/helpers/call_or_value.rb +0 -3
  57. data/lib/riffer/helpers/deep_dup.rb +3 -8
  58. data/lib/riffer/helpers/dependencies.rb +0 -4
  59. data/lib/riffer/helpers/identifier.rb +3 -12
  60. data/lib/riffer/helpers/validate.rb +42 -0
  61. data/lib/riffer/mcp/authenticated_tool.rb +4 -12
  62. data/lib/riffer/mcp/client.rb +0 -7
  63. data/lib/riffer/mcp/manifest.rb +3 -7
  64. data/lib/riffer/mcp/registration.rb +0 -10
  65. data/lib/riffer/mcp/registry.rb +0 -9
  66. data/lib/riffer/mcp/search_tool.rb +0 -4
  67. data/lib/riffer/mcp/tool.rb +1 -4
  68. data/lib/riffer/mcp/tool_factory.rb +3 -6
  69. data/lib/riffer/mcp.rb +2 -23
  70. data/lib/riffer/messages/assistant/reasoning_part.rb +4 -21
  71. data/lib/riffer/messages/assistant/tool_call.rb +1 -9
  72. data/lib/riffer/messages/assistant.rb +1 -21
  73. data/lib/riffer/messages/base.rb +0 -12
  74. data/lib/riffer/messages/system.rb +0 -3
  75. data/lib/riffer/messages/tool.rb +0 -15
  76. data/lib/riffer/messages/user/file_part.rb +2 -30
  77. data/lib/riffer/messages/user.rb +0 -4
  78. data/lib/riffer/params/boolean.rb +1 -5
  79. data/lib/riffer/params/param.rb +3 -31
  80. data/lib/riffer/params.rb +8 -41
  81. data/lib/riffer/providers/amazon_bedrock.rb +101 -42
  82. data/lib/riffer/providers/anthropic.rb +9 -24
  83. data/lib/riffer/providers/azure_open_ai.rb +3 -8
  84. data/lib/riffer/providers/base.rb +7 -28
  85. data/lib/riffer/providers/finish_reason.rb +0 -6
  86. data/lib/riffer/providers/gemini/client.rb +4 -17
  87. data/lib/riffer/providers/gemini.rb +4 -10
  88. data/lib/riffer/providers/mock.rb +1 -26
  89. data/lib/riffer/providers/open_ai.rb +7 -17
  90. data/lib/riffer/providers/open_router.rb +105 -30
  91. data/lib/riffer/providers/repository.rb +2 -14
  92. data/lib/riffer/providers/token_usage.rb +5 -14
  93. data/lib/riffer/registrable.rb +11 -45
  94. data/lib/riffer/runner/fibers.rb +1 -6
  95. data/lib/riffer/runner/sequential.rb +0 -1
  96. data/lib/riffer/runner/threaded.rb +0 -3
  97. data/lib/riffer/runner.rb +0 -3
  98. data/lib/riffer/skills/activate_tool.rb +0 -3
  99. data/lib/riffer/skills/adapter.rb +0 -8
  100. data/lib/riffer/skills/backend.rb +2 -7
  101. data/lib/riffer/skills/config.rb +4 -20
  102. data/lib/riffer/skills/context.rb +0 -29
  103. data/lib/riffer/skills/filesystem_backend.rb +0 -7
  104. data/lib/riffer/skills/frontmatter.rb +2 -16
  105. data/lib/riffer/skills/markdown_adapter.rb +3 -6
  106. data/lib/riffer/skills/xml_adapter.rb +0 -3
  107. data/lib/riffer/stream_events/base.rb +0 -3
  108. data/lib/riffer/stream_events/finish_reason_done.rb +1 -6
  109. data/lib/riffer/stream_events/guardrail_modification.rb +0 -10
  110. data/lib/riffer/stream_events/guardrail_tripwire.rb +0 -10
  111. data/lib/riffer/stream_events/interrupt.rb +2 -12
  112. data/lib/riffer/stream_events/reasoning_delta.rb +0 -3
  113. data/lib/riffer/stream_events/reasoning_done.rb +1 -5
  114. data/lib/riffer/stream_events/skill_activation.rb +0 -3
  115. data/lib/riffer/stream_events/text_delta.rb +0 -2
  116. data/lib/riffer/stream_events/text_done.rb +0 -2
  117. data/lib/riffer/stream_events/token_usage_done.rb +0 -2
  118. data/lib/riffer/stream_events/tool_call_delta.rb +1 -5
  119. data/lib/riffer/stream_events/tool_call_done.rb +0 -5
  120. data/lib/riffer/stream_events/web_search_done.rb +0 -3
  121. data/lib/riffer/stream_events/web_search_status.rb +1 -5
  122. data/lib/riffer/testing/minitest.rb +4 -5
  123. data/lib/riffer/testing.rb +5 -38
  124. data/lib/riffer/tool.rb +2 -28
  125. data/lib/riffer/tools/response.rb +3 -32
  126. data/lib/riffer/tools/runtime/fibers.rb +0 -6
  127. data/lib/riffer/tools/runtime/inline.rb +0 -1
  128. data/lib/riffer/tools/runtime/threaded.rb +0 -6
  129. data/lib/riffer/tools/runtime.rb +5 -26
  130. data/lib/riffer/tools/toolable.rb +0 -37
  131. data/lib/riffer/tracing/capture.rb +3 -5
  132. data/lib/riffer/tracing/no_op.rb +0 -7
  133. data/lib/riffer/tracing/otel.rb +7 -16
  134. data/lib/riffer/tracing/stream_recorder.rb +0 -7
  135. data/lib/riffer/tracing.rb +4 -27
  136. data/lib/riffer/version.rb +1 -1
  137. data/lib/riffer.rb +2 -30
  138. data/sig/generated/riffer/agent/config.rbs +12 -48
  139. data/sig/generated/riffer/agent/context.rbs +2 -26
  140. data/sig/generated/riffer/agent/outcome.rbs +0 -20
  141. data/sig/generated/riffer/agent/response.rbs +1 -29
  142. data/sig/generated/riffer/agent/run.rbs +1 -27
  143. data/sig/generated/riffer/agent/serializer.rbs +2 -27
  144. data/sig/generated/riffer/agent/session/repair.rbs +2 -10
  145. data/sig/generated/riffer/agent/session.rbs +10 -36
  146. data/sig/generated/riffer/agent/structured_output/result.rbs +0 -7
  147. data/sig/generated/riffer/agent/structured_output.rbs +0 -7
  148. data/sig/generated/riffer/agent.rbs +5 -121
  149. data/sig/generated/riffer/config/amazon_bedrock.rbs +21 -0
  150. data/sig/generated/riffer/config/anthropic.rbs +15 -0
  151. data/sig/generated/riffer/config/azure_open_ai.rbs +21 -0
  152. data/sig/generated/riffer/config/evals.rbs +13 -0
  153. data/sig/generated/riffer/config/files.rbs +43 -0
  154. data/sig/generated/riffer/config/gemini.rbs +15 -0
  155. data/sig/generated/riffer/config/mcp.rbs +19 -0
  156. data/sig/generated/riffer/config/open_ai.rbs +21 -0
  157. data/sig/generated/riffer/config/open_router.rbs +15 -0
  158. data/sig/generated/riffer/config/pricing/rates.rbs +19 -0
  159. data/sig/generated/riffer/config/pricing.rbs +31 -0
  160. data/sig/generated/riffer/config/skills.rbs +19 -0
  161. data/sig/generated/riffer/config/tracing.rbs +25 -0
  162. data/sig/generated/riffer/config.rbs +2 -307
  163. data/sig/generated/riffer/evals/evaluator.rbs +0 -28
  164. data/sig/generated/riffer/evals/evaluator_runner.rbs +0 -14
  165. data/sig/generated/riffer/evals/judge.rbs +0 -7
  166. data/sig/generated/riffer/evals/result.rbs +1 -11
  167. data/sig/generated/riffer/evals/run_result.rbs +0 -12
  168. data/sig/generated/riffer/evals/scenario_result.rbs +0 -14
  169. data/sig/generated/riffer/files/resolver.rbs +0 -7
  170. data/sig/generated/riffer/guardrail.rbs +2 -22
  171. data/sig/generated/riffer/guardrails/modification.rbs +0 -6
  172. data/sig/generated/riffer/guardrails/result.rbs +0 -15
  173. data/sig/generated/riffer/guardrails/runner.rbs +0 -11
  174. data/sig/generated/riffer/guardrails/tripwire.rbs +0 -8
  175. data/sig/generated/riffer/guardrails.rbs +0 -2
  176. data/sig/generated/riffer/helpers/boolean.rbs +0 -4
  177. data/sig/generated/riffer/helpers/call_or_value.rbs +0 -3
  178. data/sig/generated/riffer/helpers/deep_dup.rbs +0 -8
  179. data/sig/generated/riffer/helpers/dependencies.rbs +0 -4
  180. data/sig/generated/riffer/helpers/identifier.rbs +0 -12
  181. data/sig/generated/riffer/helpers/validate.rbs +21 -0
  182. data/sig/generated/riffer/mcp/authenticated_tool.rbs +0 -5
  183. data/sig/generated/riffer/mcp/client.rbs +0 -7
  184. data/sig/generated/riffer/mcp/manifest.rbs +3 -7
  185. data/sig/generated/riffer/mcp/registration.rbs +0 -10
  186. data/sig/generated/riffer/mcp/registry.rbs +0 -9
  187. data/sig/generated/riffer/mcp/search_tool.rbs +0 -4
  188. data/sig/generated/riffer/mcp/tool.rbs +0 -4
  189. data/sig/generated/riffer/mcp/tool_factory.rbs +0 -6
  190. data/sig/generated/riffer/mcp.rbs +2 -22
  191. data/sig/generated/riffer/messages/assistant/reasoning_part.rbs +4 -17
  192. data/sig/generated/riffer/messages/assistant/tool_call.rbs +1 -9
  193. data/sig/generated/riffer/messages/assistant.rbs +10 -30
  194. data/sig/generated/riffer/messages/base.rbs +0 -12
  195. data/sig/generated/riffer/messages/system.rbs +0 -3
  196. data/sig/generated/riffer/messages/tool.rbs +0 -12
  197. data/sig/generated/riffer/messages/user/file_part.rbs +0 -30
  198. data/sig/generated/riffer/messages/user.rbs +0 -4
  199. data/sig/generated/riffer/params/boolean.rbs +1 -4
  200. data/sig/generated/riffer/params/param.rbs +0 -31
  201. data/sig/generated/riffer/params.rbs +4 -40
  202. data/sig/generated/riffer/providers/amazon_bedrock.rbs +29 -25
  203. data/sig/generated/riffer/providers/anthropic.rbs +0 -10
  204. data/sig/generated/riffer/providers/azure_open_ai.rbs +0 -8
  205. data/sig/generated/riffer/providers/base.rbs +0 -28
  206. data/sig/generated/riffer/providers/finish_reason.rbs +0 -6
  207. data/sig/generated/riffer/providers/gemini/client.rbs +4 -17
  208. data/sig/generated/riffer/providers/gemini.rbs +0 -6
  209. data/sig/generated/riffer/providers/mock.rbs +0 -26
  210. data/sig/generated/riffer/providers/open_ai.rbs +2 -9
  211. data/sig/generated/riffer/providers/open_router.rbs +33 -14
  212. data/sig/generated/riffer/providers/repository.rbs +2 -14
  213. data/sig/generated/riffer/providers/token_usage.rbs +5 -14
  214. data/sig/generated/riffer/registrable.rbs +0 -45
  215. data/sig/generated/riffer/runner/fibers.rbs +0 -6
  216. data/sig/generated/riffer/runner/sequential.rbs +0 -1
  217. data/sig/generated/riffer/runner/threaded.rbs +0 -3
  218. data/sig/generated/riffer/runner.rbs +0 -3
  219. data/sig/generated/riffer/skills/activate_tool.rbs +0 -3
  220. data/sig/generated/riffer/skills/adapter.rbs +0 -8
  221. data/sig/generated/riffer/skills/backend.rbs +2 -7
  222. data/sig/generated/riffer/skills/config.rbs +0 -20
  223. data/sig/generated/riffer/skills/context.rbs +0 -29
  224. data/sig/generated/riffer/skills/filesystem_backend.rbs +0 -7
  225. data/sig/generated/riffer/skills/frontmatter.rbs +2 -16
  226. data/sig/generated/riffer/skills/markdown_adapter.rbs +0 -6
  227. data/sig/generated/riffer/skills/xml_adapter.rbs +0 -3
  228. data/sig/generated/riffer/stream_events/base.rbs +0 -3
  229. data/sig/generated/riffer/stream_events/finish_reason_done.rbs +1 -6
  230. data/sig/generated/riffer/stream_events/guardrail_modification.rbs +0 -10
  231. data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +0 -10
  232. data/sig/generated/riffer/stream_events/interrupt.rbs +2 -10
  233. data/sig/generated/riffer/stream_events/reasoning_delta.rbs +0 -3
  234. data/sig/generated/riffer/stream_events/reasoning_done.rbs +1 -5
  235. data/sig/generated/riffer/stream_events/skill_activation.rbs +0 -3
  236. data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
  237. data/sig/generated/riffer/stream_events/text_done.rbs +0 -2
  238. data/sig/generated/riffer/stream_events/token_usage_done.rbs +0 -2
  239. data/sig/generated/riffer/stream_events/tool_call_delta.rbs +1 -5
  240. data/sig/generated/riffer/stream_events/tool_call_done.rbs +0 -5
  241. data/sig/generated/riffer/stream_events/web_search_done.rbs +0 -3
  242. data/sig/generated/riffer/stream_events/web_search_status.rbs +1 -5
  243. data/sig/generated/riffer/testing.rbs +0 -38
  244. data/sig/generated/riffer/tool.rbs +0 -27
  245. data/sig/generated/riffer/tools/response.rbs +3 -29
  246. data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -5
  247. data/sig/generated/riffer/tools/runtime/inline.rbs +0 -1
  248. data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -5
  249. data/sig/generated/riffer/tools/runtime.rbs +2 -24
  250. data/sig/generated/riffer/tools/toolable.rbs +0 -37
  251. data/sig/generated/riffer/tracing/capture.rbs +2 -5
  252. data/sig/generated/riffer/tracing/no_op.rbs +0 -7
  253. data/sig/generated/riffer/tracing/otel.rbs +7 -16
  254. data/sig/generated/riffer/tracing/stream_recorder.rbs +0 -2
  255. data/sig/generated/riffer/tracing.rbs +4 -23
  256. data/sig/generated/riffer.rbs +2 -29
  257. data/sig/manual/riffer/helpers/validate.rbs +5 -0
  258. metadata +30 -1
@@ -1,31 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents an interrupt during streaming, fired when a callback throws
5
- # +:riffer_interrupt+.
6
4
  class Riffer::StreamEvents::Interrupt < Riffer::StreamEvents::Base
7
- # The reason provided with the interrupt, if any.
8
5
  attr_reader :reason #: (String | Symbol)? # @dynamic reason
9
6
 
10
- # Call ids of tool_use blocks riffer filled with placeholder results when the
11
- # interrupt fired (only when history healing is on).
12
- attr_reader :healed_tool_call_ids #: Array[String] # @dynamic healed_tool_call_ids
13
-
14
7
  #--
15
- #: (?reason: (String | Symbol)?, ?healed_tool_call_ids: Array[String]) -> void
16
- def initialize(reason: nil, healed_tool_call_ids: [])
8
+ #: (?reason: (String | Symbol)?) -> void
9
+ def initialize(reason: nil)
17
10
  super(role: :system)
18
11
  @reason = reason
19
- @healed_tool_call_ids = healed_tool_call_ids
20
12
  end
21
13
 
22
- # Converts the event to a hash.
23
14
  #--
24
15
  #: () -> Hash[Symbol, untyped]
25
16
  def to_h
26
17
  h = { role: @role, interrupt: true } #: Hash[Symbol, untyped]
27
18
  h[:reason] = @reason if @reason
28
- h[:healed_tool_call_ids] = @healed_tool_call_ids unless @healed_tool_call_ids.empty?
29
19
  h
30
20
  end
31
21
  end
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents an incremental reasoning chunk during streaming; only emitted by
5
- # providers that support reasoning (e.g. OpenAI with the reasoning option).
6
4
  class Riffer::StreamEvents::ReasoningDelta < Riffer::StreamEvents::Base
7
- # The incremental reasoning content.
8
5
  attr_reader :content #: String # @dynamic content
9
6
 
10
7
  #--
@@ -1,12 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents one completed reasoning block during streaming; only emitted by
5
- # providers that support reasoning (e.g. OpenAI with the reasoning option).
6
4
  class Riffer::StreamEvents::ReasoningDone < Riffer::StreamEvents::Base
7
- # The reasoning block, which the agent loop accumulates onto the assistant
8
- # message. Its +text+ is the content the preceding ReasoningDelta events
9
- # added up to.
5
+ # +text+ is the concatenation of the preceding ReasoningDelta events.
10
6
  attr_reader :part #: Riffer::Messages::Assistant::ReasoningPart # @dynamic part
11
7
 
12
8
  #--
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Emitted when a skill is activated during streaming, via the +on_activate+
5
- # callback when the LLM calls the activation tool.
6
4
  class Riffer::StreamEvents::SkillActivation < Riffer::StreamEvents::Base
7
- # The activated skill name.
8
5
  attr_reader :name #: String # @dynamic name
9
6
 
10
7
  #--
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents an incremental text chunk during streaming.
5
4
  class Riffer::StreamEvents::TextDelta < Riffer::StreamEvents::Base
6
- # The incremental text content.
7
5
  attr_reader :content #: String # @dynamic content
8
6
 
9
7
  #--
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents completed text generation during streaming.
5
4
  class Riffer::StreamEvents::TextDone < Riffer::StreamEvents::Base
6
- # The complete text content.
7
5
  attr_reader :content #: String # @dynamic content
8
6
 
9
7
  #--
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Final token usage for the response, emitted when the LLM finishes.
5
4
  class Riffer::StreamEvents::TokenUsageDone < Riffer::StreamEvents::Base
6
- # The token usage data for this response.
7
5
  attr_reader :token_usage #: Riffer::Providers::TokenUsage # @dynamic token_usage
8
6
 
9
7
  #--
@@ -1,16 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents an incremental tool call chunk (partial argument data) during
5
- # streaming.
6
4
  class Riffer::StreamEvents::ToolCallDelta < Riffer::StreamEvents::Base
7
- # The tool call item identifier.
8
5
  attr_reader :item_id #: String # @dynamic item_id
9
6
 
10
- # The tool name (may only be present in first delta).
7
+ # Providers may send the name only on the first delta.
11
8
  attr_reader :name #: String? # @dynamic name
12
9
 
13
- # The incremental arguments JSON fragment.
14
10
  attr_reader :arguments_delta #: String # @dynamic arguments_delta
15
11
 
16
12
  #--
@@ -1,18 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents a completed tool call during streaming.
5
4
  class Riffer::StreamEvents::ToolCallDone < Riffer::StreamEvents::Base
6
- # The tool call item identifier.
7
5
  attr_reader :item_id #: String # @dynamic item_id
8
6
 
9
- # The call identifier for response matching.
10
7
  attr_reader :call_id #: String # @dynamic call_id
11
8
 
12
- # The tool name.
13
9
  attr_reader :name #: String # @dynamic name
14
10
 
15
- # The complete arguments JSON string.
16
11
  attr_reader :arguments #: String # @dynamic arguments
17
12
 
18
13
  #--
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # The result of a completed server-side web search during streaming.
5
4
  class Riffer::StreamEvents::WebSearchDone < Riffer::StreamEvents::Base
6
- # The search query used.
7
5
  attr_reader :query #: String # @dynamic query
8
6
 
9
- # The search result sources with title and url.
10
7
  attr_reader :sources #: Array[Hash[Symbol, String?]] # @dynamic sources
11
8
 
12
9
  #--
@@ -1,16 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # A web search status notification, emitted as a server-side web search
5
- # progresses.
6
4
  class Riffer::StreamEvents::WebSearchStatus < Riffer::StreamEvents::Base
7
- # The web search status ("in_progress", "searching", "completed", "open_page").
5
+ # One of "in_progress", "searching", "open_page", or "completed".
8
6
  attr_reader :status #: String # @dynamic status
9
7
 
10
- # The URL being fetched (present for "open_page" status).
11
8
  attr_reader :url #: String? # @dynamic url
12
9
 
13
- # The search query (present when available during status changes).
14
10
  attr_reader :query #: String? # @dynamic query
15
11
 
16
12
  #--
@@ -1,13 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rbs-inline stays disabled here: the generated signature would name Minitest,
4
- # which a shipped signature must never reference. The hand-written stub lives in
5
- # sig/_private/riffer/testing/minitest.rbs.
3
+ # rbs-inline stays disabled: a shipped signature must never name Minitest. The
4
+ # stub lives in sig/_private/riffer/testing/minitest.rbs.
6
5
 
7
6
  require "riffer"
8
7
 
9
- # Wiring only — minitest is never a riffer dependency; a consumer requires this
10
- # file from a test_helper that has already loaded the framework.
8
+ # Minitest is never a riffer dependency; the consumer's test_helper loads it
9
+ # before requiring this file.
11
10
  module Riffer::Testing::MinitestCleanup
12
11
  # Minitest reserves +after_teardown+ for library extensions; +teardown+
13
12
  # belongs to the test author.
@@ -1,14 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Builds throwaway agents and tools a test suite can resolve by identifier or
5
- # constant name, and removes them again. Require <tt>riffer/testing/rspec</tt>
6
- # or <tt>riffer/testing/minitest</tt> to get +stub_agent+/+stub_tool+ in every
7
- # example plus per-test cleanup; otherwise include this module and call +reset!+
8
- # from your own teardown, or call the methods on the module directly.
9
- #
10
- # Tracking is not synchronized — stub from a single-threaded test, before
11
- # concurrent lookups begin.
12
4
  module Riffer::Testing
13
5
  extend self
14
6
 
@@ -17,43 +9,18 @@ module Riffer::Testing
17
9
  CONST_NAME_PATTERN = /\A[A-Z][A-Za-z0-9_]*\z/ #: Regexp
18
10
  private_constant :CONST_NAME_PATTERN
19
11
 
20
- # Builds an agent class, evaluates the optional body in it, and makes it
21
- # resolvable until the next +reset!+. A +name+ assigns a top-level constant
22
- # and derives the identifier; the body may set +identifier+ like any other
23
- # agent config.
24
- #
25
- # agent = stub_agent("SupportAgent") { model "mock/gpt-5-mini" }
26
- #
27
- # Raises Riffer::ArgumentError when the constant is already defined or the
28
- # stub ends up with no identifier, and Riffer::DuplicateIdentifierError when
29
- # another agent already holds the identifier.
30
- #
31
12
  #--
32
13
  #: (?(String | Symbol)?, ?base: singleton(Riffer::Agent)) ?{ () [self: singleton(Riffer::Agent)] -> void } -> singleton(Riffer::Agent)
33
14
  def stub_agent(name = nil, base: Riffer::Agent, &body)
34
15
  build_stub(name, base: base, &body) #: singleton(Riffer::Agent)
35
16
  end
36
17
 
37
- # Builds a tool class, evaluates the optional body in it, and makes it
38
- # resolvable until the next +reset!+. A +name+ assigns a top-level constant
39
- # and derives the identifier; the body may set +identifier+ like any other
40
- # tool config.
41
- #
42
- # tool = stub_tool("KbSearch") { def call(context:, **) = text("stubbed") }
43
- #
44
- # Raises Riffer::ArgumentError when the constant is already defined or the
45
- # stub ends up with no identifier, and Riffer::DuplicateIdentifierError when
46
- # another tool already holds the identifier.
47
- #
48
18
  #--
49
19
  #: (?(String | Symbol)?, ?base: singleton(Riffer::Tool)) ?{ () [self: singleton(Riffer::Tool)] -> void } -> singleton(Riffer::Tool)
50
20
  def stub_tool(name = nil, base: Riffer::Tool, &body)
51
21
  build_stub(name, base: base, &body) #: singleton(Riffer::Tool)
52
22
  end
53
23
 
54
- # Removes every stub built since the last reset — its registration and any
55
- # constant it created — newest first, and forgets them. A no-op when nothing
56
- # has been stubbed.
57
24
  #--
58
25
  #: () -> void
59
26
  def reset!
@@ -66,12 +33,12 @@ module Riffer::Testing
66
33
  tracked.clear
67
34
  end
68
35
 
69
- # The stub classes awaiting cleanup. Lives on the module rather than the
70
- # caller so an including test case and a direct
71
- # <tt>Riffer::Testing.stub_*</tt> call share one list.
72
36
  #--
73
37
  #: () -> Array[[Class, String?]]
74
38
  def self.registrations # :nodoc:
39
+ # Lives on the module rather than the caller so an including test case and
40
+ # a direct <tt>Riffer::Testing.stub_*</tt> call share one list. Not
41
+ # synchronized: stub from a single-threaded test.
75
42
  @registrations ||= []
76
43
  end
77
44
 
@@ -118,11 +85,11 @@ module Riffer::Testing
118
85
  const_name
119
86
  end
120
87
 
121
- # A test may have removed or replaced the constant itself, so never clobber
122
- # one that no longer points at the stub.
123
88
  #--
124
89
  #: (String, Class) -> void
125
90
  def remove_stub_const(const_name, stub)
91
+ # A test may have removed or replaced the constant itself, so never clobber
92
+ # one that no longer points at the stub.
126
93
  return unless Object.const_defined?(const_name, false) && Object.const_get(const_name, false).equal?(stub)
127
94
 
128
95
  Object.send(:remove_const, const_name)
data/lib/riffer/tool.rb CHANGED
@@ -3,64 +3,36 @@
3
3
 
4
4
  require "timeout"
5
5
 
6
- # Base class for all tools in the Riffer framework. Subclasses must implement
7
- # the +call+ method.
8
- #
9
- # class WeatherLookupTool < Riffer::Tool
10
- # description "Provides current weather information for a specified city."
11
- #
12
- # params do
13
- # required :city, String, description: "The city to look up"
14
- # optional :units, String, default: "celsius"
15
- # end
16
- #
17
- # def call(context:, city:, units: nil)
18
- # # Implementation
19
- # end
20
- # end
21
- #
22
6
  class Riffer::Tool
23
7
  extend Riffer::Tools::Toolable
24
8
  extend Riffer::Registrable
25
9
 
26
10
  kind :tool
27
11
 
28
- # Executes the tool with the given arguments.
29
12
  #--
30
13
  #: (context: Riffer::Agent::Context?, **untyped) -> Riffer::Tools::Response
31
14
  def call(context:, **kwargs)
32
15
  raise NotImplementedError, "#{self.class} must implement #call"
33
16
  end
34
17
 
35
- # Creates a text response.
36
- #
37
18
  #--
38
19
  #: (untyped) -> Riffer::Tools::Response
39
20
  def text(result)
40
21
  Riffer::Tools::Response.text(result)
41
22
  end
42
23
 
43
- # Creates a JSON response.
44
- #
45
24
  #--
46
25
  #: (untyped) -> Riffer::Tools::Response
47
26
  def json(result)
48
27
  Riffer::Tools::Response.json(result)
49
28
  end
50
29
 
51
- # Creates an error response.
52
- #
53
30
  #--
54
31
  #: (String, ?type: Symbol) -> Riffer::Tools::Response
55
32
  def error(message, type: :execution_error)
56
33
  Riffer::Tools::Response.error(message, type: type)
57
34
  end
58
35
 
59
- # Executes the tool with validation and timeout, folding every +StandardError+
60
- # into an error Response. Anything outside +StandardError+ — an unimplemented
61
- # +#call+ above all — still propagates, because a broken tool is a broken
62
- # deploy rather than a bad request.
63
- #
64
36
  #--
65
37
  #: (context: Riffer::Agent::Context?, **untyped) -> Riffer::Tools::Response
66
38
  def call_with_validation(context:, **kwargs)
@@ -88,6 +60,8 @@ class Riffer::Tool
88
60
  )
89
61
  rescue Riffer::ToolExecutionError => e
90
62
  Riffer::Tools::Response.error(e.message, type: :execution_error)
63
+ # Anything outside StandardError — an unimplemented #call above all — still
64
+ # propagates: a broken tool is a broken deploy, not a bad request.
91
65
  rescue StandardError => e
92
66
  Riffer::Tools::Response.error(
93
67
  "Error executing tool: #{e.class}: #{e.message}",
@@ -3,39 +3,20 @@
3
3
 
4
4
  require "json"
5
5
 
6
- # Represents the result of a tool execution; every tool's +call+ must return one.
7
- #
8
- # class MyTool < Riffer::Tool
9
- # def call(context:, **kwargs)
10
- # result = perform_operation
11
- # Riffer::Tools::Response.success(result)
12
- # rescue MyError => e
13
- # Riffer::Tools::Response.error(e.message)
14
- # end
15
- # end
16
- #
6
+ # Every tool's +call+ must return one.
17
7
  class Riffer::Tools::Response
18
8
  # @rbs @success: bool
19
9
 
20
10
  VALID_FORMATS = %i[text json].freeze #: Array[Symbol]
21
11
 
22
- # The response content.
23
12
  attr_reader :content #: String # @dynamic content
24
-
25
- # The error message, or +nil+ on success.
26
13
  attr_reader :error_message #: String? # @dynamic error_message
27
-
28
- # The error type, or +nil+ on success.
29
14
  attr_reader :error_type #: Symbol? # @dynamic error_type
30
15
 
31
- # The exception an unhandled failure was folded from, or +nil+. Kept out of
32
- # every serialized form so it never reaches an LLM or a message payload.
16
+ # Kept out of every serialized form so it never reaches an LLM or a message
17
+ # payload.
33
18
  attr_reader :exception #: Exception? # @dynamic exception
34
19
 
35
- # Creates a success response.
36
- #
37
- # Raises Riffer::ArgumentError if format is invalid.
38
- #
39
20
  #--
40
21
  #: (untyped, ?format: Symbol) -> Riffer::Tools::Response
41
22
  def self.success(result, format: :text)
@@ -47,42 +28,32 @@ class Riffer::Tools::Response
47
28
  new(content: content, success: true)
48
29
  end
49
30
 
50
- # Creates a success response with text format.
51
- #
52
31
  #--
53
32
  #: (untyped) -> Riffer::Tools::Response
54
33
  def self.text(result)
55
34
  success(result, format: :text)
56
35
  end
57
36
 
58
- # Creates a success response with JSON format.
59
- #
60
37
  #--
61
38
  #: (untyped) -> Riffer::Tools::Response
62
39
  def self.json(result)
63
40
  success(result, format: :json)
64
41
  end
65
42
 
66
- # Creates an error response.
67
- #
68
43
  #--
69
44
  #: (String, ?type: Symbol, ?exception: Exception?) -> Riffer::Tools::Response
70
45
  def self.error(message, type: :execution_error, exception: nil)
71
46
  new(content: message, success: false, error_message: message, error_type: type, exception: exception)
72
47
  end
73
48
 
74
- # Returns true if the tool execution succeeded.
75
49
  #--
76
50
  #: () -> bool
77
51
  def success? = @success
78
52
 
79
- # Returns true if the tool execution failed.
80
53
  #--
81
54
  #: () -> bool
82
55
  def error? = !@success
83
56
 
84
- # Returns a hash representation of the response.
85
- #
86
57
  #--
87
58
  #: () -> Hash[Symbol, untyped]
88
59
  def to_h
@@ -1,12 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Executes tool calls concurrently using fibers via the +async+ gem.
5
- #
6
- # class MyAgent < Riffer::Agent
7
- # tool_runtime Riffer::Tools::Runtime::Fibers
8
- # end
9
- #
10
4
  class Riffer::Tools::Runtime::Fibers < Riffer::Tools::Runtime
11
5
  #--
12
6
  #: (?max_concurrency: Integer?) -> void
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Executes tool calls sequentially in the current thread — the default runtime.
5
4
  class Riffer::Tools::Runtime::Inline < Riffer::Tools::Runtime
6
5
  #--
7
6
  #: () -> void
@@ -1,12 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Executes tool calls concurrently using threads.
5
- #
6
- # class MyAgent < Riffer::Agent
7
- # tool_runtime Riffer::Tools::Runtime::Threaded
8
- # end
9
- #
10
4
  class Riffer::Tools::Runtime::Threaded < Riffer::Tools::Runtime
11
5
  DEFAULT_MAX_CONCURRENCY = 5 #: Integer
12
6
 
@@ -3,9 +3,6 @@
3
3
 
4
4
  require "json"
5
5
 
6
- # Handles tool call execution for an agent, composing with a Riffer::Runner for
7
- # concurrency. Subclass and override +dispatch_tool_call+ to customize dispatch
8
- # (e.g. HTTP, gRPC).
9
6
  class Riffer::Tools::Runtime
10
7
  # @rbs @runner: Riffer::Runner
11
8
 
@@ -20,17 +17,15 @@ class Riffer::Tools::Runtime
20
17
  @runner = runner
21
18
  end
22
19
 
23
- # Executes a batch of tool calls, returning <tt>[tool_call, response]</tt> pairs.
24
20
  #--
25
21
  #: (Array[Riffer::Messages::Assistant::ToolCall], tools: Array[singleton(Riffer::Tool)], context: Riffer::Agent::Context?, ?assistant_message: Riffer::Messages::Assistant?, ?tags: Hash[String, String]) -> Array[[Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response]]
26
22
  def execute(tool_calls, tools:, context:, assistant_message: nil, tags: {})
27
23
  # Each Runner worker runs in its own thread/fiber, where the OTEL context
28
24
  # starts empty — capture here so the execute_tool span parents correctly.
29
- # tags are an ordinary local captured in the block, so they reach the
30
- # worker's span without re-propagation.
31
25
  trace_context = Riffer::Tracing.current_context
32
26
  @runner.map(tool_calls, context: context) do |tool_call|
33
27
  Riffer::Tracing.with_context(trace_context) do
28
+ # Outside around_tool_call so host enrichment spans nest beneath it.
34
29
  instrument_tool_call(tool_call, tags) do
35
30
  around_tool_call(tool_call, context: context, assistant_message: assistant_message) do
36
31
  dispatch_tool_call(tool_call, tools: tools, context: context, assistant_message: assistant_message)
@@ -40,20 +35,7 @@ class Riffer::Tools::Runtime
40
35
  end
41
36
  end
42
37
 
43
- # Hook wrapping each tool call; override in subclasses to instrument or
44
- # customize. Must +yield+ to continue.
45
- #
46
- # class InstrumentedRuntime < Riffer::Tools::Runtime::Inline
47
- # private
48
- #
49
- # def around_tool_call(tool_call, context:, assistant_message: nil)
50
- # start = Time.now
51
- # result = yield
52
- # Rails.logger.info("Tool #{tool_call.name} took #{Time.now - start}s")
53
- # result
54
- # end
55
- # end
56
- #
38
+ # Overrides must +yield+ and return its Response.
57
39
  #--
58
40
  #: (Riffer::Messages::Assistant::ToolCall, context: Riffer::Agent::Context?, ?assistant_message: Riffer::Messages::Assistant?) { () -> Riffer::Tools::Response } -> Riffer::Tools::Response
59
41
  def around_tool_call(_tool_call, context:, assistant_message: nil)
@@ -73,6 +55,7 @@ class Riffer::Tools::Runtime
73
55
  [tool_call, result] #: [Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response]
74
56
  end
75
57
 
58
+ # Subclasses override this to dispatch elsewhere (e.g. HTTP, gRPC).
76
59
  #--
77
60
  #: (Riffer::Messages::Assistant::ToolCall, tools: Array[singleton(Riffer::Tool)], context: Riffer::Agent::Context?, ?assistant_message: Riffer::Messages::Assistant?) -> Riffer::Tools::Response
78
61
  def dispatch_tool_call(tool_call, tools:, context:, assistant_message: nil)
@@ -108,7 +91,6 @@ class Riffer::Tools::Runtime
108
91
  JSON.parse(arguments, symbolize_names: true)
109
92
  end
110
93
 
111
- # Emitted outside +around_tool_call+ so host enrichment spans nest beneath it.
112
94
  #--
113
95
  #: [R] (Riffer::Messages::Assistant::ToolCall, ?Hash[String, String]) { ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span)) -> R } -> R
114
96
  def in_tool_span(tool_call, tags = {})
@@ -137,23 +119,20 @@ class Riffer::Tools::Runtime
137
119
  }.merge(tag_attributes(tags))
138
120
  end
139
121
 
140
- # Maps normalized tags to their namespaced span attribute form. An empty map
141
- # yields an empty hash, so merging it is a no-op.
142
122
  #--
143
123
  #: (Hash[String, String]) -> Hash[String, String]
144
124
  def tag_attributes(tags)
145
125
  tags.transform_keys { |key| "riffer.tag.#{key}" }
146
126
  end
147
127
 
148
- # A deliberate error Response is a handled outcome, so its status stays unset.
149
- # An error status is reserved for a Response carrying the exception it was
150
- # folded from — the tool failed for a reason nobody anticipated.
151
128
  #--
152
129
  #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Tools::Response) -> void
153
130
  def record_tool_outcome(span, result)
154
131
  error_type = result.error_type
155
132
  span.set_attribute("error.type", error_type.to_s) if error_type
156
133
 
134
+ # A deliberate error Response is a handled outcome, so only one carrying the
135
+ # exception it was folded from gets an error status.
157
136
  exception = result.exception
158
137
  if exception
159
138
  span.record_exception(exception)