riffer 0.47.2 → 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 (267) 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 +42 -0
  7. data/docs/AGENTS.md +39 -7
  8. data/docs/AGENT_LIFECYCLE.md +14 -16
  9. data/docs/CONFIGURATION.md +23 -34
  10. data/docs/EVALS.md +2 -1
  11. data/docs/MCP.md +0 -4
  12. data/docs/MESSAGES.md +85 -17
  13. data/docs/STREAM_EVENTS.md +8 -4
  14. data/docs/TOOL_ADVANCED.md +1 -3
  15. data/docs/TRACING.md +2 -2
  16. data/docs/providers/AMAZON_BEDROCK.md +32 -1
  17. data/docs/providers/CUSTOM_PROVIDERS.md +56 -4
  18. data/docs/providers/GEMINI.md +1 -1
  19. data/docs/providers/MOCK_PROVIDER.md +17 -0
  20. data/docs/providers/OPENROUTER.md +18 -1
  21. data/docs-site/build.rb +0 -6
  22. data/docs-site/check.rb +1 -5
  23. data/lib/riffer/agent/config.rb +27 -44
  24. data/lib/riffer/agent/context.rb +2 -26
  25. data/lib/riffer/agent/outcome.rb +0 -20
  26. data/lib/riffer/agent/response.rb +4 -35
  27. data/lib/riffer/agent/run.rb +32 -48
  28. data/lib/riffer/agent/serializer.rb +10 -39
  29. data/lib/riffer/agent/session/repair.rb +3 -15
  30. data/lib/riffer/agent/session.rb +26 -38
  31. data/lib/riffer/agent/structured_output/result.rb +0 -8
  32. data/lib/riffer/agent/structured_output.rb +0 -7
  33. data/lib/riffer/agent.rb +31 -127
  34. data/lib/riffer/config/amazon_bedrock.rb +30 -0
  35. data/lib/riffer/config/anthropic.rb +21 -0
  36. data/lib/riffer/config/azure_open_ai.rb +30 -0
  37. data/lib/riffer/config/evals.rb +18 -0
  38. data/lib/riffer/config/files.rb +61 -0
  39. data/lib/riffer/config/gemini.rb +21 -0
  40. data/lib/riffer/config/mcp.rb +31 -0
  41. data/lib/riffer/config/open_ai.rb +30 -0
  42. data/lib/riffer/config/open_router.rb +21 -0
  43. data/lib/riffer/config/pricing/rates.rb +36 -0
  44. data/lib/riffer/config/pricing.rb +64 -0
  45. data/lib/riffer/config/skills.rb +38 -0
  46. data/lib/riffer/config/tracing.rb +43 -0
  47. data/lib/riffer/config.rb +3 -362
  48. data/lib/riffer/evals/evaluator.rb +22 -23
  49. data/lib/riffer/evals/evaluator_runner.rb +0 -15
  50. data/lib/riffer/evals/judge.rb +9 -11
  51. data/lib/riffer/evals/result.rb +1 -11
  52. data/lib/riffer/evals/run_result.rb +0 -12
  53. data/lib/riffer/evals/scenario_result.rb +0 -19
  54. data/lib/riffer/files/downloader.rb +2 -3
  55. data/lib/riffer/files/resolver.rb +5 -10
  56. data/lib/riffer/guardrail.rb +2 -22
  57. data/lib/riffer/guardrails/modification.rb +0 -8
  58. data/lib/riffer/guardrails/result.rb +0 -17
  59. data/lib/riffer/guardrails/runner.rb +2 -11
  60. data/lib/riffer/guardrails/tripwire.rb +0 -8
  61. data/lib/riffer/guardrails.rb +0 -2
  62. data/lib/riffer/helpers/boolean.rb +0 -4
  63. data/lib/riffer/helpers/call_or_value.rb +0 -3
  64. data/lib/riffer/helpers/deep_dup.rb +37 -0
  65. data/lib/riffer/helpers/dependencies.rb +0 -4
  66. data/lib/riffer/helpers/identifier.rb +3 -12
  67. data/lib/riffer/helpers/validate.rb +42 -0
  68. data/lib/riffer/mcp/authenticated_tool.rb +4 -12
  69. data/lib/riffer/mcp/client.rb +0 -7
  70. data/lib/riffer/mcp/manifest.rb +3 -7
  71. data/lib/riffer/mcp/registration.rb +0 -10
  72. data/lib/riffer/mcp/registry.rb +0 -9
  73. data/lib/riffer/mcp/search_tool.rb +0 -4
  74. data/lib/riffer/mcp/tool.rb +1 -4
  75. data/lib/riffer/mcp/tool_factory.rb +3 -6
  76. data/lib/riffer/mcp.rb +2 -23
  77. data/lib/riffer/messages/assistant/reasoning_part.rb +73 -0
  78. data/lib/riffer/messages/assistant/tool_call.rb +55 -0
  79. data/lib/riffer/messages/assistant.rb +43 -15
  80. data/lib/riffer/messages/base.rb +5 -33
  81. data/lib/riffer/messages/system.rb +8 -1
  82. data/lib/riffer/messages/tool.rb +15 -13
  83. data/lib/riffer/messages/{file_part.rb → user/file_part.rb} +16 -40
  84. data/lib/riffer/messages/user.rb +11 -4
  85. data/lib/riffer/params/boolean.rb +1 -5
  86. data/lib/riffer/params/param.rb +15 -31
  87. data/lib/riffer/params.rb +15 -39
  88. data/lib/riffer/providers/amazon_bedrock.rb +104 -44
  89. data/lib/riffer/providers/anthropic.rb +11 -26
  90. data/lib/riffer/providers/azure_open_ai.rb +3 -8
  91. data/lib/riffer/providers/base.rb +30 -32
  92. data/lib/riffer/providers/finish_reason.rb +0 -6
  93. data/lib/riffer/providers/gemini/client.rb +4 -17
  94. data/lib/riffer/providers/gemini.rb +6 -12
  95. data/lib/riffer/providers/mock.rb +18 -27
  96. data/lib/riffer/providers/open_ai.rb +12 -21
  97. data/lib/riffer/providers/open_router.rb +109 -33
  98. data/lib/riffer/providers/repository.rb +2 -14
  99. data/lib/riffer/providers/token_usage.rb +19 -12
  100. data/lib/riffer/registrable.rb +11 -45
  101. data/lib/riffer/runner/fibers.rb +1 -6
  102. data/lib/riffer/runner/sequential.rb +0 -1
  103. data/lib/riffer/runner/threaded.rb +0 -3
  104. data/lib/riffer/runner.rb +0 -3
  105. data/lib/riffer/skills/activate_tool.rb +0 -3
  106. data/lib/riffer/skills/adapter.rb +0 -8
  107. data/lib/riffer/skills/backend.rb +2 -7
  108. data/lib/riffer/skills/config.rb +13 -17
  109. data/lib/riffer/skills/context.rb +0 -29
  110. data/lib/riffer/skills/filesystem_backend.rb +0 -7
  111. data/lib/riffer/skills/frontmatter.rb +2 -16
  112. data/lib/riffer/skills/markdown_adapter.rb +3 -6
  113. data/lib/riffer/skills/xml_adapter.rb +0 -3
  114. data/lib/riffer/stream_events/base.rb +0 -3
  115. data/lib/riffer/stream_events/finish_reason_done.rb +1 -6
  116. data/lib/riffer/stream_events/guardrail_modification.rb +0 -10
  117. data/lib/riffer/stream_events/guardrail_tripwire.rb +0 -10
  118. data/lib/riffer/stream_events/interrupt.rb +2 -12
  119. data/lib/riffer/stream_events/reasoning_delta.rb +0 -3
  120. data/lib/riffer/stream_events/reasoning_done.rb +6 -8
  121. data/lib/riffer/stream_events/skill_activation.rb +0 -3
  122. data/lib/riffer/stream_events/text_delta.rb +0 -2
  123. data/lib/riffer/stream_events/text_done.rb +0 -2
  124. data/lib/riffer/stream_events/token_usage_done.rb +0 -2
  125. data/lib/riffer/stream_events/tool_call_delta.rb +1 -5
  126. data/lib/riffer/stream_events/tool_call_done.rb +0 -5
  127. data/lib/riffer/stream_events/web_search_done.rb +0 -3
  128. data/lib/riffer/stream_events/web_search_status.rb +1 -5
  129. data/lib/riffer/testing/minitest.rb +4 -5
  130. data/lib/riffer/testing.rb +5 -38
  131. data/lib/riffer/tool.rb +2 -28
  132. data/lib/riffer/tools/response.rb +3 -32
  133. data/lib/riffer/tools/runtime/fibers.rb +0 -6
  134. data/lib/riffer/tools/runtime/inline.rb +0 -1
  135. data/lib/riffer/tools/runtime/threaded.rb +0 -6
  136. data/lib/riffer/tools/runtime.rb +8 -30
  137. data/lib/riffer/tools/toolable.rb +0 -37
  138. data/lib/riffer/tracing/capture.rb +5 -9
  139. data/lib/riffer/tracing/no_op.rb +0 -7
  140. data/lib/riffer/tracing/otel.rb +7 -16
  141. data/lib/riffer/tracing/stream_recorder.rb +0 -7
  142. data/lib/riffer/tracing.rb +4 -27
  143. data/lib/riffer/version.rb +1 -1
  144. data/lib/riffer.rb +2 -30
  145. data/sig/generated/riffer/agent/config.rbs +24 -46
  146. data/sig/generated/riffer/agent/context.rbs +2 -26
  147. data/sig/generated/riffer/agent/outcome.rbs +0 -20
  148. data/sig/generated/riffer/agent/response.rbs +4 -27
  149. data/sig/generated/riffer/agent/run.rbs +12 -31
  150. data/sig/generated/riffer/agent/serializer.rbs +2 -27
  151. data/sig/generated/riffer/agent/session/repair.rbs +2 -10
  152. data/sig/generated/riffer/agent/session.rbs +10 -36
  153. data/sig/generated/riffer/agent/structured_output/result.rbs +0 -7
  154. data/sig/generated/riffer/agent/structured_output.rbs +0 -7
  155. data/sig/generated/riffer/agent.rbs +25 -125
  156. data/sig/generated/riffer/config/amazon_bedrock.rbs +21 -0
  157. data/sig/generated/riffer/config/anthropic.rbs +15 -0
  158. data/sig/generated/riffer/config/azure_open_ai.rbs +21 -0
  159. data/sig/generated/riffer/config/evals.rbs +13 -0
  160. data/sig/generated/riffer/config/files.rbs +43 -0
  161. data/sig/generated/riffer/config/gemini.rbs +15 -0
  162. data/sig/generated/riffer/config/mcp.rbs +19 -0
  163. data/sig/generated/riffer/config/open_ai.rbs +21 -0
  164. data/sig/generated/riffer/config/open_router.rbs +15 -0
  165. data/sig/generated/riffer/config/pricing/rates.rbs +19 -0
  166. data/sig/generated/riffer/config/pricing.rbs +31 -0
  167. data/sig/generated/riffer/config/skills.rbs +19 -0
  168. data/sig/generated/riffer/config/tracing.rbs +25 -0
  169. data/sig/generated/riffer/config.rbs +2 -307
  170. data/sig/generated/riffer/evals/evaluator.rbs +12 -21
  171. data/sig/generated/riffer/evals/evaluator_runner.rbs +0 -14
  172. data/sig/generated/riffer/evals/judge.rbs +4 -8
  173. data/sig/generated/riffer/evals/result.rbs +1 -11
  174. data/sig/generated/riffer/evals/run_result.rbs +0 -12
  175. data/sig/generated/riffer/evals/scenario_result.rbs +0 -14
  176. data/sig/generated/riffer/files/resolver.rbs +6 -13
  177. data/sig/generated/riffer/guardrail.rbs +2 -22
  178. data/sig/generated/riffer/guardrails/modification.rbs +0 -6
  179. data/sig/generated/riffer/guardrails/result.rbs +0 -15
  180. data/sig/generated/riffer/guardrails/runner.rbs +0 -11
  181. data/sig/generated/riffer/guardrails/tripwire.rbs +0 -8
  182. data/sig/generated/riffer/guardrails.rbs +0 -2
  183. data/sig/generated/riffer/helpers/boolean.rbs +0 -4
  184. data/sig/generated/riffer/helpers/call_or_value.rbs +0 -3
  185. data/sig/generated/riffer/helpers/deep_dup.rbs +13 -0
  186. data/sig/generated/riffer/helpers/dependencies.rbs +0 -4
  187. data/sig/generated/riffer/helpers/identifier.rbs +0 -12
  188. data/sig/generated/riffer/helpers/validate.rbs +21 -0
  189. data/sig/generated/riffer/mcp/authenticated_tool.rbs +0 -5
  190. data/sig/generated/riffer/mcp/client.rbs +0 -7
  191. data/sig/generated/riffer/mcp/manifest.rbs +3 -7
  192. data/sig/generated/riffer/mcp/registration.rbs +0 -10
  193. data/sig/generated/riffer/mcp/registry.rbs +0 -9
  194. data/sig/generated/riffer/mcp/search_tool.rbs +0 -4
  195. data/sig/generated/riffer/mcp/tool.rbs +0 -4
  196. data/sig/generated/riffer/mcp/tool_factory.rbs +0 -6
  197. data/sig/generated/riffer/mcp.rbs +2 -22
  198. data/sig/generated/riffer/messages/assistant/reasoning_part.rbs +45 -0
  199. data/sig/generated/riffer/messages/assistant/tool_call.rbs +34 -0
  200. data/sig/generated/riffer/messages/assistant.rbs +25 -31
  201. data/sig/generated/riffer/messages/base.rbs +0 -12
  202. data/sig/generated/riffer/messages/system.rbs +4 -1
  203. data/sig/generated/riffer/messages/tool.rbs +4 -10
  204. data/sig/generated/riffer/messages/user/file_part.rbs +76 -0
  205. data/sig/generated/riffer/messages/user.rbs +7 -5
  206. data/sig/generated/riffer/params/boolean.rbs +1 -4
  207. data/sig/generated/riffer/params/param.rbs +7 -31
  208. data/sig/generated/riffer/params.rbs +8 -38
  209. data/sig/generated/riffer/providers/amazon_bedrock.rbs +33 -29
  210. data/sig/generated/riffer/providers/anthropic.rbs +2 -12
  211. data/sig/generated/riffer/providers/azure_open_ai.rbs +0 -8
  212. data/sig/generated/riffer/providers/base.rbs +18 -34
  213. data/sig/generated/riffer/providers/finish_reason.rbs +0 -6
  214. data/sig/generated/riffer/providers/gemini/client.rbs +4 -17
  215. data/sig/generated/riffer/providers/gemini.rbs +4 -10
  216. data/sig/generated/riffer/providers/mock.rbs +6 -27
  217. data/sig/generated/riffer/providers/open_ai.rbs +6 -13
  218. data/sig/generated/riffer/providers/open_router.rbs +37 -18
  219. data/sig/generated/riffer/providers/repository.rbs +2 -14
  220. data/sig/generated/riffer/providers/token_usage.rbs +9 -12
  221. data/sig/generated/riffer/registrable.rbs +0 -45
  222. data/sig/generated/riffer/runner/fibers.rbs +0 -6
  223. data/sig/generated/riffer/runner/sequential.rbs +0 -1
  224. data/sig/generated/riffer/runner/threaded.rbs +0 -3
  225. data/sig/generated/riffer/runner.rbs +0 -3
  226. data/sig/generated/riffer/skills/activate_tool.rbs +0 -3
  227. data/sig/generated/riffer/skills/adapter.rbs +0 -8
  228. data/sig/generated/riffer/skills/backend.rbs +2 -7
  229. data/sig/generated/riffer/skills/config.rbs +6 -17
  230. data/sig/generated/riffer/skills/context.rbs +0 -29
  231. data/sig/generated/riffer/skills/filesystem_backend.rbs +0 -7
  232. data/sig/generated/riffer/skills/frontmatter.rbs +2 -16
  233. data/sig/generated/riffer/skills/markdown_adapter.rbs +0 -6
  234. data/sig/generated/riffer/skills/xml_adapter.rbs +0 -3
  235. data/sig/generated/riffer/stream_events/base.rbs +0 -3
  236. data/sig/generated/riffer/stream_events/finish_reason_done.rbs +1 -6
  237. data/sig/generated/riffer/stream_events/guardrail_modification.rbs +0 -10
  238. data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +0 -10
  239. data/sig/generated/riffer/stream_events/interrupt.rbs +2 -10
  240. data/sig/generated/riffer/stream_events/reasoning_delta.rbs +0 -3
  241. data/sig/generated/riffer/stream_events/reasoning_done.rbs +4 -6
  242. data/sig/generated/riffer/stream_events/skill_activation.rbs +0 -3
  243. data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
  244. data/sig/generated/riffer/stream_events/text_done.rbs +0 -2
  245. data/sig/generated/riffer/stream_events/token_usage_done.rbs +0 -2
  246. data/sig/generated/riffer/stream_events/tool_call_delta.rbs +1 -5
  247. data/sig/generated/riffer/stream_events/tool_call_done.rbs +0 -5
  248. data/sig/generated/riffer/stream_events/web_search_done.rbs +0 -3
  249. data/sig/generated/riffer/stream_events/web_search_status.rbs +1 -5
  250. data/sig/generated/riffer/testing.rbs +0 -38
  251. data/sig/generated/riffer/tool.rbs +0 -27
  252. data/sig/generated/riffer/tools/response.rbs +3 -29
  253. data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -5
  254. data/sig/generated/riffer/tools/runtime/inline.rbs +0 -1
  255. data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -5
  256. data/sig/generated/riffer/tools/runtime.rbs +4 -26
  257. data/sig/generated/riffer/tools/toolable.rbs +0 -37
  258. data/sig/generated/riffer/tracing/capture.rbs +6 -9
  259. data/sig/generated/riffer/tracing/no_op.rbs +0 -7
  260. data/sig/generated/riffer/tracing/otel.rbs +7 -16
  261. data/sig/generated/riffer/tracing/stream_recorder.rbs +0 -2
  262. data/sig/generated/riffer/tracing.rbs +4 -23
  263. data/sig/generated/riffer.rbs +2 -29
  264. data/sig/manual/riffer/helpers/deep_dup.rbs +5 -0
  265. data/sig/manual/riffer/helpers/validate.rbs +5 -0
  266. metadata +39 -3
  267. data/sig/generated/riffer/messages/file_part.rbs +0 -101
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Thread-safe global store for MCP server registrations, keyed by manifest name.
5
4
  module Riffer::Mcp::Registry
