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,22 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Normalized token usage for an LLM API call. Buckets carry the same
5
- # meaning for every provider.
6
4
  class Riffer::Providers::TokenUsage
7
- # Number of tokens entering the context window, including cache reads and writes.
5
+ #--
6
+ #: ((Hash[Symbol, untyped] | Riffer::Providers::TokenUsage)) -> Riffer::Providers::TokenUsage
7
+ def self.from_hash(usage)
8
+ return usage if usage.is_a?(Riffer::Providers::TokenUsage)
9
+
10
+ new(
11
+ input_tokens: usage[:input_tokens],
12
+ output_tokens: usage[:output_tokens],
13
+ cache_write_tokens: usage[:cache_write_tokens],
14
+ cache_read_tokens: usage[:cache_read_tokens],
15
+ cost: usage[:cost],
16
+ )
17
+ end
18
+
19
+ # Normalized across providers: includes cache reads and writes.
8
20
  attr_reader :input_tokens #: Integer # @dynamic input_tokens
9
21
 
10
- # Number of tokens generated by the model, including reasoning/thinking tokens.
22
+ # Includes reasoning/thinking tokens.
11
23
  attr_reader :output_tokens #: Integer # @dynamic output_tokens
12
24
 
13
- # Subset of +input_tokens+ written to the provider's prompt cache, when the provider reports it.
25
+ # Subset of +input_tokens+.
14
26
  attr_reader :cache_write_tokens #: Integer? # @dynamic cache_write_tokens
15
27
 
16
- # Subset of +input_tokens+ read from the provider's prompt cache, when the provider reports it.
28
+ # Subset of +input_tokens+.
17
29
  attr_reader :cache_read_tokens #: Integer? # @dynamic cache_read_tokens
18
30
 
19
- # Cost of the call, set when the model is priced. For observability, not billing.
31
+ # For observability, not billing.
20
32
  attr_reader :cost #: Float? # @dynamic cost
21
33
 
22
34
  #--
@@ -29,16 +41,12 @@ class Riffer::Providers::TokenUsage
29
41
  @cost = cost
30
42
  end
31
43
 
32
- # Returns the total number of tokens (input + output).
33
- #
34
44
  #--
35
45
  #: () -> Integer
36
46
  def total_tokens
37
47
  input_tokens + output_tokens
38
48
  end
39
49
 
40
- # Combines two TokenUsage objects for cumulative tracking.
41
- #
42
50
  #--
43
51
  #: (Riffer::Providers::TokenUsage) -> Riffer::Providers::TokenUsage
44
52
  def +(other)
@@ -51,7 +59,6 @@ class Riffer::Providers::TokenUsage
51
59
  )
52
60
  end
53
61
 
54
- # Converts the token usage to a hash; cache tokens and cost are omitted when nil.
55
62
  #--
56
63
  #: () -> Hash[Symbol, (Integer | Float)]
57
64
  def to_h
@@ -1,57 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Registry of a class's direct subclasses, keyed by identifier. Extend it
5
- # onto a base class to look up subclasses in constant time via +find+ and +all+.
6
- # Subclasses join implicitly by inheriting; +register+ adds one explicitly, for
7
- # ephemeral classes a test suite builds and tears down. Registration is not
8
- # synchronized — register during boot or from a single-threaded test, before
9
- # concurrent lookups begin.
10
- #
11
- # class Riffer::Tool
12
- # extend Riffer::Registrable
13
- # end
14
- #
15
- # Riffer::Tool.find("weather_tool") # => WeatherTool
16
- #
17
4
  # @rbs module-self Class
18
5
  module Riffer::Registrable
19
6
  # @rbs @identifier_registry: Hash[String, Class]?
20
7
  # @rbs @explicit_registrations: Hash[String, Class]?
21
8
 
22
- # Finds a registered subclass by identifier, or +nil+ when none matches.
23
- # Implicit registration covers only *named direct* subclasses: grandchildren
24
- # are not visible to a grandparent's +find+ (call +find+ on their direct
25
- # parent instead), anonymous classes are never registered implicitly, and a
26
- # subclass whose name no longer resolves back to it is dropped at the next
27
- # registry rebuild. Duplicate identifiers raise
28
- # Riffer::DuplicateIdentifierError at first lookup.
29
- #
30
9
  #--
