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,13 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Searches available MCP tools by name or description.
5
4
  class Riffer::Mcp::SearchTool < Riffer::Tool
6
5
  IDENTIFIER = "mcp_search"
7
6
 
8
- # Successful search response carrying the matched tool classes.
9
7
  class Result < Riffer::Tools::Response
10
- # Tool classes that matched the search query.
11
8
  attr_reader :discovered_tools #: Array[singleton(Riffer::Tool)] # @dynamic discovered_tools
12
9
 
13
10
  #--
@@ -25,7 +22,6 @@ class Riffer::Mcp::SearchTool < Riffer::Tool
25
22
  required :query, String, description: "Non-empty substring to filter tools by name or description."
26
23
  end
27
24
 
28
- # Searches progressive MCP tools and returns a +Result+ with +discovered_tools+.
29
25
  #--
30
26
  #: (context: Riffer::Agent::Context?, query: String) -> Riffer::Tools::Response
31
27
  def call(context:, query:)
@@ -1,23 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Base class for MCP-generated tools.
5
4
  class Riffer::Mcp::Tool < Riffer::Tool
6
5
  # @rbs self.@mcp_server_tool_name: String?
7
6
  # @rbs self.@input_schema: Hash[Symbol, untyped]?
8
7
 
9
- # Returns the unprefixed tool name used for +tools/call+ on the MCP server.
10
8
  #--
11
9
  #: () -> String
12
10
  def self.mcp_server_tool_name
13
11
  @mcp_server_tool_name || raise(NotImplementedError, "#{self} must set @mcp_server_tool_name")
14
12
  end
15
13
 
16
- # Returns the server-published input schema, falling back to the params DSL.
17
- # MCP schemas are server-defined, so +strict+ is not applied to them.
18
14
  #--
19
15
  #: (?strict: bool) -> Hash[Symbol, untyped]
20
16
  def self.parameters_schema(strict: false)
17
+ # MCP schemas are server-defined, so +strict+ is not applied to them.
21
18
  @input_schema || super
22
19
  end
23
20
  end
@@ -1,15 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Generates anonymous Riffer::Mcp::Tool subclasses from MCP tool definitions.
5
- # Generated tools delegate +#call+ to the MCP client and skip Riffer's param
6
- # validation — the MCP server validates inputs.
7
4
  module Riffer::Mcp::ToolFactory
8
5
  extend self
9
6
 
10
- # Builds one Riffer::Mcp::Tool subclass per tool definition, prefixing names
11
- # with the manifest name to avoid cross-server collisions (e.g.
12
- # +jira__search+); the server-side name stays on +.mcp_server_tool_name+.
13
7
  #--
14
8
  #: (String, Riffer::Mcp::Client, Array[Hash[Symbol, untyped]]) -> Array[singleton(Riffer::Mcp::Tool)]
15
9
  def build(manifest_name, client, tool_defs)
@@ -25,6 +19,8 @@ module Riffer::Mcp::ToolFactory
25
19
 
26
20
  #: (String, Riffer::Mcp::Client, Hash[Symbol, untyped]) -> singleton(Riffer::Mcp::Tool)
27
21
  def build_tool_class(manifest_name, client, descriptor)
22
+ # Prefixed to avoid cross-server collisions; the server-side name stays on
23
+ # mcp_server_tool_name.
28
24
  prefixed = "#{sanitize_name_component(manifest_name)}__#{sanitize_name_component(descriptor[:name])}"
29
25
 
30
26
  # steep does not model Class.new's class_eval semantics — the block body
@@ -37,6 +33,7 @@ module Riffer::Mcp::ToolFactory
37
33
  @description = descriptor[:description]
38
34
  @input_schema = descriptor[:input_schema]
39
35
 
36
+ # No Riffer param validation — the MCP server validates inputs.
40
37
  define_method(:call) do |context:, **kwargs|
41
38
  text(client.tools_call(self.class.mcp_server_tool_name, kwargs))
42
39
  end
