riffer 0.30.0 → 0.32.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 (214) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/code-style.md +63 -4
  3. data/.agents/rbs-inline.md +1 -6
  4. data/.release-please-manifest.json +1 -1
  5. data/AGENTS.md +1 -2
  6. data/CHANGELOG.md +25 -0
  7. data/docs/08_MESSAGES.md +1 -1
  8. data/docs/14_MCP.md +50 -5
  9. data/docs/15_SERIALIZATION.md +23 -12
  10. data/docs/providers/02_AMAZON_BEDROCK.md +14 -0
  11. data/lib/riffer/agent/config.rb +42 -47
  12. data/lib/riffer/agent/context.rb +70 -50
  13. data/lib/riffer/agent/response.rb +4 -20
  14. data/lib/riffer/agent/run.rb +28 -67
  15. data/lib/riffer/agent/serializer.rb +36 -85
  16. data/lib/riffer/agent/session/repair.rb +14 -40
  17. data/lib/riffer/agent/session.rb +25 -67
  18. data/lib/riffer/agent/structured_output/result.rb +3 -11
  19. data/lib/riffer/agent/structured_output.rb +5 -13
  20. data/lib/riffer/agent.rb +81 -199
  21. data/lib/riffer/config.rb +34 -101
  22. data/lib/riffer/evals/evaluator.rb +7 -27
  23. data/lib/riffer/evals/evaluator_runner.rb +11 -19
  24. data/lib/riffer/evals/judge.rb +4 -25
  25. data/lib/riffer/evals/result.rb +1 -18
  26. data/lib/riffer/evals/run_result.rb +0 -11
  27. data/lib/riffer/evals/scenario_result.rb +0 -14
  28. data/lib/riffer/evals.rb +0 -6
  29. data/lib/riffer/guardrail.rb +4 -27
  30. data/lib/riffer/guardrails/modification.rb +0 -10
  31. data/lib/riffer/guardrails/result.rb +3 -30
  32. data/lib/riffer/guardrails/runner.rb +5 -22
  33. data/lib/riffer/guardrails/tripwire.rb +1 -19
  34. data/lib/riffer/guardrails.rb +2 -4
  35. data/lib/riffer/helpers/call_or_value.rb +4 -3
  36. data/lib/riffer/helpers/class_name_converter.rb +3 -1
  37. data/lib/riffer/helpers/dependencies.rb +5 -7
  38. data/lib/riffer/helpers.rb +0 -5
  39. data/lib/riffer/mcp/authenticated_tool.rb +9 -9
  40. data/lib/riffer/mcp/client.rb +12 -17
  41. data/lib/riffer/mcp/manifest.rb +13 -10
  42. data/lib/riffer/mcp/registration.rb +2 -11
  43. data/lib/riffer/mcp/registry.rb +44 -52
  44. data/lib/riffer/mcp/search_tool.rb +53 -0
  45. data/lib/riffer/mcp/tool_factory.rb +13 -18
  46. data/lib/riffer/mcp.rb +12 -17
  47. data/lib/riffer/messages/assistant.rb +2 -9
  48. data/lib/riffer/messages/base.rb +46 -16
  49. data/lib/riffer/messages/file_part.rb +32 -24
  50. data/lib/riffer/messages/system.rb +0 -5
  51. data/lib/riffer/messages/tool.rb +0 -10
  52. data/lib/riffer/messages/user.rb +0 -10
  53. data/lib/riffer/messages.rb +0 -7
  54. data/lib/riffer/params/boolean.rb +2 -4
  55. data/lib/riffer/params/param.rb +28 -39
  56. data/lib/riffer/params.rb +9 -21
  57. data/lib/riffer/providers/amazon_bedrock.rb +42 -28
  58. data/lib/riffer/providers/anthropic.rb +4 -9
  59. data/lib/riffer/providers/azure_open_ai.rb +3 -19
  60. data/lib/riffer/providers/base.rb +13 -26
  61. data/lib/riffer/providers/gemini.rb +4 -4
  62. data/lib/riffer/providers/mock.rb +6 -26
  63. data/lib/riffer/providers/open_ai.rb +6 -8
  64. data/lib/riffer/providers/open_router.rb +4 -10
  65. data/lib/riffer/providers/repository.rb +4 -3
  66. data/lib/riffer/providers/token_usage.rb +9 -20
  67. data/lib/riffer/providers.rb +0 -8
  68. data/lib/riffer/runner/fibers.rb +10 -16
  69. data/lib/riffer/runner/sequential.rb +1 -4
  70. data/lib/riffer/runner/threaded.rb +3 -14
  71. data/lib/riffer/runner.rb +2 -15
  72. data/lib/riffer/skills/activate_tool.rb +2 -11
  73. data/lib/riffer/skills/adapter.rb +4 -22
  74. data/lib/riffer/skills/backend.rb +7 -21
  75. data/lib/riffer/skills/config.rb +10 -31
  76. data/lib/riffer/skills/context.rb +5 -20
  77. data/lib/riffer/skills/filesystem_backend.rb +7 -25
  78. data/lib/riffer/skills/frontmatter.rb +10 -28
  79. data/lib/riffer/skills/markdown_adapter.rb +2 -9
  80. data/lib/riffer/skills/xml_adapter.rb +2 -8
  81. data/lib/riffer/stream_events/base.rb +1 -6
  82. data/lib/riffer/stream_events/guardrail_modification.rb +1 -8
  83. data/lib/riffer/stream_events/guardrail_tripwire.rb +1 -8
  84. data/lib/riffer/stream_events/interrupt.rb +4 -7
  85. data/lib/riffer/stream_events/reasoning_delta.rb +2 -4
  86. data/lib/riffer/stream_events/reasoning_done.rb +2 -4
  87. data/lib/riffer/stream_events/skill_activation.rb +2 -4
  88. data/lib/riffer/stream_events/text_delta.rb +0 -2
  89. data/lib/riffer/stream_events/text_done.rb +1 -3
  90. data/lib/riffer/stream_events/token_usage_done.rb +1 -8
  91. data/lib/riffer/stream_events/tool_call_delta.rb +2 -3
  92. data/lib/riffer/stream_events/tool_call_done.rb +1 -3
  93. data/lib/riffer/stream_events/web_search_done.rb +1 -3
  94. data/lib/riffer/stream_events/web_search_status.rb +2 -3
  95. data/lib/riffer/stream_events.rb +0 -10
  96. data/lib/riffer/tool.rb +6 -13
  97. data/lib/riffer/tools/response.rb +8 -4
  98. data/lib/riffer/tools/runtime/fibers.rb +0 -3
  99. data/lib/riffer/tools/runtime/inline.rb +1 -4
  100. data/lib/riffer/tools/runtime/threaded.rb +0 -2
  101. data/lib/riffer/tools/runtime.rb +5 -38
  102. data/lib/riffer/tools/toolable.rb +5 -16
  103. data/lib/riffer/tools.rb +0 -4
  104. data/lib/riffer/version.rb +1 -1
  105. data/lib/riffer.rb +7 -8
  106. data/sig/generated/riffer/agent/config.rbs +29 -46
  107. data/sig/generated/riffer/agent/context.rbs +40 -48
  108. data/sig/generated/riffer/agent/response.rbs +4 -20
  109. data/sig/generated/riffer/agent/run.rbs +12 -61
  110. data/sig/generated/riffer/agent/serializer.rbs +28 -81
  111. data/sig/generated/riffer/agent/session/repair.rbs +12 -40
  112. data/sig/generated/riffer/agent/session.rbs +25 -67
  113. data/sig/generated/riffer/agent/structured_output/result.rbs +2 -10
  114. data/sig/generated/riffer/agent/structured_output.rbs +5 -12
  115. data/sig/generated/riffer/agent.rbs +62 -191
  116. data/sig/generated/riffer/config.rbs +34 -100
  117. data/sig/generated/riffer/evals/evaluator.rbs +7 -27
  118. data/sig/generated/riffer/evals/evaluator_runner.rbs +9 -19
  119. data/sig/generated/riffer/evals/judge.rbs +4 -24
  120. data/sig/generated/riffer/evals/result.rbs +1 -17
  121. data/sig/generated/riffer/evals/run_result.rbs +0 -10
  122. data/sig/generated/riffer/evals/scenario_result.rbs +0 -13
  123. data/sig/generated/riffer/evals.rbs +0 -6
  124. data/sig/generated/riffer/guardrail.rbs +4 -27
  125. data/sig/generated/riffer/guardrails/modification.rbs +0 -10
  126. data/sig/generated/riffer/guardrails/result.rbs +3 -30
  127. data/sig/generated/riffer/guardrails/runner.rbs +5 -22
  128. data/sig/generated/riffer/guardrails/tripwire.rbs +1 -19
  129. data/sig/generated/riffer/guardrails.rbs +2 -4
  130. data/sig/generated/riffer/helpers/call_or_value.rbs +4 -3
  131. data/sig/generated/riffer/helpers/class_name_converter.rbs +1 -1
  132. data/sig/generated/riffer/helpers/dependencies.rbs +3 -7
  133. data/sig/generated/riffer/helpers.rbs +0 -5
  134. data/sig/generated/riffer/mcp/authenticated_tool.rbs +5 -4
  135. data/sig/generated/riffer/mcp/client.rbs +10 -16
  136. data/sig/generated/riffer/mcp/manifest.rbs +9 -9
  137. data/sig/generated/riffer/mcp/registration.rbs +2 -10
  138. data/sig/generated/riffer/mcp/registry.rbs +11 -18
  139. data/sig/generated/riffer/mcp/search_tool.rbs +26 -0
  140. data/sig/generated/riffer/mcp/tool_factory.rbs +10 -15
  141. data/sig/generated/riffer/mcp.rbs +10 -17
  142. data/sig/generated/riffer/messages/assistant.rbs +2 -8
  143. data/sig/generated/riffer/messages/base.rbs +11 -16
  144. data/sig/generated/riffer/messages/file_part.rbs +13 -23
  145. data/sig/generated/riffer/messages/system.rbs +0 -4
  146. data/sig/generated/riffer/messages/tool.rbs +0 -9
  147. data/sig/generated/riffer/messages/user.rbs +0 -9
  148. data/sig/generated/riffer/messages.rbs +0 -7
  149. data/sig/generated/riffer/params/boolean.rbs +2 -4
  150. data/sig/generated/riffer/params/param.rbs +21 -39
  151. data/sig/generated/riffer/params.rbs +9 -21
  152. data/sig/generated/riffer/providers/amazon_bedrock.rbs +21 -25
  153. data/sig/generated/riffer/providers/anthropic.rbs +2 -7
  154. data/sig/generated/riffer/providers/azure_open_ai.rbs +3 -18
  155. data/sig/generated/riffer/providers/base.rbs +9 -25
  156. data/sig/generated/riffer/providers/gemini.rbs +0 -2
  157. data/sig/generated/riffer/providers/mock.rbs +6 -26
  158. data/sig/generated/riffer/providers/open_ai.rbs +1 -5
  159. data/sig/generated/riffer/providers/open_router.rbs +4 -10
  160. data/sig/generated/riffer/providers/repository.rbs +2 -3
  161. data/sig/generated/riffer/providers/token_usage.rbs +6 -16
  162. data/sig/generated/riffer/providers.rbs +0 -8
  163. data/sig/generated/riffer/runner/fibers.rbs +8 -15
  164. data/sig/generated/riffer/runner/sequential.rbs +1 -3
  165. data/sig/generated/riffer/runner/threaded.rbs +3 -13
  166. data/sig/generated/riffer/runner.rbs +2 -14
  167. data/sig/generated/riffer/skills/activate_tool.rbs +2 -11
  168. data/sig/generated/riffer/skills/adapter.rbs +4 -22
  169. data/sig/generated/riffer/skills/backend.rbs +7 -21
  170. data/sig/generated/riffer/skills/config.rbs +10 -31
  171. data/sig/generated/riffer/skills/context.rbs +5 -20
  172. data/sig/generated/riffer/skills/filesystem_backend.rbs +7 -24
  173. data/sig/generated/riffer/skills/frontmatter.rbs +10 -27
  174. data/sig/generated/riffer/skills/markdown_adapter.rbs +2 -9
  175. data/sig/generated/riffer/skills/xml_adapter.rbs +2 -8
  176. data/sig/generated/riffer/stream_events/base.rbs +1 -6
  177. data/sig/generated/riffer/stream_events/guardrail_modification.rbs +1 -8
  178. data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +1 -8
  179. data/sig/generated/riffer/stream_events/interrupt.rbs +4 -7
  180. data/sig/generated/riffer/stream_events/reasoning_delta.rbs +2 -4
  181. data/sig/generated/riffer/stream_events/reasoning_done.rbs +2 -4
  182. data/sig/generated/riffer/stream_events/skill_activation.rbs +2 -4
  183. data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
  184. data/sig/generated/riffer/stream_events/text_done.rbs +1 -3
  185. data/sig/generated/riffer/stream_events/token_usage_done.rbs +1 -7
  186. data/sig/generated/riffer/stream_events/tool_call_delta.rbs +2 -3
  187. data/sig/generated/riffer/stream_events/tool_call_done.rbs +1 -3
  188. data/sig/generated/riffer/stream_events/web_search_done.rbs +1 -3
  189. data/sig/generated/riffer/stream_events/web_search_status.rbs +2 -3
  190. data/sig/generated/riffer/stream_events.rbs +0 -10
  191. data/sig/generated/riffer/tool.rbs +5 -12
  192. data/sig/generated/riffer/tools/response.rbs +6 -4
  193. data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -3
  194. data/sig/generated/riffer/tools/runtime/inline.rbs +1 -3
  195. data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -2
  196. data/sig/generated/riffer/tools/runtime.rbs +5 -37
  197. data/sig/generated/riffer/tools/toolable.rbs +4 -14
  198. data/sig/generated/riffer/tools.rbs +0 -4
  199. data/sig/generated/riffer.rbs +5 -4
  200. data/sig/manual/riffer/agent/session/repair.rbs +5 -0
  201. data/sig/manual/riffer/evals/evaluator_runner.rbs +5 -0
  202. data/sig/manual/riffer/helpers/class_name_converter.rbs +5 -0
  203. data/sig/manual/riffer/helpers/dependencies.rbs +5 -0
  204. data/sig/manual/riffer/mcp/authenticated_tool.rbs +5 -0
  205. data/sig/manual/riffer/mcp/registry.rbs +5 -0
  206. data/sig/manual/riffer/mcp/tool_factory.rbs +5 -0
  207. data/sig/manual/riffer/mcp.rbs +5 -0
  208. data/sig/manual/riffer/providers/repository.rbs +5 -0
  209. data/sig/manual/riffer.rbs +5 -0
  210. metadata +17 -9
  211. data/.agents/rdoc.md +0 -69
  212. data/lib/riffer/messages/converter.rb +0 -90
  213. data/sig/generated/riffer/messages/converter.rbs +0 -33
  214. data/sig/manual/riffer/tools/toolable.rbs +0 -6
