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,24 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents the result of evaluating a single scenario.
5
4
  class Riffer::Evals::ScenarioResult
6
- # The input that was evaluated.
7
5
  attr_reader :input #: String # @dynamic input
8
-
9
- # The agent output for this scenario.
10
6
  attr_reader :output #: String # @dynamic output
11
-
12
- # The ground truth used during evaluation.
13
7
  attr_reader :ground_truth #: String? # @dynamic ground_truth
14
-
15
- # Individual evaluation results.
16
8
  attr_reader :results #: Array[Riffer::Evals::Result] # @dynamic results
17
-
18
- # The full message history from the agent conversation.
19
9
  attr_reader :messages #: Array[Riffer::Messages::Base] # @dynamic messages
20
-
21
- # Token usage the agent under test spent generating this scenario's output.
22
10
  attr_reader :token_usage #: Riffer::Providers::TokenUsage? # @dynamic token_usage
23
11
 
24
12
  #--
@@ -32,8 +20,6 @@ class Riffer::Evals::ScenarioResult
32
20
  @token_usage = token_usage
33
21
  end
34
22
 
35
- # Returns scores keyed by evaluator class.
36
- #
37
23
  #--
38
24
  #: () -> Hash[singleton(Riffer::Evals::Evaluator), Float]
39
25
  def scores
@@ -43,17 +29,12 @@ class Riffer::Evals::ScenarioResult
43
29
  end
44
30
  end
45
31
 
46
- # Returns the summed token usage across this scenario's LLM-as-judge
47
- # evaluators, or nil when none reported usage.
48
- #
49
32
  #--
50
33
  #: () -> Riffer::Providers::TokenUsage?
51
34
  def evaluator_token_usage
52
35
  results.filter_map(&:token_usage).reduce(:+)
53
36
  end
54
37
 
55
- # Returns a hash representation of the scenario result.
56
- #
57
38
  #--
58
39
  #: () -> Hash[Symbol, untyped]
59
40
  def to_h
@@ -33,9 +33,8 @@ class Riffer::Files::Downloader
33
33
  http.open_timeout = timeout
34
34
  http.read_timeout = timeout
35
35
 
36
- # request_get without a block reads (and discards our chance to cap) the
37
- # whole body before returning; the cap/read has to happen inside the
38
- # block it yields to, where the body hasn't been consumed yet.
36
+ # request_get without a block reads the whole body before returning, so the
37
+ # size cap has to run inside the block, before the body is consumed.
39
38
  redirect_location = nil #: String?
40
39
  content = nil #: String?
41
40
 
@@ -14,10 +14,6 @@ class Riffer::Files::Resolver
14
14
  @config = Riffer.config.files
15
15
  end
16
16
 
17
- # Resolves every file in every User message in place - downloading,
18
- # verifying, and caching as the provider's capability and each file's
19
- # sha256 require. Raises Riffer::FileError on any file that can't be
20
- # resolved
21
17
  #: (Array[Riffer::Messages::Base]) -> void
22
18
  def resolve!(messages)
23
19
  files = messages.flat_map do |message|
@@ -51,6 +47,8 @@ class Riffer::Files::Resolver
51
47
 
52
48
  case delivery
53
49
  when :url
50
+ # The request still sends the URL, never these bytes, so caching them
51
+ # would hold memory nothing reads and let later turns skip re-verifying.
54
52
  download!(file, cache: false) if file.sha256
55
53
  when :base64
56
54
  file.data ? verify_inline!(file) : download!(file, cache: :base64)
@@ -69,9 +67,6 @@ class Riffer::Files::Resolver
69
67
  verify_bytes!(file.data_bytes, file.sha256)
70
68
  end
71
69
 
72
- # +cache:+ is false for a :url-delivery provider verifying a sha256 — the
73
- # request still sends the URL, never the downloaded bytes, so caching them
74
- # would hold memory nothing reads and let later turns skip re-verifying.
75
70
  #: (Riffer::Messages::User::FilePart, cache: (false | Symbol)) -> void
76
71
  def download!(file, cache:)
77
72
  raise Riffer::FileDownloadsDisabledError, "File attachments are disabled" unless @config.allow_downloads