data/lib/riffer/mcp.rb CHANGED
@@ -1,49 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Integration with Model Context Protocol (MCP) servers. Register servers
5
- # globally; agents opt-in by tag via the +use_mcp+ DSL. Tags are
6
- # application-defined; see +docs/MCP.md+.
7
- #
8
- # Riffer::Mcp.register(
9
- # name: "github",
10
- # tags: [:github],
11
- # endpoint: "https://mcp.github.com",
12
- # discovery_headers: -> { {"Authorization" => "Bearer #{ENV['GITHUB_TOKEN']}"} }
13
- # )
14
- #
15
- # class MyAgent < Riffer::Agent
16
- # model "openai/gpt-4o"
17
- # use_mcp :github
18
- # end
19
- #
20
4
  module Riffer::Mcp
21
5
  extend self
22
6
 
23
- # Base error for all MCP-related failures.
24
7
  class Error < Riffer::Error; end
25
8
 
26
9
  # Raised when +Riffer.config.mcp.credentials+ returns +nil+ during +tools/call+
27
- # after the server's tools were included for this run.
10
+ # after the server's tools were already included for this run.
28
11
  class CredentialsDeniedError < Error; end
29
12
 
30
- # Registers an MCP server, blocking until tool discovery completes. Raises
31
- # on discovery failure.
13
+ # Blocks until tool discovery completes.
32
14
  #--
33
15
  #: ((Hash[Symbol, untyped] | Riffer::Mcp::Manifest)) -> Riffer::Mcp::Registration
34
16
  def register(manifest_or_hash)
35
17
  Registry.register(manifest_or_hash)
36
18
  end
37
19
 
38
- # Removes a registration by name; subsequent agent runs won't see its tools.
39
20
  #--
40
21
  #: (String) -> void
41
22
  def unregister(name)
42
23
  Registry.unregister(name)
43
24
  end
44
25
 
45
- # Returns all current registrations keyed by name.
46
- #
47
26
  #--
48
27
  #: () -> Hash[String, Riffer::Mcp::Registration]
49
28
  def registrations
@@ -1,35 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents one block of model reasoning attached to an assistant message.
5
- # riffer stores and replays a part verbatim rather than interpreting it, so a
6
- # provider that requires its own thinking blocks back gets them unchanged.
4
+ # Stored and replayed verbatim, never interpreted, so a provider that requires its own thinking
5
+ # blocks back gets them unchanged.
7
6
  class Riffer::Messages::Assistant::ReasoningPart
8
7
  TYPES = %i[text summary encrypted].freeze #: Array[Symbol]
9
8
 
10
- # What the part carries: readable reasoning (+:text+), a provider-condensed
11
- # digest (+:summary+), or an opaque payload (+:encrypted+).
12
9
  attr_reader :type #: Symbol # @dynamic type
13
-
14
- # The reasoning prose, for +:text+ and +:summary+ parts.
15
10
  attr_reader :text #: String? # @dynamic text
16
-
17
- # The opaque payload, for +:encrypted+ parts.
18
11
  attr_reader :data #: String? # @dynamic data
19
-
20
- # The provider's signature over the part, when it issues one.
21
12
  attr_reader :signature #: String? # @dynamic signature
22
-
23
- # The provider's identifier for the part, when it issues one.
24
13
  attr_reader :id #: String? # @dynamic id
25
14
 
26
- # The wire format of the part, owned by the provider adapter that produced it
27
- # (e.g. <tt>"anthropic-claude-v1"</tt>). Adapters replay only the formats they
28
- # recognize, so it is never validated here.
15
+ # Owned by the producing adapter (e.g. <tt>"anthropic-claude-v1"</tt>); adapters replay only the
16
+ # formats they recognize, so it is never validated here.
29
17
  attr_reader :format #: String? # @dynamic format
30
18
 
31
- # Builds a ReasoningPart from a hash, or returns +part+ unchanged when it is
32
- # already a ReasoningPart.
33
19
  #--
34
20
  #: ((Hash[Symbol, untyped] | Riffer::Messages::Assistant::ReasoningPart)) -> Riffer::Messages::Assistant::ReasoningPart
35
21
  def self.from_hash(part)
@@ -45,7 +31,6 @@ class Riffer::Messages::Assistant::ReasoningPart
45
31
  )
46
32
  end
47
33
 
48
- # Raises Riffer::ArgumentError on a +type+ outside TYPES.
49
34
  #--
50
35
  #: (type: Symbol, ?text: String?, ?data: String?, ?signature: String?, ?id: String?, ?format: String?) -> void