6
5
  extend self
7
6
 
@@ -11,8 +10,6 @@ module Riffer::Mcp::Registry
11
10
  @mutex = Mutex.new
12
11
  @store = {} #: Hash[String, Riffer::Mcp::Registration]
13
12
 
14
- # Registers an MCP server and starts async tool discovery, replacing any
15
- # existing registration with the same name.
16
13
  #--
17
14
  #: ((Hash[Symbol, untyped] | Riffer::Mcp::Manifest)) -> Riffer::Mcp::Registration
18
15
  def register(manifest_or_hash)
@@ -33,8 +30,6 @@ module Riffer::Mcp::Registry
33
30
  registration
34
31
  end
35
32
 
36
- # Removes a registration by name.
37
- #
38
33
  #--
39
34
  #: ((String | Symbol)) -> void
40
35
  def unregister(name)
@@ -42,16 +37,12 @@ module Riffer::Mcp::Registry
42
37
  removed&.retire!
43
38
  end
44
39
 
45
- # Returns a frozen snapshot of all current registrations.
46
- #
47
40
  #--
48
41
  #: () -> Hash[String, Riffer::Mcp::Registration]
49
42
  def registrations
50
43
  @mutex.synchronize { @store.dup.freeze }
51
44
  end
52
45
 
53
- # Returns all registrations whose manifest tags intersect the given tags
54
- # (normalized to symbols).
55
46
  #--