31
10
  #: (String | Symbol) -> Class?
32
11
  def find(identifier)
33
12
  identifier_registry[identifier.to_s]
34
13
  end
35
14
 
36
- # Returns all registered subclasses, implicit and explicit. Carries the same
37
- # registration rules as +find+.
38
- #
39
15
  #--
40
16
  #: () -> Array[Class]
41
17
  def all
42
18
  identifier_registry.values
43
19
  end
44
20
 
45
- # Registers a direct subclass under its +identifier+, whether or not it is
46
- # named — unlike implicit registration, it survives a name that no longer
47
- # resolves, so an ephemeral class stays findable until +unregister+. Prefer
48
- # Riffer::Testing for ordinary test setup, which stubs and cleans up
49
- # automatically.
50
- #
51
- # Raises Riffer::ArgumentError when the identifier is blank or the class is
52
- # not a direct subclass, and Riffer::DuplicateIdentifierError when the
53
- # identifier is already taken — including by this same class.
54
- #
55
21
  #--
56
22
  #: (Class) -> void
57
23
  def register(klass)
@@ -65,12 +31,12 @@ module Riffer::Registrable
65
31
  existing = identifier_registry[key]
66
32
  raise_duplicate_identifier!(key, existing, klass) if existing
67
33
 
34
+ # Not synchronized: register during boot or from a single-threaded test,
35
+ # before concurrent lookups begin.
68
36
  explicit_registrations[key] = klass
69
37
  @identifier_registry = nil
70
38
  end
71
39
 
72
- # Removes an explicit registration of +klass+, leaving implicit registrations
73
- # untouched.
74
40
  #--
75
41
  #: (Class) -> void
76
42
  def unregister(klass)
@@ -83,13 +49,12 @@ module Riffer::Registrable
83
49
 
84
50
  private
85
51
 
86
- # Ruby invokes +inherited+ with +self+ bound to the direct superclass — the
87
- # only registry the new subclass joins — so busting self's memo is exactly
88
- # sufficient.
89
52
  #--
90
53
  #: (Class) -> void
91
54
  def inherited(subclass)
92
55
  super
56
+ # +self+ is the direct superclass — the only registry the new subclass
57
+ # joins — so busting self's memo is exactly sufficient.
93
58
  @identifier_registry = nil
94
59
  end
95
60
 
@@ -108,6 +73,8 @@ module Riffer::Registrable
108
73
  #--
109
74
  #: () -> Hash[String, Class]
110
75
  def build_identifier_registry
76
+ # Explicit entries skip +live?+ so an ephemeral class stays findable until
77
+ # +unregister+, even once its name no longer resolves.
111
78
  subclasses.each_with_object(explicit_registrations.dup) do |subclass, acc|
112
79
  next unless live?(subclass)
113
80
 
@@ -121,15 +88,14 @@ module Riffer::Registrable
121
88
  end.freeze
122
89
  end
123
90
 
124
- # Class#subclasses keeps returning superseded generations of a reloaded or
125
- # stubbed class, so a subclass counts only while its own name still resolves
126
- # back to it. An anonymous class has no name to resolve and is skipped even
127
- # with an explicit identifier — the MCP factory and serializer shells
128
- # synthesize short-lived anonymous classes whose registration would flake
129
- # with GC timing.
130
91
  #--
131
92
  #: (Class) -> bool
132
93
  def live?(subclass)
94
+ # Class#subclasses keeps returning superseded generations of a reloaded or
95
+ # stubbed class, so a subclass counts only while its own name still
96
+ # resolves back to it. Anonymous classes are skipped even with an
97
+ # identifier: the MCP factory and serializer shells synthesize short-lived
98
+ # anonymous classes whose registration would flake with GC timing.
133
99
  real_name = Riffer::Helpers::Identifier.real_name(subclass)