51
36
  def initialize(type:, text: nil, data: nil, signature: nil, id: nil, format: nil)
@@ -62,8 +47,6 @@ class Riffer::Messages::Assistant::ReasoningPart
62
47
  @format = format
63
48
  end
64
49
 
65
- # Serializes the part to a hash, omitting the fields it doesn't carry.
66
- #
67
50
  #--
68
51
  #: () -> Hash[Symbol, untyped]
69
52
  def to_h
@@ -1,20 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents one tool invocation the model requested on an assistant message.
5
4
  class Riffer::Messages::Assistant::ToolCall
6
- # The provider's identifier for the call, echoed back on the tool result.
7
5
  attr_reader :call_id #: String # @dynamic call_id
8
6
 
9
- # The name of the tool to invoke.
10
7
  attr_reader :name #: String # @dynamic name
11
8
 
12
- # The JSON-encoded arguments, exactly as the provider emitted them.
9
+ # JSON-encoded, exactly as the provider emitted them.
13
10
  attr_reader :arguments #: String # @dynamic arguments
14
11
 
15
- # Builds a ToolCall from a hash, or returns +call+ unchanged when it is
16
- # already a ToolCall. Raises Riffer::ArgumentError when the hash is missing
17
- # a field.
18
12
  #--
19
13
  #: ((Hash[Symbol, untyped] | Riffer::Messages::Assistant::ToolCall)) -> Riffer::Messages::Assistant::ToolCall
20
14
  def self.from_hash(call)
@@ -35,8 +29,6 @@ class Riffer::Messages::Assistant::ToolCall
35
29
  @arguments = arguments
36
30
  end
37
31
 
38
- # Serializes the call to a hash.
39
- #
40
32
  #--
41
33
  #: () -> Hash[Symbol, untyped]
42
34
  def to_h
@@ -1,15 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents an assistant (LLM) message in a conversation; may include tool
5
- # calls when the LLM requests tool execution.
6
4
  class Riffer::Messages::Assistant < Riffer::Messages::Base
7
- # The reasoning part types +reasoning_text+ reads; the rest carry no prose.
5
+ # The other reasoning part types carry no prose.
8
6
  REASONING_TEXT_TYPES = %i[text summary].freeze #: Array[Symbol]
9
7
 
10
- # Builds an Assistant message from a hash, or returns +msg+ unchanged when it
11
- # is already an Assistant message. Raises Riffer::ArgumentError on an invalid
12
- # tool call, reasoning part, or +finish_reason+.
13
8
  #--
14
9
  #: ((Hash[Symbol, untyped] | Riffer::Messages::Assistant)) -> Riffer::Messages::Assistant
15
10
  def self.from_hash(msg)
@@ -27,29 +22,18 @@ class Riffer::Messages::Assistant < Riffer::Messages::Base
27
22
  )
28
23
  end
29
24
 
30
- # Array of tool calls requested by the assistant.
31
25
  attr_reader :tool_calls #: Array[Riffer::Messages::Assistant::ToolCall] # @dynamic tool_calls
32
26
 
33
- # The model's reasoning blocks for this response, in the order the provider
34
- # emitted them.
35
27
  attr_reader :reasoning #: Array[Riffer::Messages::Assistant::ReasoningPart] # @dynamic reasoning
36
28
 
37
- # Token usage data for this response.
38
29
  attr_reader :token_usage #: Riffer::Providers::TokenUsage? # @dynamic token_usage
39
30
 
40
- # Parsed structured output hash, or nil when not applicable.
41
31
  attr_reader :structured_output #: Hash[Symbol, untyped]? # @dynamic structured_output
42
32
 
43
- # Normalized reason the provider finished this response, when reported (see
44
- # <tt>Riffer::Providers::FinishReason::VALUES</tt>).
45
33
  attr_reader :finish_reason #: Symbol? # @dynamic finish_reason
46
34
 
47
- # The provider's raw finish-reason value behind +finish_reason+, when one
48
- # exists on the wire.
49
35
  attr_reader :finish_reason_raw #: String? # @dynamic finish_reason_raw
50
36
 
51
- # Raises Riffer::ArgumentError when +finish_reason+ is outside the
52
- # normalized vocabulary.
53
37
  #--
54
38
  #: (