@@ -1,39 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Riffer::Agent::Session::Repair holds the pure transformations that keep the
5
- # +tool_use+ ↔ +tool_result+ invariant on a message array. No state, no
6
- # instance — module-level functions only. Each entry point is gated by
7
- # +Riffer.config.experimental_history_healing+: when the flag is off the
8
- # function returns its input unchanged.
9
- #
10
- # Two seams:
11
- #
12
- # - +fill_orphans+ — fills orphan +tool_use+ blocks with placeholder
13
- # results. Used on interrupt (caller-issued or +max_steps+).
14
- # - +prune_orphans+ — drops orphan +tool_use+ blocks and parentless Tool
15
- # messages from a caller-provided seed so it is well-formed before the
16
- # next inference call. Used at construction time when
17
- # +Riffer::Agent.new(session:)+ receives a session.
4
+ # Pure, stateless transformations keeping the +tool_use+ +tool_result+
5
+ # invariant on a message array. Each entry point no-ops when
6
+ # +Riffer.config.experimental_history_healing+ is off.
18
7
  module Riffer::Agent::Session::Repair
19
- # Placeholder used to fill orphan +tool_use+ blocks. Emitted as the
20
- # +Riffer::Tools::Response+ body for each filled call_id.
8
+ extend self
9
+
10
+ # Placeholder response filled in for an orphaned +tool_use+ on interrupt.
21
11
  ORPHAN_PLACEHOLDER = ->(_tool_call) {
22
12
  Riffer::Tools::Response.error("Tool call interrupted before completion.", type: :interrupted)
23
13
  } #: ^(Riffer::Messages::Assistant::ToolCall) -> Riffer::Tools::Response