134
100
  return false if real_name.nil?
135
101
 
@@ -1,12 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Processes items concurrently using fibers via the +async+ gem.
5
- # +max_concurrency+ caps simultaneous fibers via an <tt>Async::Semaphore</tt>.
6
- # If multiple fibers raise, only the first exception is re-raised after all
7
- # finish.
8
- # Joins the current reactor task when one is already running, and otherwise
9
- # starts its own.
10
4
  class Riffer::Runner::Fibers < Riffer::Runner
11
5
  # @rbs @max_concurrency: Integer?
12
6
 
@@ -35,6 +29,7 @@ class Riffer::Runner::Fibers < Riffer::Runner
35
29
  barrier
36
30
  end
37
31
 
32
+ # Sync joins the running reactor task if there is one, otherwise starts its own.
38
33
  Sync do
39
34
  items.each_with_index do |item, index|
40
35
  parent.async do
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Processes items sequentially in the current thread — the default runner.
5
4
  class Riffer::Runner::Sequential < Riffer::Runner
6
5
  #--
7
6
  #: (Array[untyped], context: Riffer::Agent::Context?) { (untyped) -> untyped } -> Array[untyped]
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Processes items concurrently using a thread pool of up to +max_concurrency+
5
- # workers pulling from a shared queue, so a slow item doesn't block others. If
6
- # multiple workers raise, only the first exception is re-raised after all finish.
7
4
  class Riffer::Runner::Threaded < Riffer::Runner
8
5
  # @rbs @max_concurrency: Integer
9
6
 
data/lib/riffer/runner.rb CHANGED
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Generic concurrency primitive for batch execution. Subclasses implement
5
- # +map+ to control how items are processed.
6
4
  class Riffer::Runner
7
- # Maps over items using the provided block.
8
5
  #--
9
6
  #: (Array[untyped], context: Riffer::Agent::Context?) { (untyped) -> untyped } -> Array[untyped]
10
7
  def map(items, context:, &block)
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Tool the LLM calls to activate a skill and receive its instructions;
5
- # registered automatically when an agent has skills configured.
6
4
  class Riffer::Skills::ActivateTool < Riffer::Tool
7
5
  identifier "skill_activate"
8
6
  description "Activates a skill and returns its instructions. " \
@@ -13,7 +11,6 @@ class Riffer::Skills::ActivateTool < Riffer::Tool
13
11
  required :name, String, description: "The skill name to activate"
14
12
  end
15
13
 
16
- # Activates a skill by name and returns its wrapped body, or a short pointer when the skill is already active.
17
14
  #--
18
15
  #: (context: Riffer::Agent::Context?, name: String) -> Riffer::Tools::Response
19
16
  def call(context:, name:)
@@ -1,12 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Interface for skill adapters — provider-specific rendering of the
5
- # available-skills section in the system prompt. Subclass and override
6
- # +render_catalog+; the activation tool is exposed via +#skill_activate_tool+
7
- # for the rendered output.
8
4
  class Riffer::Skills::Adapter
9
- # The activation tool class for this adapter.
10
5
  attr_reader :skill_activate_tool #: singleton(Riffer::Tool) # @dynamic skill_activate_tool
11
6
 
12
7
  #--
@@ -15,21 +10,18 @@ class Riffer::Skills::Adapter
15
10
  @skill_activate_tool = skill_activate_tool
16
11
  end
17
12
 
18
- # Renders a skill catalog section for the system prompt.
19
13
  #--
20
14
  #: (Array[Riffer::Skills::Frontmatter]) -> String
21
15
  def render_catalog(skills)
22
16
  raise NotImplementedError, "#{self.class} must implement #render_catalog"
23
17
  end
24
18
 
25
- # Renders an activated skill body wrapped in identifying tags.
26
19
  #--
27
20
  #: (Riffer::Skills::Frontmatter, String) -> String
28
21
  def render_activation(skill, body)