55
39
  # String,
@@ -109,8 +93,6 @@ class Riffer::Messages::Assistant < Riffer::Messages::Base
109
93
  !@reasoning.empty?
110
94
  end
111
95
 
112
- # The readable reasoning across this message's +:text+ and +:summary+ parts,
113
- # joined by blank lines, or nil when it carries none.
114
96
  #--
115
97
  #: () -> String?
116
98
  def reasoning_text
@@ -128,8 +110,6 @@ class Riffer::Messages::Assistant < Riffer::Messages::Base
128
110
  )
129
111
  end
130
112
 
131
- # Converts the message to a hash.
132
- #
133
113
  #--
134
114
  #: () -> Hash[Symbol, untyped]
135
115
  def to_h
@@ -3,10 +3,7 @@
3
3
 
4
4
  require "securerandom"
5
5
 
6
- # Base class for all message types. Subclasses must implement +role+.
7
6
  class Riffer::Messages::Base
8
- # Builds the matching message subclass from a hash, or returns +msg+ unchanged
9
- # when it is already a message. Raises Riffer::ArgumentError on an invalid message.
10
7
  #--
11
8
  #: ((Hash[Symbol, untyped] | Riffer::Messages::Base)) -> Riffer::Messages::Base
12
9
  def self.from_hash(msg)
@@ -23,10 +20,8 @@ class Riffer::Messages::Base
23
20
  end
24
21
  end
25
22
 
26
- # The message content.
27
23
  attr_reader :content #: String # @dynamic content
28
24
 
29
- # The message id, or nil when +Riffer.config.message_id_strategy+ is +:none+.
30
25
  attr_reader :id #: String? # @dynamic id
31
26
 
32
27
  #--
@@ -36,8 +31,6 @@ class Riffer::Messages::Base
36
31
  @id = id || generate_id
37
32
  end
38
33
 
39
- # Converts the message to a hash.
40
- #
41
34
  #--
42
35
  #: () -> Hash[Symbol, untyped]
43
36
  def to_h
@@ -46,23 +39,18 @@ class Riffer::Messages::Base
46
39
  hash
47
40
  end
48
41
 
49
- # Returns the message role.
50
42
  #--
51
43
  #: () -> Symbol
52
44
  def role
53
45
  raise NotImplementedError, "Subclasses must implement #role"
54
46
  end
55
47
 
56
- # Whether this message carries pending tool calls (overridden by
57
- # +Riffer::Messages::Assistant+).
58
48
  #--
59
49
  #: () -> bool
60
50
  def has_tool_calls?
61
51
  false
62
52
  end
63
53
 
64
- # Merges another same-role message into this one. +Tool+ messages are never
65
- # merged.
66
54
  #--
67
55
  #: (untyped) -> Riffer::Messages::Base
68
56
  def +(other)
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents a system message (instructions) in a conversation.
5
4
  class Riffer::Messages::System < Riffer::Messages::Base
6
- # Builds a System message from a hash, or returns +msg+ unchanged when it is
7
- # already a System message.
8
5
  #--
9
6
  #: ((Hash[Symbol, untyped] | Riffer::Messages::System)) -> Riffer::Messages::System
10
7
  def self.from_hash(msg)
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents a tool execution result in a conversation.
5
4
  class Riffer::Messages::Tool < Riffer::Messages::Base
6
- # Builds a Tool message from a hash, or returns +msg+ unchanged when it is
7
- # already a Tool message.
8
5
  #--
9
6
  #: ((Hash[Symbol, untyped] | Riffer::Messages::Tool)) -> Riffer::Messages::Tool
10
7
  def self.from_hash(msg)
@@ -20,17 +17,9 @@ class Riffer::Messages::Tool < Riffer::Messages::Base
20
17
  )
21
18
  end
22
19
 
23
- # The ID of the tool call this result responds to.
24
20
  attr_reader :tool_call_id #: String # @dynamic tool_call_id
25
-
26
- # The name of the tool that was called.
27
21
  attr_reader :name #: String # @dynamic name
28
-
29
- # The error message if the tool execution failed.
30
22
  attr_reader :error #: String? # @dynamic error
31
-
32
- # The type of error (:unknown_tool, :validation_error, :execution_error,
33
- # :timeout_error, :unhandled_error).
34
23
  attr_reader :error_type #: Symbol? # @dynamic error_type