24
14
 
25
- # Fills any orphaned +tool_use+ in +messages+ with the
26
- # +ORPHAN_PLACEHOLDER+ response. Each placeholder Tool message is
27
- # inserted immediately after its parent assistant message. Returns
28
- # +[new_messages, filled_call_ids]+; +filled_call_ids+ is empty when
29
- # there are no orphans.
30
- #
31
- # No-op when +Riffer.config.experimental_history_healing+ is off:
32
- # returns +[messages, []]+ with the same array reference.
33
- #
15
+ # Fills each orphaned +tool_use+ in +messages+ with an +ORPHAN_PLACEHOLDER+
16
+ # result inserted after its parent. Returns +[new_messages, filled_call_ids]+.
34
17
  #--
35
18
  #: (Array[Riffer::Messages::Base]) -> [Array[Riffer::Messages::Base], Array[String]]
36
- def self.fill_orphans(messages)
19
+ def fill_orphans(messages)
37
20
  return [messages, []] unless Riffer.config.experimental_history_healing
38
21
 
39
22
  result_ids = messages.filter_map { |m| m.tool_call_id if m.is_a?(Riffer::Messages::Tool) }
@@ -62,22 +45,13 @@ module Riffer::Agent::Session::Repair
62
45
  [new_messages, filled]