29
22
  %(<skill_content name="#{skill.name}">\n#{body}\n</skill_content>)
30
23
  end
31
24
 
32
- # The behavioral instructions rendered alongside the catalog.
33
25
  #--
34
26
  #: () -> String
35
27
  def catalog_instructions
@@ -1,24 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Interface for skill storage backends. Subclass and implement +list_skills+
5
- # and +read_skill+ for custom storage (database, S3, etc.); use
6
- # Riffer::Skills::Frontmatter.parse on raw SKILL.md content.
7
4
  class Riffer::Skills::Backend
8
5
  SKILL_FILENAME = "SKILL.md" #: String
9
6
 
10
7
  def initialize = nil
11
8
 
12
- # Returns frontmatter for all available skills; called once at the start of
13
- # generate/stream.
14
9
  #--
15
10
  #: () -> Array[Riffer::Skills::Frontmatter]
16
11
  def list_skills
17
12
  raise NotImplementedError, "#{self.class} must implement #list_skills"
18
13
  end
19
14
 
20
- # Returns the full SKILL.md body (without frontmatter) for a skill. Raises
21
- # Riffer::ArgumentError if the skill is not found.
15
+ # Returns the SKILL.md body without frontmatter. Raises Riffer::ArgumentError
16
+ # if the skill is not found.
22
17
  #--
23
18
  #: (String) -> String
24
19
  def read_skill(name)
@@ -1,13 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Configuration object for the skills block DSL.
5
- #
6
- # skills do
7
- # backend Riffer::Skills::FilesystemBackend.new(".skills")
8
- # adapter Riffer::Skills::XmlAdapter
9
- # activate ["code-review"]
10
- # end
11
4
  class Riffer::Skills::Config
12
5
  # @rbs @backend: (Riffer::Skills::Backend | Proc)?
13
6
  # @rbs @adapter: singleton(Riffer::Skills::Adapter)?
@@ -23,7 +16,6 @@ class Riffer::Skills::Config
23
16
  @activate_tool = nil
24
17
  end
25
18
 
26
- # Gets or sets the skills backend (a Backend or a +context+-resolved Proc).
27
19
  #--
28
20
  #: (?(Riffer::Skills::Backend | Proc)?) -> (Riffer::Skills::Backend | Proc)?
29
21
  def backend(value = nil)
@@ -32,8 +24,6 @@ class Riffer::Skills::Config
32
24
  @backend = value
33
25
  end
34
26
 
35
- # Gets or sets a custom skill adapter class; defaults to the provider's
36
- # preferred adapter.
37
27
  #--
38
28
  #: (?singleton(Riffer::Skills::Adapter)?) -> singleton(Riffer::Skills::Adapter)?
39
29
  def adapter(value = nil)
@@ -42,9 +32,6 @@ class Riffer::Skills::Config
42
32
  @adapter = value
43
33
  end
44
34
 
45
- # Gets or sets skill names to activate at startup (an array or a
46
- # +context+-resolved Proc); activated skills' bodies are included in the
47
- # system prompt without a tool call.
48
35
  #--
49
36
  #: (?(Array[String] | Proc)?) -> (Array[String] | Proc)?
50
37
  def activate(value = nil)
@@ -53,13 +40,11 @@ class Riffer::Skills::Config
53
40
  @activate = value
54
41
  end
55
42
 
56
- # Gets or sets the per-agent skill activation tool override, or +nil+ when
57
- # unset — the global fallback to <tt>Riffer.config.skills.default_activate_tool</tt>
58
- # is applied by the agent at resolution, not here. Raises Riffer::ArgumentError
59
- # on an invalid value.
60
43
  #--
61
44
  #: (?singleton(Riffer::Tool)?) -> singleton(Riffer::Tool)?
62
45
  def activate_tool(value = nil)
46
+ # No fallback to <tt>Riffer.config.skills.default_activate_tool</tt> here;
47
+ # the agent applies it at resolution.
63
48
  return @activate_tool if value.nil?
64
49
  unless value.is_a?(Class) && value < Riffer::Tool
65
50
  raise Riffer::ArgumentError, "activate_tool must be a Riffer::Tool subclass"
@@ -67,4 +52,15 @@ class Riffer::Skills::Config
67
52
 
68
53
  @activate_tool = value
69
54
  end
55
+
56
+ private
57
+
58
+ #--
59
+ #: (Riffer::Skills::Config) -> void
60
+ def initialize_copy(source)
61
+ super
62
+ # A shallow copy would share the activation list, so activating a skill on
63
+ # either config would reach the other.
64
+ @activate = Riffer::Helpers::DeepDup.call(source.activate)
65
+ end
70
66
  end
@@ -1,22 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Skills context for an agent generation cycle — coordinates discovery,
5
- # activation, and prompt rendering, caching skill bodies to avoid redundant
6
- # backend reads. Exposed to tools via <tt>context.skills</tt>.
7
4
  class Riffer::Skills::Context
8
5
  # @rbs @backend: Riffer::Skills::Backend
9
6
  # @rbs @bodies: Hash[String, String]
10
7
  # @rbs @activated: Array[String]
11
8
  # @rbs @preactivated: Array[String]
12
9
 
13
- # Skill catalog indexed by name.
14
10
  attr_reader :skills #: Hash[String, Riffer::Skills::Frontmatter] # @dynamic skills
15
11
 
16
- # The skill adapter used for this context.
17
12
  attr_reader :adapter #: Riffer::Skills::Adapter # @dynamic adapter
18
13
 
19
- # Optional callback invoked when a skill is first activated.
20
14
  attr_accessor :on_activate #: (^(String) -> void)? # @dynamic on_activate, on_activate=
21
15
 
22
16
  #--
@@ -30,10 +24,7 @@ class Riffer::Skills::Context
30
24
  @preactivated = [] #: Array[String]
31
25
  end
32
26
 
33
- # Returns a skill's body without recording an activation.
34
- #
35
27
  # Raises Riffer::ArgumentError if the skill is not in the catalog.
36
- #
37
28
  #--
38
29
  #: (String) -> String
39
30
  def read(name)
@@ -42,10 +33,7 @@ class Riffer::Skills::Context
42
33
  @bodies[name] ||= @backend.read_skill(name)
43
34
  end
44
35
 
45
- # Activates a skill by name. Returns the cached body on re-activation.
46
- #
47
36
  # Raises Riffer::ArgumentError if the skill is not in the catalog.
48
- #
49
37
  #--
50
38
  #: (String) -> String
51
39
  def activate(name)
@@ -57,11 +45,7 @@ class Riffer::Skills::Context
57
45
  body
58
46
  end
59
47
 
60
- # Activates a skill and returns its body wrapped for injection as a user
61
- # message.
62
- #
63
48
  # Raises Riffer::ArgumentError if the skill is not in the catalog.
64
- #
65
49
  #--
66
50
  #: (String) -> String
67
51
  def activation_prompt(name)
@@ -69,11 +53,7 @@ class Riffer::Skills::Context
69
53
  @adapter.render_activation(skills.fetch(name), body)
70
54
  end
71
55
 
72
- # Activates a skill whose body renders in the system prompt rather than the
73
- # conversation.
74
- #
75
56
  # Raises Riffer::ArgumentError if the skill is not in the catalog.
76
- #
77
57
  #--
78
58
  #: (String) -> void
79
59
  def preactivate(name)
@@ -81,10 +61,7 @@ class Riffer::Skills::Context
81
61
  @preactivated << name unless @preactivated.include?(name)
82
62
  end
83
63
 
84
- # Clears a skill's activation so the next activation is treated as the first.
85
- #
86
64
  # Raises Riffer::ArgumentError if the skill is not in the catalog.
87
- #
88
65
  #--
89
66
  #: (String) -> void
90
67
  def deactivate(name)
@@ -94,30 +71,24 @@ class Riffer::Skills::Context
94
71
  nil
95
72
  end
96
73
 
97
- # Returns whether a skill has been activated.
98
- #
99
74
  #--
100
75
  #: (String) -> bool
101
76
  def activated?(name)
102
77
  @activated.include?(name)
103
78
  end
104
79
 
105
- # Returns whether a skill exists and may be activated by the model.
106
80
  #--
107
81
  #: (String) -> bool
108
82
  def model_invocable?(name)
109
83
  skills.key?(name) && !skills.fetch(name).disable_model_invocation
110
84
  end
111
85
 
112
- # Returns whether any skill is available for the model to activate.
113
86
  #--
114
87
  #: () -> bool
115
88
  def activatable?
116
89
  available_skills.any?
117
90
  end
118
91
 
119
- # Returns the complete skills section for the system prompt — the catalog plus
120
- # any pre-activated skill bodies.
121
92
  #--
122
93
  #: () -> String
123
94
  def system_prompt
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Built-in backend that reads skills from the filesystem. Scans configured
5
- # directories for immediate child directories containing +SKILL.md+; directory
6
- # names must match the skill +name+.
7
4
  class Riffer::Skills::FilesystemBackend < Riffer::Skills::Backend
8
5
  # @rbs @paths: Array[String]
9
6
  # @rbs @skills_cache: Hash[String, String]?
@@ -16,8 +13,6 @@ class Riffer::Skills::FilesystemBackend < Riffer::Skills::Backend
16
13
  @skills_cache = nil #: Hash[String, String]?
17
14
  end
18
15
 
19
- # Returns frontmatter for all discovered skills; on a name collision across
20
- # paths, first-path-wins.
21
16
  #--
22
17
  #: () -> Array[Riffer::Skills::Frontmatter]
23
18
  def list_skills
@@ -46,8 +41,6 @@ class Riffer::Skills::FilesystemBackend < Riffer::Skills::Backend
46
41
  frontmatters
47
42
  end
48
43
 
49
- # Returns the full SKILL.md body (without frontmatter) for a skill. Raises
50
- # Riffer::ArgumentError if the skill is not found.
51
44
  #--
52
45
  #: (String) -> String
53
46
  def read_skill(name)
@@ -3,33 +3,21 @@
3
3
 
4
4
  require "yaml"
5
5
 
6
- # Immutable value object holding parsed SKILL.md YAML frontmatter. Required
7
- # fields: +name+ and +description+; the optional +disable-model-invocation+
8
- # flag is recognized, and any other unrecognized top-level keys are merged into
9
- # +metadata+.
10
6
  class Riffer::Skills::Frontmatter
11
7
  NAME_PATTERN = /\A[a-z0-9]+(-[a-z0-9]+)*\z/ #: Regexp
12
8
  MAX_NAME_LENGTH = 64 #: Integer
13
9
  MAX_DESCRIPTION_LENGTH = 1024 #: Integer
14
10
 
15
- # The skill name (1-64 chars, lowercase alphanumeric and hyphens).
16
11
  attr_reader :name #: String # @dynamic name
17
12
 
18
- # The skill description (1-1024 chars).
19
13
  attr_reader :description #: String # @dynamic description
20
14
 
21
- # Whether the skill opts out of model-driven activation. Hidden from the
22
- # catalog and rejected at model activation; still reachable via programmatic
23
- # activation.
24
15
  attr_reader :disable_model_invocation #: bool # @dynamic disable_model_invocation
25
16
 
26
- # Metadata from the spec's +metadata+ field plus any unrecognized top-level
27
- # keys.
28
17
  attr_reader :metadata #: Hash[Symbol, untyped] # @dynamic metadata
29
18
 
30
- # Parses a raw SKILL.md string into a +[Frontmatter, body]+ pair — public so
31
- # custom backends needn't reimplement parsing. Raises Riffer::ArgumentError
32
- # if the frontmatter is invalid.
19
+ # Public so custom backends needn't reimplement parsing. Raises
20
+ # Riffer::ArgumentError if the frontmatter is invalid.
33
21
  #--
34
22
  #: (String) -> [Riffer::Skills::Frontmatter, String]
35
23
  def self.parse(raw)
@@ -47,7 +35,6 @@ class Riffer::Skills::Frontmatter
47
35
  ]