56
47
  #: (Array[Symbol]) -> Array[Riffer::Mcp::Registration]
57
48
  def find_by_tags(tags)
@@ -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
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ # Stored and replayed verbatim, never interpreted, so a provider that requires its own thinking
5
+ # blocks back gets them unchanged.
6
+ class Riffer::Messages::Assistant::ReasoningPart
7
+ TYPES = %i[text summary encrypted].freeze #: Array[Symbol]
8
+
9
+ attr_reader :type #: Symbol # @dynamic type
10
+ attr_reader :text #: String? # @dynamic text
11
+ attr_reader :data #: String? # @dynamic data
12
+ attr_reader :signature #: String? # @dynamic signature
13
+ attr_reader :id #: String? # @dynamic id
14
+
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.
17
+ attr_reader :format #: String? # @dynamic format
18
+
19
+ #--
20
+ #: ((Hash[Symbol, untyped] | Riffer::Messages::Assistant::ReasoningPart)) -> Riffer::Messages::Assistant::ReasoningPart
21
+ def self.from_hash(part)
22
+ return part if part.is_a?(Riffer::Messages::Assistant::ReasoningPart)
23
+
24
+ new(
25
+ type: part[:type].to_sym,
26
+ text: part[:text],
27
+ data: part[:data],
28
+ signature: part[:signature],
29
+ id: part[:id],
30
+ format: part[:format],
31
+ )
32
+ end
33
+
34
+ #--
35
+ #: (type: Symbol, ?text: String?, ?data: String?, ?signature: String?, ?id: String?, ?format: String?) -> void
36
+ def initialize(type:, text: nil, data: nil, signature: nil, id: nil, format: nil)
37
+ unless TYPES.include?(type)
38
+ raise Riffer::ArgumentError,
39
+ "type must be one of #{TYPES.inspect}, got #{type.inspect}"
40
+ end
41
+
42
+ @type = type
43
+ @text = text
44
+ @data = data
45
+ @signature = signature
46
+ @id = id
47
+ @format = format
48
+ end
49
+
50
+ #--
51
+ #: () -> Hash[Symbol, untyped]
52
+ def to_h
53
+ { type: type, text: text, data: data, signature: signature, id: id, format: format }.compact
54
+ end
55
+
56
+ #--
57
+ #: (untyped) -> bool
58
+ def ==(other)
59
+ other.is_a?(Riffer::Messages::Assistant::ReasoningPart) && to_h == other.to_h
60
+ end
61
+
62
+ #--
63
+ #: (untyped) -> bool
64
+ def eql?(other)
65
+ self == other
66
+ end
67
+
68
+ #--
69
+ #: () -> Integer
70
+ def hash
71
+ to_h.hash
72
+ end
73
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ class Riffer::Messages::Assistant::ToolCall
5
+ attr_reader :call_id #: String # @dynamic call_id
6
+
7
+ attr_reader :name #: String # @dynamic name
8
+
9
+ # JSON-encoded, exactly as the provider emitted them.
10
+ attr_reader :arguments #: String # @dynamic arguments
11
+
12
+ #--
13
+ #: ((Hash[Symbol, untyped] | Riffer::Messages::Assistant::ToolCall)) -> Riffer::Messages::Assistant::ToolCall
14
+ def self.from_hash(call)
15
+ return call if call.is_a?(Riffer::Messages::Assistant::ToolCall)
16
+
17
+ if call.values_at(:call_id, :name, :arguments).any?(&:nil?)
18
+ raise Riffer::ArgumentError, "Tool call hash must include :call_id, :name, and :arguments"
19
+ end
20
+
21
+ new(call_id: call[:call_id], name: call[:name], arguments: call[:arguments])
22
+ end
23
+
24
+ #--
25
+ #: (call_id: String, name: String, arguments: String) -> void
26
+ def initialize(call_id:, name:, arguments:)
27
+ @call_id = call_id
28
+ @name = name
29
+ @arguments = arguments
30
+ end
31
+
32
+ #--
33
+ #: () -> Hash[Symbol, untyped]
34
+ def to_h
35
+ { call_id: call_id, name: name, arguments: arguments }
36
+ end
37
+
38
+ #--
39
+ #: (untyped) -> bool
40
+ def ==(other)
41
+ other.is_a?(Riffer::Messages::Assistant::ToolCall) && to_h == other.to_h
42
+ end
43
+
44
+ #--
45
+ #: (untyped) -> bool
46
+ def eql?(other)
47
+ self == other
48
+ end
49
+
50
+ #--
51
+ #: () -> Integer
52
+ def hash
53
+ to_h.hash
54
+ end
55
+ end
@@ -1,35 +1,45 @@
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
- ToolCall = Struct.new(:call_id, :name, :arguments)
5
+ # The other reasoning part types carry no prose.
6
+ REASONING_TEXT_TYPES = %i[text summary].freeze #: Array[Symbol]
7
+
8
+ #--
9
+ #: ((Hash[Symbol, untyped] | Riffer::Messages::Assistant)) -> Riffer::Messages::Assistant
10
+ def self.from_hash(msg)
11
+ return msg if msg.is_a?(Riffer::Messages::Assistant)
12
+
13
+ new(
14
+ msg[:content],
15
+ id: msg[:id],
16
+ tool_calls: (msg[:tool_calls] || []).map { |tc| Riffer::Messages::Assistant::ToolCall.from_hash(tc) },
17
+ reasoning: (msg[:reasoning] || []).map { |part| Riffer::Messages::Assistant::ReasoningPart.from_hash(part) },
18
+ token_usage: msg[:token_usage] && Riffer::Providers::TokenUsage.from_hash(msg[:token_usage]),
19
+ structured_output: msg[:structured_output],
20
+ finish_reason: msg[:finish_reason]&.to_sym,
21
+ finish_reason_raw: msg[:finish_reason_raw],
22
+ )
23
+ end
8
24
 