63
46
  end
64
47
 
65
- # Prunes a seeded message array so the +tool_use+ +tool_result+
66
- # invariant holds. Drops orphaned tool exchanges (assistant +tool_call+
67
- # with no matching Tool result) and parentless Tool messages. Returns a
68
- # new array; the input is not mutated.
69
- #
70
- # Pending tool_calls on the resume boundary — the last assistant whose
71
- # tail is purely Tool results (or empty) — are preserved. They get
72
- # swept up by +execute_pending_tool_calls+ at the start of the next
73
- # generate/stream call.
74
- #
75
- # No-op when +Riffer.config.experimental_history_healing+ is off:
76
- # returns +messages+ unchanged.
77
- #
48
+ # Prunes a seeded message array to the invariant dropping orphaned tool
49
+ # exchanges and parentless Tool messages, but preserving the pending
50
+ # tool_calls on the resume boundary (the last assistant) for
51
+ # +execute_pending_tool_calls+. Returns a new array.
78
52
  #--
79
53
  #: (Array[Riffer::Messages::Base]) -> Array[Riffer::Messages::Base]
80
- def self.prune_orphans(messages)
54
+ def prune_orphans(messages)
81
55
  return messages unless Riffer.config.experimental_history_healing
82
56
 
83
57
  resume_boundary = (messages.length - 1).downto(0).find { |idx|
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Riffer::Agent::Session owns the conversation handle for an agent: the message
5
- # array, the +on_message+ callback list, and the +tool_use+ ↔ +tool_result+
6
- # invariant that keeps tool calls and their results consistent.
7
- #
8
- # Access via +agent.session+. Sessions are constructed by +Riffer::Agent+
9
- # and live for the lifetime of the agent.
4
+ # Owns the conversation handle for an agent: the message array, the
5
+ # +on_message+ callbacks, and the +tool_use+ ↔ +tool_result+ invariant that
6
+ # keeps tool calls and their results consistent.
10
7
  #
11
8
  # agent.session.add(msg) # append + fire callbacks
12
9
  # agent.session.set([msg1, msg2]) # bulk replace (silent)
@@ -31,12 +28,6 @@ class Riffer::Agent::Session
31
28
  end
32
29
 
33
30
  # Registers a callback invoked once per message appended via +#add+.
34
- #
35
- # Callbacks do NOT fire for +#set+, +#unset+, +#remove+, or +#update+.
36
- # Returns +self+ to allow chaining.
37
- #
38
- # Raises Riffer::ArgumentError if no block is given.
39
- #
40
31
  #--
41
32
  #: () { (Riffer::Messages::Base) -> void } -> self
42
33
  def on_message(&block)
@@ -45,13 +36,9 @@ class Riffer::Agent::Session
45
36
  self
46
37
  end
47
38
 
48
- # Appends +message+ and fires every registered callback once with it.
49
- #
50
- # Pass +silent: true+ to skip +on_message+ callbacks used for
51
- # non-inference inputs like user messages, which subscribers don't
52
- # expect to observe through the callback channel. Inference-produced
53
- # messages (Assistant, Tool) always go through +add+ without +silent+.
54
- #
39
+ # Appends +message+ and fires every registered callback once with it. Pass
40
+ # +silent: true+ to skip callbacks — used for non-inference inputs like user
41
+ # messages that subscribers don't expect on the callback channel.
55
42
  #--
56
43
  #: (Riffer::Messages::Base, ?silent: bool) -> Riffer::Messages::Base
57
44
  def add(message, silent: false)
@@ -60,13 +47,7 @@ class Riffer::Agent::Session
60
47
  message
61
48
  end
62
49
 
63
- # Replaces the message history wholesale. Does NOT fire +on_message+
64
- # callbacks; registered callbacks persist across the swap.
65
- #
66
- # Used for seeding, guardrail rewrites, and history healing — cases
67
- # where firing callbacks would double-emit messages that subscribers
68
- # have already observed (or never produced).
69
- #
50
+ # Replaces the message history wholesale
70
51
  #--
71
52
  #: (Array[Riffer::Messages::Base]) -> self
72
53
  def set(messages)
@@ -74,9 +55,7 @@ class Riffer::Agent::Session
74
55
  self
75
56
  end
76
57
 
77
- # Clears the session. Does NOT fire +on_message+ callbacks; registered
78
- # callbacks persist.
79
- #
58
+ # Clears the session.
80
59
  #--
81
60
  #: () -> self
82
61
  def unset
@@ -84,18 +63,10 @@ class Riffer::Agent::Session
84
63
  self
85
64
  end
86
65
 
87
- # Removes a message by id. When the target is an assistant message that
88
- # carries +tool_calls+, every +Riffer::Messages::Tool+ result whose
89
- # +tool_call_id+ matches one of those calls is removed atomically — keeping
90
- # the +tool_use+ +tool_result+ invariant intact.
91
- #
92
- # Raises Riffer::ArgumentError when called on a +Riffer::Messages::Tool+
93
- # message — that would orphan the parent's +tool_use+. Use
94
- # +#update+ to rewrite a tool result instead.
95
- #
96
- # Returns the removed message, or +nil+ when no message has the given id
97
- # (idempotent).
98
- #
66
+ # Removes a message by id, cascading to drop the +Tool+ results of a removed
67
+ # assistant's +tool_calls+ so the +tool_use+ +tool_result+ invariant holds.
68
+ # Raises on a +Tool+ message that would orphan its parent; use +#update+
69
+ # instead. Returns +nil+ if no message matches.
99
70
  #--
100
71
  #: (id: String) -> Riffer::Messages::Base?
101
72
  def remove(id:)
@@ -118,19 +89,10 @@ class Riffer::Agent::Session
118
89
  target
119
90
  end
120
91
 
121
- # Partial in-place update. Looks up a message by either +id:+ or
122
- # +tool_call_id:+ (exactly one required), constructs a replacement of the
123
- # same concrete type with +attrs+ overlaid on the existing fields, and
124
- # swaps it in place.
125
- #
126
- # When the target is an assistant message and the update drops one or more
127
- # entries from +tool_calls+, every +Riffer::Messages::Tool+ result whose
128
- # +tool_call_id+ matches a dropped call is removed atomically — keeping the
129
- # +tool_use+ ↔ +tool_result+ invariant intact.
130
- #
131
- # Raises Riffer::ArgumentError when neither or both lookup keys are
132
- # provided, or when no message matches.
133
- #
92
+ # Partial in-place update: looks up a message by +id:+ or +tool_call_id:+
93
+ # (exactly one), overlays +attrs+ onto a same-type replacement, and swaps it
94
+ # in. Dropping +tool_calls+ from an assistant cascades to remove their +Tool+
95
+ # results, preserving the invariant. Raises on neither/both keys or no match.
134
96
  #--
135
97
  #: (?id: String?, ?tool_call_id: String?, **untyped) -> Riffer::Messages::Base
136
98
  def update(id: nil, tool_call_id: nil, **attrs)
@@ -155,12 +117,9 @@ class Riffer::Agent::Session
155
117
  replacement
156
118
  end
157
119
 
158
- # Returns the call_ids of every +tool_call+ on any assistant message that
159
- # has no matching +Riffer::Messages::Tool+ result anywhere in history.
160
- #
161
- # Zero-cost validation hook for callers that want to check the
162
- # +tool_use+ ↔ +tool_result+ invariant before mutating or persisting.
163
- #
120
+ # Returns the call_ids of every +tool_call+ with no matching
121
+ # +Riffer::Messages::Tool+ result anywhere in history — a hook for checking
122
+ # the +tool_use+ ↔ +tool_result+ invariant before mutating or persisting.
164
123
  #--
165
124
  #: () -> Array[String]
166
125
  def orphaned_tool_call_ids
@@ -171,10 +130,8 @@ class Riffer::Agent::Session
171
130
  }