@@ -1,38 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Base class for guardrails that process input and output in the agent pipeline.
5
- #
6
- # class MyGuardrail < Riffer::Guardrail
7
- # def process_input(messages, context:)
8
- # # Return pass(messages), transform(modified_messages), or block(reason)
9
- # pass(messages)
10
- # end
11
- #
12
- # def process_output(response, messages:, context:)
13
- # # Return pass(response), transform(modified_response), or block(reason)
14
- # pass(response)
15
- # end
16
- # end
17
4
  class Riffer::Guardrail
18
- # Processes input messages before they're sent to the LLM; override in
19
- # subclasses.
20
5
  #--
21
6
  #: (Array[Riffer::Messages::Base], context: untyped) -> Riffer::Guardrails::Result
22
7
  def process_input(messages, context:)
23
8
  pass(messages)
24
9
  end
25
10
 
26
- # Processes the output response after it's received from the LLM; override in
27
- # subclasses.
28
11
  #--
29
12
  #: (Riffer::Messages::Assistant, messages: Array[Riffer::Messages::Base], context: untyped) -> Riffer::Guardrails::Result
30
13
  def process_output(response, messages:, context:)
31
14
  pass(response)
32
15
  end
33
16
 
34
- # Returns the guardrail's identifier, used as the tracing span suffix and the
35
- # <tt>riffer.guardrail.name</tt> attribute; override to label the span.
17
+ # Used as the tracing span suffix and the <tt>riffer.guardrail.name</tt>
18
+ # attribute.
36
19
  #--
37
20
  #: () -> String
38
21
  def name
@@ -41,21 +24,18 @@ class Riffer::Guardrail
41
24
 
42
25
  protected
43
26
 
44
- # Creates a pass result that continues with unchanged data.
45
27
  #--
46
28
  #: (untyped) -> Riffer::Guardrails::Result
47
29
  def pass(data)
48
30
  Riffer::Guardrails::Result.pass(data)
49
31
  end
50
32
 
51
- # Creates a transform result that continues with transformed data.
52
33
  #--
53
34
  #: (untyped) -> Riffer::Guardrails::Result
54
35
  def transform(data)
55
36
  Riffer::Guardrails::Result.transform(data)
56
37
  end
57
38
 
58
- # Creates a block result that halts execution.
59
39
  #--
60
40
  #: (String, ?metadata: Hash[Symbol, untyped]?) -> Riffer::Guardrails::Result
61
41
  def block(reason, metadata: nil)
@@ -1,15 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Records a guardrail transformation event.
5
4
  class Riffer::Guardrails::Modification
6
- # The guardrail class that transformed data.
7
5
  attr_reader :guardrail #: singleton(Riffer::Guardrail) # @dynamic guardrail
8
-
9
- # The phase when the transformation occurred (:before or :after).
10
6
  attr_reader :phase #: Symbol # @dynamic phase
11
-
12
- # The indices of messages that were changed.
13
7
  attr_reader :message_indices #: Array[Integer] # @dynamic message_indices
14
8
 
15
9
  #--
@@ -20,8 +14,6 @@ class Riffer::Guardrails::Modification
20
14
  @message_indices = message_indices
21
15
  end
22
16
 
23
- # Converts the modification to a hash.
24
- #
25
17
  #--
26
18
  #: () -> Hash[Symbol, untyped]
27
19
  def to_h
@@ -1,36 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents the result of a guardrail execution: +pass+ (continue unchanged),
5
- # +transform+ (continue with changed data), or +block+ (halt with a reason).
6
4
  class Riffer::Guardrails::Result
7
5
  TYPES = %i[pass transform block].freeze #: Array[Symbol]
8
6
 
9
- # The result type (:pass, :transform, or :block).
10
7
  attr_reader :type #: Symbol # @dynamic type
11
-
12
- # The data (for pass/transform) or reason (for block).
13
8
  attr_reader :data #: untyped # @dynamic data
14
-
15
- # Optional metadata for block results.
16
9
  attr_reader :metadata #: Hash[Symbol, untyped]? # @dynamic metadata
17
10
 
18
11
  class << self
19
- # Creates a pass result that continues with unchanged data.
20
12
  #--
21
13
  #: (untyped) -> Riffer::Guardrails::Result
22
14
  def pass(data)
23
15
  new(:pass, data)
24
16
  end
25
17
 
26
- # Creates a transform result that continues with transformed data.
27
18
  #--
28
19
  #: (untyped) -> Riffer::Guardrails::Result