9
- # Array of tool calls requested by the assistant.
10
25
  attr_reader :tool_calls #: Array[Riffer::Messages::Assistant::ToolCall] # @dynamic tool_calls
11
26
 
12
- # Token usage data for this response.
27
+ attr_reader :reasoning #: Array[Riffer::Messages::Assistant::ReasoningPart] # @dynamic reasoning
28
+
13
29
  attr_reader :token_usage #: Riffer::Providers::TokenUsage? # @dynamic token_usage
14
30
 
15
- # Parsed structured output hash, or nil when not applicable.
16
31
  attr_reader :structured_output #: Hash[Symbol, untyped]? # @dynamic structured_output
17
32
 
18
- # Normalized reason the provider finished this response, when reported (see
19
- # <tt>Riffer::Providers::FinishReason::VALUES</tt>).
20
33
  attr_reader :finish_reason #: Symbol? # @dynamic finish_reason
21
34
 
22
- # The provider's raw finish-reason value behind +finish_reason+, when one
23
- # exists on the wire.
24
35
  attr_reader :finish_reason_raw #: String? # @dynamic finish_reason_raw
25
36
 
26
- # Raises Riffer::ArgumentError when +finish_reason+ is outside the
27
- # normalized vocabulary.
28
37
  #--
29
38
  #: (