172
131
  end
173
132
 
174
- # Returns +[assistant, pending_tool_calls]+ for the last assistant message.
175
- # When there is no assistant message or no pending calls, the second
176
- # element is an empty array.
177
- #
133
+ # Returns +[last_assistant, pending_tool_calls]+; the second element is empty
134
+ # when there's no assistant message or no pending calls.
178
135
  #--
179
136
  #: () -> [Riffer::Messages::Assistant?, Array[Riffer::Messages::Assistant::ToolCall]]
180
137
  def pending_tool_calls
@@ -191,6 +148,7 @@ class Riffer::Agent::Session
191
148
  [assistant, assistant.tool_calls.reject { |tc| executed_ids.include?(tc.call_id) }]
192
149
  end
193
150
 
151
+ # Yields each message in order, or returns an Enumerator without a block.
194
152
  #--
195
153
  #: () -> Enumerator[Riffer::Messages::Base, self]
196
154
  #: () { (Riffer::Messages::Base) -> void } -> untyped
@@ -199,10 +157,8 @@ class Riffer::Agent::Session
199
157
  @messages.each(&block)
200
158
  end
201
159
 
202
- # The number of LLM steps completed in this session, derived from the
203
- # count of assistant messages. Used by the agent loop to enforce
160
+ # The number of LLM steps completed, used by the agent loop to enforce
204
161
  # +max_steps+ on resume.