48
36
  end
49
37
 
50
- # Parses only the frontmatter from a raw SKILL.md string, ignoring the body.
51
38
  # Raises Riffer::ArgumentError if the frontmatter is invalid.
52
39
  #--
53
40
  #: (String) -> Riffer::Skills::Frontmatter
@@ -80,7 +67,6 @@ class Riffer::Skills::Frontmatter
80
67
  private_class_method :split_frontmatter
81
68
 
82
69
  # Raises Riffer::ArgumentError if +name+ or +description+ is invalid.
83
- # +disable_model_invocation+ is treated as set only when literally +true+.
84
70
  #--
85
71
  #: (name: String, description: String, ?disable_model_invocation: bool, ?metadata: Hash[Symbol, untyped]) -> void
86
72
  def initialize(name:, description:, disable_model_invocation: false, metadata: {})
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Default skill adapter — renders a skill catalog as Markdown for the system
5
- # prompt.
6
4
  class Riffer::Skills::MarkdownAdapter < Riffer::Skills::Adapter
7
- # Renders a skill catalog as Markdown.
8
5
  #--
9
6
  #: (Array[Riffer::Skills::Frontmatter]) -> String
10
7
  def render_catalog(skills)
@@ -21,12 +18,12 @@ class Riffer::Skills::MarkdownAdapter < Riffer::Skills::Adapter
21
18
 