30
39
  # String,
31
40
  # ?id: String?,
32
41
  # ?tool_calls: Array[Riffer::Messages::Assistant::ToolCall],
42
+ # ?reasoning: Array[Riffer::Messages::Assistant::ReasoningPart],
33
43
  # ?token_usage: Riffer::Providers::TokenUsage?,
34
44
  # ?structured_output: Hash[Symbol, untyped]?,
35
45
  # ?finish_reason: Symbol?,
@@ -39,6 +49,7 @@ class Riffer::Messages::Assistant < Riffer::Messages::Base
39
49
  content,
40
50
  id: nil,
41
51
  tool_calls: [],
52
+ reasoning: [],
42
53
  token_usage: nil,
43
54
  structured_output: nil,
44
55
  finish_reason: nil,
@@ -51,6 +62,7 @@ class Riffer::Messages::Assistant < Riffer::Messages::Base
51
62
 
52
63
  super(content, id: id)
53
64
  @tool_calls = tool_calls
65
+ @reasoning = reasoning
54
66
  @token_usage = token_usage
55
67
  @structured_output = structured_output
56
68
  @finish_reason = finish_reason
@@ -75,20 +87,36 @@ class Riffer::Messages::Assistant < Riffer::Messages::Base
75
87
  !@tool_calls.empty?