205
- #
206
162
  #--
207
163
  #: () -> Integer
208
164
  def steps
@@ -223,6 +179,7 @@ class Riffer::Agent::Session
223
179
 
224
180
  private
225
181
 
182
+ #--
226
183
  #: (Riffer::Messages::Base, Riffer::Messages::Base) -> void
227
184
  def cascade_dropped_tool_calls(old, replacement)
228
185
  return unless old.is_a?(Riffer::Messages::Assistant)
@@ -234,6 +191,7 @@ class Riffer::Agent::Session
234
191
  @messages.reject! { |m| m.is_a?(Riffer::Messages::Tool) && removed_ids.include?(m.tool_call_id) }
235
192
  end
236
193
 
194
+ #--
237
195
  #: (Riffer::Messages::Base, Hash[Symbol, untyped]) -> Riffer::Messages::Base
238
196
  def rebuild_message(old, attrs)
239
197
  case old
@@ -2,19 +2,11 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  # Wraps the result of structured output parsing and validation.
5
- #
6
- # On success, +object+ contains the validated Hash and +error+ is nil.
7
- # On failure, +error+ contains the error message and +object+ is nil.
8
- #
9
- # result = structured_output.parse_and_validate(json_string)
10
- # if result.success?
11
- # result.object #=> {sentiment: "positive", score: 0.9}
12
- # else
13
- # result.error #=> "JSON parse error: ..."
14
- # end
15
- #
16
5
  class Riffer::Agent::StructuredOutput::Result