29
20
  def transform(data)
30
21
  new(:transform, data)
31
22
  end
32
23
 
33
- # Creates a block result that halts execution.
34
24
  #--
35
25
  #: (String, ?metadata: Hash[Symbol, untyped]?) -> Riffer::Guardrails::Result
36
26
  def block(reason, metadata: nil)
@@ -38,7 +28,6 @@ class Riffer::Guardrails::Result
38
28
  end
39
29
  end
40
30
 
41
- # Raises Riffer::ArgumentError if +type+ is not :pass, :transform, or :block.
42
31
  #--
43
32
  #: (Symbol, untyped, ?metadata: Hash[Symbol, untyped]?) -> void
44
33
  def initialize(type, data, metadata: nil)
@@ -49,24 +38,18 @@ class Riffer::Guardrails::Result
49
38
  @metadata = metadata
50
39
  end
51
40
 
52
- # Returns true if this is a pass result.
53
- #
54
41
  #--
55
42
  #: () -> bool
56
43
  def pass?
57
44
  type == :pass
58
45
  end
59
46
 
60
- # Returns true if this is a transform result.
61
- #
62
47
  #--
63
48
  #: () -> bool
64
49
  def transform?
65
50
  type == :transform
66
51
  end
67
52
 
68
- # Returns true if this is a block result.
69
- #
70
53
  #--
71
54
  #: () -> bool
72
55
  def block?
@@ -1,19 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Executes guardrails sequentially, passing each one's output to the next; if
5
- # any blocks, execution stops and a tripwire is returned.
6
4
  class Riffer::Guardrails::Runner
7
- # The guardrail configs to execute.
8
5
  attr_reader :guardrail_configs #: Array[Hash[Symbol, untyped]] # @dynamic guardrail_configs
9
6
 
10
- # The execution phase (:before or :after).
11
7
  attr_reader :phase #: Symbol # @dynamic phase
12
8
 
13
- # The context passed to guardrails.
14
9
  attr_reader :context #: untyped # @dynamic context
15
10
 
16
- # The normalized per-call tags, stamped as +riffer.tag.*+ on guardrail spans.
17
11
  attr_reader :tags #: Hash[String, String] # @dynamic tags
18
12
 
19
13
  #--
@@ -25,9 +19,6 @@ class Riffer::Guardrails::Runner
25
19
  @tags = tags
26
20
  end
27
21
 
28
- # Runs the guardrails sequentially. For the +:before+ phase +data+ is the
29
- # messages array; for +:after+ it's the response (and +messages+ must be
30
- # provided).
31
22
  #--
32
23
  #: (untyped, ?messages: Array[Riffer::Messages::Base]?) -> [untyped, Riffer::Guardrails::Tripwire?, Array[Riffer::Guardrails::Modification]]
33
24
  def run(data, messages: nil)
@@ -124,12 +115,12 @@ class Riffer::Guardrails::Runner
124
115
  }.merge(tags.transform_keys { |key| "riffer.tag.#{key}" })
125
116
  end
126
117
 
127
- # A block is a handled outcome, so its span status stays unset — an error
128
- # span status is reserved for a raised exception.
129
118
  #--
130
119
  #: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Guardrails::Result) -> void
131
120
  def record_guardrail_outcome(span, result)
132
121
  span.set_attribute("riffer.guardrail.action", result.type.to_s)
122
+ # A block is a handled outcome, so span status stays unset — error status
123
+ # is reserved for a raised exception.
133
124
  span.set_attribute("riffer.tripwire.reason", result.data) if result.block?
134
125
  end
135
126
  end
@@ -1,23 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Captures information about a blocked guardrail execution.
5
4
  class Riffer::Guardrails::Tripwire
6
5
  PHASES = Riffer::Guardrails::PHASES #: Array[Symbol]
7
6
 
8
- # The reason for blocking.
9
7
  attr_reader :reason #: String # @dynamic reason
10
8
 
11
- # The guardrail class that triggered the block.
12
9
  attr_reader :guardrail #: singleton(Riffer::Guardrail) # @dynamic guardrail
13
10
 
14
- # The phase when the block occurred (:before or :after).
15
11
  attr_reader :phase #: Symbol # @dynamic phase
16
12
 
17
- # Optional metadata about the block.
18
13
  attr_reader :metadata #: Hash[Symbol, untyped]? # @dynamic metadata