35
24
 
36
25
  #--
@@ -43,8 +32,6 @@ class Riffer::Messages::Tool < Riffer::Messages::Base
43
32
  @error_type = error_type
44
33
  end
45
34
 
46
- # Returns true if the tool execution resulted in an error.
47
- #
48
35
  #--
49
36
  #: () -> bool
50
37
  def error?
@@ -57,8 +44,6 @@ class Riffer::Messages::Tool < Riffer::Messages::Base
57
44
  :tool
58
45
  end
59
46
 
60
- # Converts the message to a hash.
61
- #
62
47
  #--
63
48
  #: () -> Hash[Symbol, untyped]
64
49
  def to_h
@@ -4,8 +4,6 @@
4
4
  require "base64"
5
5
  require "uri"
6
6
 
7
- # Represents a file attachment (image or document) — from a URL (+from_url+) or
8
- # raw base64 data (+new+).
9
7
  class Riffer::Messages::User::FilePart
10
8
  # @rbs @url_string: String?
11
9
  # @rbs @data: String?
@@ -28,17 +26,12 @@ class Riffer::Messages::User::FilePart
28
26
  SUPPORTED_MEDIA_TYPES = MEDIA_TYPES.values.uniq.freeze #: Array[String]
29
27
  SHA256_PATTERN = /\A[0-9a-f]{64}\z/i #: Regexp
30
28
 
31
- # The MIME type of the file.
32
29
  attr_reader :media_type #: String # @dynamic media_type
33
30
 
34
- # The filename, if available.
35
31
  attr_reader :filename #: String? # @dynamic filename
36
32
 
37
- # The expected SHA-256 of the file contents, if the caller supplied one.
38
33
  attr_reader :sha256 #: String? # @dynamic sha256
39
34
 
40
- # Raises Riffer::ArgumentError unless +data+ or +url+ is given and
41
- # +media_type+ is supported.
42
35
  #--
43
36
  #: (media_type: String, ?data: String?, ?filename: String?, ?url: String?, ?sha256: String?) -> void
44
37
  def initialize(media_type:, data: nil, filename: nil, url: nil, sha256: nil)
@@ -59,17 +52,12 @@ class Riffer::Messages::User::FilePart
59
52
  @url_string = url
60
53
  end
61
54
 
62
- # Creates a FilePart from a URL, detecting +media_type+ from the path
63
- # extension when omitted. Raises Riffer::ArgumentError if it can't be detected.
64
55
  #--
65
56
  #: (String, ?media_type: String?, ?filename: String?, ?sha256: String?) -> Riffer::Messages::User::FilePart
66
57
  def self.from_url(url, media_type: nil, filename: nil, sha256: nil)
67
58
  new(url: url, media_type: media_type || detect_media_type(url), filename: filename, sha256: sha256)
68
59
  end
69
60
 
70
- # Builds a FilePart from a +{url:, media_type:}+ or +{data:, media_type:}+ hash,
71
- # or returns +file+ unchanged when it is already a FilePart. Raises
72
- # Riffer::ArgumentError on an invalid hash.
73
61
  #--
74
62
  #: ((Hash[Symbol, untyped] | Riffer::Messages::User::FilePart)) -> Riffer::Messages::User::FilePart
75
63
  def self.from_hash(file)
@@ -90,7 +78,6 @@ class Riffer::Messages::User::FilePart
90
78
  end
91
79
  end
92
80
 
93
- # Raises Riffer::ArgumentError when the URL's extension isn't a known media type.
94
81
  #--
95
82
  #: (String) -> String
96
83
  def self.detect_media_type(url)
@@ -99,8 +86,6 @@ class Riffer::Messages::User::FilePart
99
86
  end
100
87
  private_class_method :detect_media_type
101
88
 
102
- # The base64-encoded contents - caller-supplied, or filled in by the file
103
- # resolver after a download. Nil for a URL source riffer hasn't fetched.
104
89
  #--
105
90
  #: () -> String?
106
91
  def data
@@ -124,60 +109,47 @@ class Riffer::Messages::User::FilePart
124
109
  @data_bytes = bytes
125
110
  end
126
111
 