6
+ # The validated object, or +nil+ on failure.
17
7
  attr_reader :object #: Hash[Symbol, untyped]?
8
+
9
+ # The error message, or +nil+ on success.
18
10
  attr_reader :error #: String?
19
11
 
20
12
  #--
@@ -3,16 +3,10 @@
3
3
 
4
4
  require "json"
5
5
 
6
- # Riffer::Agent::StructuredOutput provides parse/validate for structured JSON
7
- # responses from LLM providers.
8
- #
9
- # params = Riffer::Params.new
10
- # params.required(:sentiment, String)
11
- # so = Riffer::Agent::StructuredOutput.new(params)
12
- # result = so.parse_and_validate('{"sentiment":"positive","score":0.9}')
13
- # result.object #=> {sentiment: "positive", score: 0.9}
14
- #
6
+ # Parses and validates structured JSON responses against a Riffer::Params
7
+ # schema.
15
8
  class Riffer::Agent::StructuredOutput
9
+ # The schema parameters.
16
10
  attr_reader :params #: Riffer::Params
17
11
 
18
12
  #--
@@ -29,10 +23,8 @@ class Riffer::Agent::StructuredOutput
29
23
  @params.to_json_schema(strict: strict)
30
24
  end
31
25
 
32
- # Parses a JSON string and validates it against the schema.
33
- #
34
- # Returns a Result with the validated object on success, or an error message on failure.
35
- #
26
+ # Parses a JSON string and validates it against the schema, returning a
27
+ # Result carrying either the validated object or an error message.
36
28
  #--
37
29
  #: (String) -> Riffer::Agent::StructuredOutput::Result
38
30
  def parse_and_validate(json_string)