19
14
 
20
- # Raises Riffer::ArgumentError if +phase+ is invalid.
21
15
  #--
22
16
  #: (reason: String, guardrail: singleton(Riffer::Guardrail), phase: Symbol, ?metadata: Hash[Symbol, untyped]?) -> void
23
17
  def initialize(reason:, guardrail:, phase:, metadata: nil)
@@ -29,8 +23,6 @@ class Riffer::Guardrails::Tripwire
29
23
  @metadata = metadata
30
24
  end
31
25
 
32
- # Converts the tripwire to a hash.
33
- #
34
26
  #--
35
27
  #: () -> Hash[Symbol, untyped]
36
28
  def to_h
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Namespace for guardrail components that pre-process input and post-process
5
- # output in the agent pipeline.
6
4
  module Riffer::Guardrails
7
5
  PHASES = %i[before after].freeze #: Array[Symbol]
8
6
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Coercion for boolean-ish configuration values.
5
4
  module Riffer::Helpers::Boolean
6
5
  extend self
7
6
 
8
- # Coerces +value+ to a boolean so an env-var +"false"+ (truthy in Ruby)
9
- # doesn't silently read as +true+. Raises Riffer::ArgumentError on an
10
- # unrecognized value, naming +attribute+ in the message.
11
7
  #--
12
8
  #: (untyped, attribute: String) -> bool
13
9
  def coerce(value, attribute:)
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Resolves the Proc-or-value idiom.
5
4
  module Riffer::Helpers::CallOrValue
6
5
  extend self
7
6
 
8
- # Calls +thing+ when it's a Proc (passing +context+ if its arity is non-zero),
9
- # returns it unchanged otherwise, or +default+ when +nil+.
10
7
  #--
11
8
  #: (untyped, ?context: untyped, ?default: untyped) -> untyped
12
9
  def resolve(thing, context: nil, default: nil)
@@ -1,20 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Copying for the nested Hashes and Arrays a configuration object holds.
5
4
  module Riffer::Helpers::DeepDup
6
5
  extend self
7
6
 
8
- # Returns +value+ with every Hash and Array rebuilt, so a copy shares no
9
- # collection with its source.
10
- #
11
- # Anything else is returned as-is, which a Class or a Proc needs: +Class#dup+
12
- # answers a new anonymous class. One source collection maps to one copy, so
13
- # references shared within +value+ stay shared in the result — an +:around+
14
- # guardrail registered under two phases is still one registration afterwards.
15
7
  #--
16
8
  #: (untyped) -> untyped
17
9
  def call(value)
10
+ # References shared within +value+ stay shared in the copy — an +:around+
11
+ # guardrail registered under two phases is still one registration.
18
12
  seen = {} #: Hash[untyped, untyped]
19
13
 
20
14
  rebuild(value, seen.compare_by_identity)
@@ -36,6 +30,7 @@ module Riffer::Helpers::DeepDup
36
30
  copy = seen[value] = []
37
31
  value.each { |entry| copy << rebuild(entry, seen) }
38
32
  copy
33
+ # Class#dup answers a new anonymous class, so non-collections stay as-is.
39
34
  else value
40
35
  end
41
36
  end
@@ -1,15 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Lazy-loads gem dependencies — used by providers to load required gems only
5
- # when needed.
6
4
  module Riffer::Helpers::Dependencies
7
5
  extend self
8
6
 
9
- # Raised when a required gem cannot be loaded.
10
7
  class LoadError < ::LoadError; end
11
8
 
12
- # Requires a gem by name; raises LoadError if it isn't installed.
13
9
  #--
14
10
  #: (String) -> true
15
11
  def depends_on(gem_name)
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Helper module for deriving snake_case identifiers from class names.
5
4
  module Riffer::Helpers::Identifier
6
5
  extend self
7
6
 
8
- # Derives a snake_case identifier from a class name string.
9
- #
10
7
  #--
11
8
  #: (String?) -> String
12
9
  def derive(class_name)
@@ -18,11 +15,6 @@ module Riffer::Helpers::Identifier
18
15
  downcase
19
16
  end
20
17
 
21
- # Derives and memoizes the identifier for a class or module. Anonymous
22
- # classes return "" without caching, so a class named later still derives its
23
- # real identifier — a guard that must travel with the cache, so callers never
24
- # memoize their own.
25
- #
26
18
  #--