127
- # Whether data was supplied directly, as opposed to filled in later by the
128
- # file resolver after a download
129
112
  #: () -> bool
130
113
  def inline_data?
131
114
  !@data.nil?
132
115
  end
133
116
 
134
- # Caches bytes fetched for a URL source. Deliberately absent from +to_h+:
135
- # the agent loop re-sends history on every turn, so the cache saves refreshing
136
- # the same file, while persisted history stays free of megabytes of base64
137
117
  #--
138
118
  #: (String) -> void
139
119
  def cache_downloaded_data(data)
140
120
  @downloaded_data = data
141
121
  end
142
122
 
143
- # Returns the URL if the source was a URL, nil otherwise.
144
- #
145
123
  #--
146
124
  #: () -> String?
147
125
  def url
148
126
  @url_string
149
127
  end
150
128
 
151
- # Returns true if the source was a URL.
152
- #
153
129
  #--
154
130
  #: () -> bool
155
131
  def url?
156
132
  !@url_string.nil?
157
133
  end
158
134
 
159
- # Returns true if the file is an image.
160
- #
161
135
  #--
162
136
  #: () -> bool
163
137
  def image?
164
138
  media_type.start_with?("image/")
165
139
  end
166
140
 
167
- # Returns true if the file is a document (not an image).
168
- #
169
141
  #--
170
142
  #: () -> bool
171
143
  def document?
172
144
  !image?
173
145
  end
174
146
 
175
- # Serializes the FilePart to a hash.
176
- #
177
147
  #--
178
148
  #: () -> Hash[Symbol, untyped]
179
149
  def to_h
180
150
  hash = { media_type: media_type } #: Hash[Symbol, untyped]
151
+ # Downloaded data is left out so persisted history stays free of megabytes
152
+ # of base64; the in-memory cache still spares refetching on every turn.
181
153
  hash[:data] = @data if @data
182
154
  hash[:url] = @url_string if @url_string
183
155
  hash[:filename] = filename if filename
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents a user message in a conversation.
5
4
  class Riffer::Messages::User < Riffer::Messages::Base
6
- # Builds a User message from a hash, or returns +msg+ unchanged when it is
7
- # already a User message. Raises Riffer::ArgumentError on an invalid file.
8
5
  #--
9
6
  #: ((Hash[Symbol, untyped] | Riffer::Messages::User)) -> Riffer::Messages::User
10
7
  def self.from_hash(msg)
@@ -14,7 +11,6 @@ class Riffer::Messages::User < Riffer::Messages::Base
14
11
  new(msg[:content], id: msg[:id], files: files)
15
12
  end
16
13
 
17
- # File attachments for this message.
18
14
  attr_reader :files #: Array[Riffer::Messages::User::FilePart] # @dynamic files
19
15
 
20
16
  #--
@@ -1,10 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Sentinel type for declaring boolean parameters — Ruby has no +Boolean+ class
5
- # (+true+/+false+ are +TrueClass+/+FalseClass+).
6
- #
7
- # required :verbose, Riffer::Params::Boolean
8
- #
4
+ # Ruby has no +Boolean+ class, so params declare booleans with this sentinel.
9
5
  module Riffer::Params::Boolean
10
6
  end
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # A single parameter definition, handling type validation and JSON Schema
5
- # generation.
6
4
  class Riffer::Params::Param
7
- # Maps Ruby types to JSON Schema type strings
8
5
  TYPE_MAPPINGS = {
9
6
  String => "string",
10
7
  Integer => "integer",
@@ -16,11 +13,8 @@ class Riffer::Params::Param
16
13
  Hash => "object",
17
14
  }.freeze #: Hash[Module, String]
18
15
 
19
- # Primitive types allowed for the <tt>of:</tt> keyword on Array params
20
16
  PRIMITIVE_TYPES = (TYPE_MAPPINGS.keys - [Array, Hash]).freeze #: Array[Module]
21
17
 
22
- # Maps JSON Schema type strings back to Ruby types (inverse of TYPE_MAPPINGS),
23
- # collapsing the three boolean spellings onto Riffer::Params::Boolean.
24
18
  JSON_TYPE_MAPPINGS = {
25
19
  "string" => String,
26
20
  "integer" => Integer,
@@ -30,32 +24,22 @@ class Riffer::Params::Param
30
24
  "object" => Hash,
31
25
  }.freeze #: Hash[String, Module]