76
88
  end
77
89
 
90
+ #--
91
+ #: () -> bool
92
+ def reasoning?
93
+ !@reasoning.empty?
94
+ end
95
+
96
+ #--
97
+ #: () -> String?
98
+ def reasoning_text
99
+ texts = reasoning.filter_map { |part| part.text if REASONING_TEXT_TYPES.include?(part.type) }
100
+ texts.empty? ? nil : texts.join("\n\n")
101
+ end
102
+
78
103
  #--
79
104
  #: (Riffer::Messages::Assistant) -> Riffer::Messages::Assistant
80
105
  def +(other)
81
- self.class.new("#{content}\n\n#{other.content}", tool_calls: tool_calls + other.tool_calls)
106
+ self.class.new(
107
+ "#{content}\n\n#{other.content}",
108
+ tool_calls: tool_calls + other.tool_calls,
109
+ reasoning: reasoning + other.reasoning,
110
+ )
82
111
  end
83
112
 
84
- # Converts the message to a hash.
85
- #
86
113
  #--
87
114
  #: () -> Hash[Symbol, untyped]
88
115
  def to_h
89
116
  hash = { role: role, content: content } #: Hash[Symbol, untyped]
90
117
  hash[:id] = id if id
91
118
  hash[:tool_calls] = tool_calls.map(&:to_h) unless tool_calls.empty?