22
19
  private
23
20
 
24
- # Collapses whitespace so a multi-line (block scalar) description stays within
25
- # its `-` list item instead of breaking out to column 0, where continuation
26
- # lines would read as top-level prompt text or fabricated catalog entries.
27
21
  #--
28
22
  #: (String) -> String
29
23
  def single_line(description)
24
+ # A multi-line (block scalar) description must stay within its `-` list
25
+ # item; continuation lines at column 0 would read as top-level prompt text
26
+ # or fabricated catalog entries.
30
27
  description.gsub(/\s+/, " ").strip
31
28
  end
32
29
  end
@@ -3,10 +3,7 @@
3
3
 
4
4
  require "cgi/escape"
5
5
 
6
- # Renders a skill catalog as XML for the system prompt, optimized for
7
- # Anthropic/Claude.
8
6
  class Riffer::Skills::XmlAdapter < Riffer::Skills::Adapter
9
- # Renders a skill catalog as XML.
10
7
  #--
11
8
  #: (Array[Riffer::Skills::Frontmatter]) -> String
12
9
  def render_catalog(skills)
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Base class for all streaming events. Subclasses must implement +to_h+.
5
4
  class Riffer::StreamEvents::Base
6
- # The message role (typically :assistant).
7
5
  attr_reader :role #: Symbol # @dynamic role