32
26
 
33
- # The parameter name.
34
27
  attr_reader :name #: Symbol # @dynamic name
35
28
 
36
- # The Ruby type.
37
29
  attr_reader :type #: Module # @dynamic type
38
30
 
39
- # Whether the parameter is required.
40
31
  attr_reader :required #: bool # @dynamic required
41
32
 
42
- # The parameter description, if any.
43
33
  attr_reader :description #: String? # @dynamic description
44
34
 
45
- # Allowed values, if constrained.
46
35
  attr_reader :enum #: Array[untyped]? # @dynamic enum
47
36
 
48
- # The default value, if any.
49
37
  attr_reader :default #: untyped # @dynamic default
50
38
 
51
- # Element type for a typed array (+of:+).
52
39
  attr_reader :item_type #: Module? # @dynamic item_type
53
40
 
54
- # Nested Params for object / array-of-object types.
55
41
  attr_reader :nested_params #: Riffer::Params? # @dynamic nested_params
56
42
 
57
- # Reconstructs a Param from a single JSON Schema property. Raises
58
- # Riffer::ArgumentError on a type outside the Params-expressible subset.
59
43
  #--
60
44
  #: (Symbol, Hash[Symbol, untyped], required: bool) -> Riffer::Params::Param
61
45
  def self.from_json_schema(name, schema, required:)
@@ -88,9 +72,6 @@ class Riffer::Params::Param
88
72
  end
89
73
  private_class_method :resolve_nesting
90
74
 
91
- # Resolves a JSON Schema +type+ (or a <tt>[type, "null"]</tt> union) to its
92
- # Ruby type. Returns a Module — Riffer::Params::Boolean is a Module, not a
93
- # Class. Raises Riffer::ArgumentError on an unsupported type.
94
75
  #--
95
76
  #: (untyped) -> Module
96
77
  def self.json_type_to_ruby(type)
@@ -112,8 +93,6 @@ class Riffer::Params::Param
112
93
  @nested_params = nested_params
113
94
  end
114
95
 
115
- # Validates that a value matches the expected type.
116
- #
117
96
  #--
118
97
  #: (untyped) -> bool
119
98
  def valid_type?(value)
@@ -128,28 +107,20 @@ class Riffer::Params::Param
128
107
  end
129
108
  end
130
109
 
131
- # Returns the JSON Schema type name for this parameter.
132
- #
133
110
  #--
134
111
  #: () -> String
135
112
  def type_name
136
113
  TYPE_MAPPINGS[type] || type.to_s.downcase
137
114
  end
138
115
 
139
- # Converts this parameter to JSON Schema format. When +strict+, optional
140
- # params are made nullable (<tt>["type", "null"]</tt>) so strict providers
141
- # distinguish absent from present; optional params with an +enum+ use +anyOf+
142
- # instead, since providers like Anthropic reject
143
- # <tt>{"type": ["string", "null"], "enum": [...]}</tt>. Raises
144
- # Riffer::ArgumentError when +strict+ and a Hash param has no block or an
145
- # Array param has neither a block nor <tt>of:</tt>, since strict providers
146
- # reject objects without +properties+ and arrays without +items+.
147
116
  #--
148
117
  #: (?strict: bool) -> Hash[Symbol, untyped]
149
118
  def to_json_schema(strict: false)
150
119
  validate_strict_shape! if strict
120
+ # Nullable so strict providers can distinguish absent from present.
151
121
  nullable = strict && !required
152
122
 
123
+ # Providers like Anthropic reject a nullable type union combined with enum.
153
124
  if nullable && enum
154
125
  schema = { anyOf: [{ type: type_name, enum: enum }, { type: "null" }] } #: Hash[Symbol, untyped]
155
126
  schema[:description] = description if description
@@ -196,6 +167,7 @@ class Riffer::Params::Param
196
167
  #--
197
168
  #: () -> void
198
169
  def validate_strict_shape!
170
+ # Strict providers reject objects without properties and arrays without items.
199
171
  if type == Hash && nested_params.nil?
200
172
  raise Riffer::ArgumentError,
201
173
  "#{name}: a Hash param requires a block defining its properties under strict schemas"