119
+ hash[:reasoning] = reasoning.map(&:to_h) if reasoning?
92
120
  hash[:token_usage] = token_usage.to_h if token_usage
93
121
  hash[:structured_output] = structured_output if structured_output?
94
122
  hash[:finish_reason] = finish_reason if finish_reason
@@ -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)
@@ -15,34 +12,16 @@ class Riffer::Messages::Base
15
12
  raise Riffer::ArgumentError, "Message hash must include a 'role' key" if msg[:role].nil? || msg[:role].empty?
16
13
 
17
14
  case msg[:role].to_sym
18
- when :user
19
- files = (msg[:files] || []).map { |f| Riffer::Messages::FilePart.from_hash(f) }
20
- Riffer::Messages::User.new(msg[:content], id: msg[:id], files: files)
21
- when :assistant
22
- tool_calls = (msg[:tool_calls] || []).map do |tc|
23
- tc.is_a?(Riffer::Messages::Assistant::ToolCall) ? tc : Riffer::Messages::Assistant::ToolCall.new(**tc)
24
- end
25
- Riffer::Messages::Assistant.new(
26
- msg[:content],
27
- id: msg[:id],
28
- tool_calls: tool_calls,
29
- structured_output: msg[:structured_output],
30
- finish_reason: msg[:finish_reason]&.to_sym,
31
- finish_reason_raw: msg[:finish_reason_raw],
32
- )
33
- when :system
34
- Riffer::Messages::System.new(msg[:content], id: msg[:id])
35
- when :tool
36
- Riffer::Messages::Tool.new(msg[:content], id: msg[:id], tool_call_id: msg[:tool_call_id], name: msg[:name])
37
- else
38
- raise Riffer::ArgumentError, "Unknown message role: #{msg[:role]}"
15
+ when :user then Riffer::Messages::User.from_hash(msg)
16
+ when :assistant then Riffer::Messages::Assistant.from_hash(msg)
17
+ when :system then Riffer::Messages::System.from_hash(msg)
18
+ when :tool then Riffer::Messages::Tool.from_hash(msg)
19
+ else raise Riffer::ArgumentError, "Unknown message role: #{msg[:role]}"
39
20
  end