27
19
  #: (Module) -> String
28
20
  def for(klass)
@@ -30,6 +22,8 @@ module Riffer::Helpers::Identifier
30
22
  return cached if cached
31
23
 
32
24
  real_name = real_name(klass)
25
+ # Anonymous classes skip the cache so a class named later still derives its
26
+ # real identifier; callers must not memoize their own for the same reason.
33
27
  return "" if real_name.nil?
34
28
 
35
29
  derived = derive(real_name)
@@ -37,13 +31,10 @@ module Riffer::Helpers::Identifier
37
31
  derived
38
32
  end
39
33
 
40
- # Returns the class-path name of a class or module, or +nil+ when anonymous.
41
- # Tool classes shadow Module#name with the identifier DSL, so the real name
42
- # must come from Module's own implementation.
43
- #
44
34
  #--
45
35
  #: (Module) -> String?
46
36
  def real_name(klass)
37
+ # Tool classes shadow Module#name with the identifier DSL.
47
38
  Module.instance_method(:name).bind_call(klass) #: String?
48
39
  end
49
40
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Riffer::Helpers::Validate
5
+ extend self
6
+
7
+ # The error names the class, not the value, so a misassigned secret never
8
+ # lands in a log.
9
+ #--
10
+ #: (untyped, attribute: String) -> String?
11
+ def optional_string(value, attribute:)
12
+ return value if value.nil? || value.is_a?(String)
13
+
14
+ raise Riffer::ArgumentError, "#{attribute} must be a String or nil, got #{value.class}"
15
+ end
16
+
17
+ #--
18
+ #: (untyped, attribute: String) -> Integer
19
+ def positive_integer(value, attribute:)
20
+ return value if value.is_a?(Integer) && value.positive?
21
+
22
+ raise Riffer::ArgumentError, "#{attribute} must be a positive integer"
23
+ end
24
+
25
+ #--
26
+ #: (untyped, attribute: String) -> Riffer::Runner
27
+ def runner(value, attribute:)
28
+ return value if value.is_a?(Riffer::Runner)
29
+
30
+ raise Riffer::ArgumentError, "#{attribute} must be a Riffer::Runner instance"
31
+ end
32
+
33
+ #--
34
+ #: (untyped, attribute: String) -> String
35
+ def model_id(value, attribute:)
36
+ segments = value.to_s.split("/", 2)
37
+ valid = value.is_a?(String) && segments.length == 2 && segments.none? { |segment| segment.strip.empty? }
38
+ return value if valid
39
+
40
+ raise Riffer::ArgumentError, "#{attribute} must be in \"provider/model\" form, got #{value.inspect}"
41
+ end
42
+ end
@@ -1,14 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Wraps MCP-generated tool classes so +tools/call+ resolves
5
- # +Riffer.config.mcp.credentials+ per invocation, copying metadata from the
6
- # inner class at wrap time.
7
4
  module Riffer::Mcp::AuthenticatedTool
8
5
  extend self
9
6
 
10
- # Returns one wrapper class per inner tool, sharing +manifest+ and +matched_tags+.
11
- #
12
7
  #--
13
8
  #: (Array[singleton(Riffer::Mcp::Tool)], Riffer::Mcp::Manifest, Array[Symbol]) -> Array[singleton(Riffer::Mcp::Tool)]
14
9
  def wrap_all(tool_classes, manifest, matched_tags)
@@ -23,8 +18,7 @@ module Riffer::Mcp::AuthenticatedTool
23
18
  tags = matched_tags
24
19
 
25
20
  # steep does not model Class.new's class_eval semantics — the block body
26
- # typechecks against the enclosing module, so the ivar assignments and the
27
- # define_method bodies are unresolvable.
21
+ # typechecks against the enclosing module.
28
22
  Class.new(Riffer::Mcp::Tool) do
29
23
  # steep:ignore:start
30
24
  @identifier = inner.identifier
@@ -32,11 +26,9 @@ module Riffer::Mcp::AuthenticatedTool
32
26
  @input_schema = inner.parameters_schema
33
27
  @mcp_server_tool_name = inner.mcp_server_tool_name
34
28
 