8
6
 
9
7
  #--
@@ -12,7 +10,6 @@ class Riffer::StreamEvents::Base
12
10
  @role = role
13
11
  end
14
12
 
15
- # Converts the event to a hash.
16
13
  #--
17
14
  #: () -> Hash[Symbol, untyped]
18
15
  def to_h
@@ -1,17 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Normalized reason the LLM finished, emitted once near the end of the
5
- # stream; no ordering guarantee relative to TokenUsageDone.
4
+ # No ordering guarantee relative to TokenUsageDone.
6
5
  class Riffer::StreamEvents::FinishReasonDone < Riffer::StreamEvents::Base
7
- # The normalized finish reason (see <tt>Riffer::Providers::FinishReason::VALUES</tt>).
8
6
  attr_reader :finish_reason #: Symbol # @dynamic finish_reason
9
7
 
10
- # The provider's raw finish-reason value, when one exists on the wire.
11
8
  attr_reader :raw_finish_reason #: String? # @dynamic raw_finish_reason
12
9
 
13
- # Raises Riffer::ArgumentError when +finish_reason+ is outside the
14
- # normalized vocabulary.
15
10
  #--
16
11
  #: (finish_reason: Symbol, ?raw_finish_reason: String?, ?role: Symbol) -> void
17
12
  def initialize(finish_reason:, raw_finish_reason: nil, role: :assistant)