40
21
  end
41
22
 
42
- # The message content.
43
23
  attr_reader :content #: String # @dynamic content
44
24
 
45
- # The message id, or nil when +Riffer.config.message_id_strategy+ is +:none+.
46
25
  attr_reader :id #: String? # @dynamic id
47
26
 
48
27
  #--
@@ -52,8 +31,6 @@ class Riffer::Messages::Base
52
31
  @id = id || generate_id
53
32
  end
54
33
 
55
- # Converts the message to a hash.
56
- #
57
34
  #--
58
35
  #: () -> Hash[Symbol, untyped]
59
36
  def to_h
@@ -62,23 +39,18 @@ class Riffer::Messages::Base
62
39
  hash
63
40
  end
64
41
 
65
- # Returns the message role.
66
42
  #--
67
43
  #: () -> Symbol
68
44
  def role
69
45
  raise NotImplementedError, "Subclasses must implement #role"
70
46
  end
71
47
 
72
- # Whether this message carries pending tool calls (overridden by
73
- # +Riffer::Messages::Assistant+).
74
48
  #--
75
49
  #: () -> bool
76
50
  def has_tool_calls?
77
51
  false
78
52
  end
79
53
 
80
- # Merges another same-role message into this one. +Tool+ messages are never
81
- # merged.
82
54
  #--
83
55
  #: (untyped) -> Riffer::Messages::Base
84
56
  def +(other)
@@ -1,8 +1,15 @@
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
5
+ #--
6
+ #: ((Hash[Symbol, untyped] | Riffer::Messages::System)) -> Riffer::Messages::System
7
+ def self.from_hash(msg)
8
+ return msg if msg.is_a?(Riffer::Messages::System)
9
+
10
+ new(msg[:content], id: msg[:id])
11
+ end
12
+
6
13
  #--
7
14
  #: () -> Symbol
8
15
  def role
@@ -1,19 +1,25 @@
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
- # The ID of the tool call this result responds to.
7
- attr_reader :tool_call_id #: String # @dynamic tool_call_id
5
+ #--
6
+ #: ((Hash[Symbol, untyped] | Riffer::Messages::Tool)) -> Riffer::Messages::Tool
7
+ def self.from_hash(msg)
8
+ return msg if msg.is_a?(Riffer::Messages::Tool)
9
+
10
+ new(
11
+ msg[:content],
12
+ id: msg[:id],
13
+ tool_call_id: msg[:tool_call_id],
14
+ name: msg[:name],
15
+ error: msg[:error],
16
+ error_type: msg[:error_type]&.to_sym,
17
+ )
18
+ end
8
19
 
9
- # The name of the tool that was called.
20
+ attr_reader :tool_call_id #: String # @dynamic tool_call_id
10
21
  attr_reader :name #: String # @dynamic name
11
-
12
- # The error message if the tool execution failed.
13
22
  attr_reader :error #: String? # @dynamic error
14
-
15
- # The type of error (:unknown_tool, :validation_error, :execution_error,
16
- # :timeout_error, :unhandled_error).
17
23
  attr_reader :error_type #: Symbol? # @dynamic error_type
18
24
 
19
25
  #--
@@ -26,8 +32,6 @@ class Riffer::Messages::Tool < Riffer::Messages::Base
26
32
  @error_type = error_type
27
33
  end
28
34
 
29
- # Returns true if the tool execution resulted in an error.
30
- #
31
35
  #--
32
36
  #: () -> bool
33
37
  def error?
@@ -40,8 +44,6 @@ class Riffer::Messages::Tool < Riffer::Messages::Base
40
44
  :tool
41
45
  end
42
46
 
43
- # Converts the message to a hash.
44
- #
45
47
  #--
46
48
  #: () -> Hash[Symbol, untyped]
47
49
  def to_h