35
- # Creates a fresh client per +tools/call+ so headers from the credentials
36
- # proc stay current.
37
- # TODO: A per-headers cache would reduce connection churn under load, and
38
- # requires a follow-up investigation to determine how to invalidate failing
39
- # clients.
29
+ # A fresh client per +tools/call+ keeps credentials-proc headers current at
30
+ # the cost of connection churn; a per-headers cache would first need a way
31
+ # to invalidate failing clients.
40
32
  define_method(:build_call_client) do |endpoint, headers|
41
33
  Riffer::Mcp::Client.new(endpoint: endpoint, headers: headers)
42
34
  end
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Thin wrapper around the MCP Ruby SDK client (mcp gem v0.8+). Resolves headers
5
- # (if a Proc) once at init, then provides +tools_list+ / +tools_call+ — used for
6
- # discovery and for +tools/call+ when no +credentials+ proc is configured.
7
4
  class Riffer::Mcp::Client
8
5
  # @rbs @client: untyped
9
6
 
@@ -20,8 +17,6 @@ class Riffer::Mcp::Client
20
17
  end
21
18
  end
22
19
 
23
- # Returns tool definition hashes with +:name+, +:description+, and
24
- # +:input_schema+ keys.
25
20
  #--
26
21
  #: () -> Array[Hash[Symbol, untyped]]
27
22
  def tools_list
@@ -34,8 +29,6 @@ class Riffer::Mcp::Client
34
29
  end
35
30
  end
36
31
 
37
- # Calls a tool on the MCP server and returns joined text content from the response.
38
- #
39
32
  #--
40
33
  #: (String, ?Hash[untyped, untyped]) -> String
41
34
  def tools_call(name, arguments = {})
@@ -3,22 +3,18 @@
3
3
 
4
4
  require "uri"
5
5
 
6
- # Holds the configuration for a single MCP server.
7
6
  class Riffer::Mcp::Manifest
8
- # Identifier used as the registration key and generated-agent identifier.
9
7
  attr_reader :name #: String # @dynamic name
10
8
 
11
- # Tags for matching +use_mcp+.
12
9
  attr_reader :tags #: Array[Symbol] # @dynamic tags
13
10
 
14
- # HTTPS URL passed to the MCP transport.
15
11
  attr_reader :endpoint #: String # @dynamic endpoint
16
12
 
17
- # Headers (or a Proc) resolved once when building the discovery client.
13
+ # Resolved once, when building the discovery client.
18
14
  attr_reader :discovery_headers #: (Hash[String, untyped] | ::Proc)? # @dynamic discovery_headers
19
15
 
20
- # Optional hint (+:global+/+:tenant+/+:user+) for whether invocation
21
- # credentials depend on tenant/user keys in +context+.
16
+ # +:global+, +:tenant+, or +:user+: whether invocation credentials depend on
17
+ # tenant/user keys in +context+.
22
18
  attr_reader :credentials_scope #: Symbol? # @dynamic credentials_scope
23
19
 
24
20
  # Raises Riffer::ArgumentError unless +name+ is present and +endpoint+ is a
@@ -1,18 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Per-server state managed by Riffer::Mcp::Registry — discovers tools via
5
- # +tools/list+ and generates tool classes when a server is registered.
6
4
  class Riffer::Mcp::Registration
7
5
  # @rbs @cancelled: bool
8
6
  # @rbs @tools: Array[singleton(Riffer::Mcp::Tool)]
9
7
  # @rbs @mutex: Thread::Mutex
10
8
 
11
- # The manifest that describes this server.
12
9
  attr_reader :manifest #: Riffer::Mcp::Manifest # @dynamic manifest
13
10
 
14
- # Generated Riffer::Mcp::Tool subclasses.
15
- #
16
11
  #--
17
12
  #: () -> Array[singleton(Riffer::Mcp::Tool)]
18
13
  def tools
@@ -29,17 +24,12 @@ class Riffer::Mcp::Registration
29
24
  run_discovery
30
25
  end
31
26
 
32
- # Retires this registration, preventing in-flight discovery from publishing
33
- # state.
34
- #
35
27
  #--
36
28
  #: () -> void
37
29
  def retire!
38
30
  @mutex.synchronize { @cancelled = true }
39
31
  end
40
32
 
41
- # Returns true if this registration has been retired.
42
- #
43
33
  #--
44
34
  #: () -> bool
45
35
  def retired?
@@ -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)