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,23 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
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.
7
- #
8
- # agent.session.add(msg) # append + fire callbacks
9
- # agent.session.set([msg1, msg2]) # bulk replace (silent)
10
- # agent.session.unset # clear (silent)
11
- # agent.session.remove(id: "a_1")
12
- # agent.session.update(id: "a_1", content: "...")
13
- # agent.session.find { |m| m.id == "a_1" }
14
- #
15
4
  class Riffer::Agent::Session
16
5
  include Enumerable #[Riffer::Messages::Base]
17
6
 
18
7
  # @rbs @callbacks: Array[^(Riffer::Messages::Base) -> void]
19
8
 
20
- # The message history.
21
9
  attr_reader :messages #: Array[Riffer::Messages::Base] # @dynamic messages
22
10
 
23
11
  #--
@@ -27,7 +15,6 @@ class Riffer::Agent::Session
27
15
  @callbacks = [] #: Array[^(Riffer::Messages::Base) -> void]
28
16
  end
29
17
 
30
- # Registers a callback invoked once per message appended via +#add+.
31
18
  #--
32
19
  #: () { (Riffer::Messages::Base) -> void } -> self
33
20
  def on_message(&block)
@@ -37,18 +24,15 @@ class Riffer::Agent::Session
37
24
  self
38
25
  end
39
26
 
40
- # Appends +message+ and fires every registered callback once with it. Pass
41
- # +silent: true+ to skip callbacks — used for non-inference inputs like user
42
- # messages that subscribers don't expect on the callback channel.
43
27
  #--
44
28
  #: (Riffer::Messages::Base, ?silent: bool) -> Riffer::Messages::Base
45
29
  def add(message, silent: false)
46
- @messages << message
30
+ @messages.insert(insertion_index(message), message)
31
+ # +silent+ is for non-inference inputs (e.g. user messages) that subscribers don't expect.
47
32
  @callbacks.each { |callback| callback.call(message) } unless silent
48
33
  message
49
34
  end
50
35
 
51
- # Replaces the message history wholesale
52
36
  #--
53
37
  #: (Array[Riffer::Messages::Base]) -> self
54
38
  def set(messages)
@@ -56,7 +40,6 @@ class Riffer::Agent::Session
56
40
  self
57
41
  end
58
42
 
59
- # Clears the session.
60
43
  #--
61
44
  #: () -> self
62
45
  def unset
@@ -64,10 +47,6 @@ class Riffer::Agent::Session
64
47
  self
65
48
  end
66
49
 
67
- # Removes a message by id, cascading to drop the +Tool+ results of a removed
68
- # assistant's +tool_calls+ so the +tool_use+ ↔ +tool_result+ invariant holds.
69
- # Raises on a +Tool+ message — that would orphan its parent; use +#update+
70
- # instead. Returns +nil+ if no message matches.
71
50
  #--
72
51
  #: (id: String) -> Riffer::Messages::Base?
73
52
  def remove(id:)
@@ -90,10 +69,6 @@ class Riffer::Agent::Session
90
69
  target
91
70
  end
92
71
 
93
- # Partial in-place update: looks up a message by +id:+ or +tool_call_id:+
94
- # (exactly one), overlays +attrs+ onto a same-type replacement, and swaps it
95
- # in. Dropping +tool_calls+ from an assistant cascades to remove their +Tool+
96
- # results, preserving the invariant. Raises on neither/both keys or no match.
97
72
  #--
98
73
  #: (?id: String?, ?tool_call_id: String?, **untyped) -> Riffer::Messages::Base
99
74
  def update(id: nil, tool_call_id: nil, **attrs)
@@ -118,9 +93,6 @@ class Riffer::Agent::Session
118
93
  replacement
119
94
  end
120
95
 
121
- # Returns the call_ids of every +tool_call+ with no matching
122
- # +Riffer::Messages::Tool+ result anywhere in history — a hook for checking
123
- # the +tool_use+ ↔ +tool_result+ invariant before mutating or persisting.
124
96
  #--
125
97
  #: () -> Array[String]
126
98
  def orphaned_tool_call_ids
@@ -132,8 +104,6 @@ class Riffer::Agent::Session
132
104
  end
133
105
  end
134
106
 
135
- # Returns +[last_assistant, pending_tool_calls]+; the second element is empty
136
- # when there's no assistant message or no pending calls.
137
107
  #--
138
108
  #: () -> [Riffer::Messages::Assistant?, Array[Riffer::Messages::Assistant::ToolCall]]
139
109
  def pending_tool_calls
@@ -150,7 +120,14 @@ class Riffer::Agent::Session
150
120
  [assistant, assistant.tool_calls.reject { |tc| executed_ids.include?(tc.call_id) }]
151
121
  end
152
122
 
153
- # Yields each message in order, or returns an Enumerator without a block.
123
+ #--
124
+ #: () -> Array[String]
125
+ def discard_pending_tool_calls
126
+ messages, filled = Repair.fill_orphans(@messages)
127
+ set(messages)
128
+ filled
129
+ end
130
+
154
131
  #--
155
132
  #: () -> Enumerator[Riffer::Messages::Base, self]
156
133
  #: () { (Riffer::Messages::Base) -> void } -> untyped
@@ -160,17 +137,12 @@ class Riffer::Agent::Session
160
137
  @messages.each(&block)
161
138
  end
162
139
 
163
- # The number of LLM steps completed, used by the agent loop to enforce
164
- # +max_steps+ on resume.
165
140
  #--
166
141
  #: () -> Integer
167
142
  def steps
168
143
  @messages.count { |m| m.is_a?(Riffer::Messages::Assistant) }
169
144
  end
170
145
 
171
- # The most recent +Riffer::Messages::Assistant+ in the session, or +nil+
172
- # when none exists.
173
- #
174
146
  #--
175
147
  #: () -> Riffer::Messages::Assistant?
176
148
  def final_assistant_message
@@ -180,6 +152,21 @@ class Riffer::Agent::Session
180
152
 
181
153
  private
182
154
 
155
+ # Providers require tool results to directly follow their tool_use, but a
156
+ # resumed turn appends the new user message before pending calls execute.
157
+ #--
158
+ #: (Riffer::Messages::Base) -> Integer
159
+ def insertion_index(message)
160
+ return @messages.length unless message.is_a?(Riffer::Messages::Tool)
161
+
162
+ parent_idx = @messages.rindex do |m|
163
+ m.is_a?(Riffer::Messages::Assistant) && m.tool_calls.any? { |tc| tc.call_id == message.tool_call_id }
164
+ end
165
+ return @messages.length unless parent_idx
166
+
167
+ parent_idx + 1 + @messages.drop(parent_idx + 1).take_while { |m| m.is_a?(Riffer::Messages::Tool) }.length
168
+ end
169
+
183
170
  #--
184
171
  #: (Riffer::Messages::Base, Riffer::Messages::Base) -> void
185
172
  def cascade_dropped_tool_calls(old, replacement)
@@ -1,12 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Wraps the result of structured output parsing and validation.
5
4
  class Riffer::Agent::StructuredOutput::Result
6
- # The validated object, or +nil+ on failure.
7
5
  attr_reader :object #: Hash[Symbol, untyped]? # @dynamic object
8
-
9
- # The error message, or +nil+ on success.
10
6
  attr_reader :error #: String? # @dynamic error
11
7
 
12
8
  #--
@@ -16,14 +12,10 @@ class Riffer::Agent::StructuredOutput::Result
16
12
  @error = error
17
13
  end
18
14
 
19
- # Returns true when parsing and validation succeeded.
20
- #
21
15
  #--
22
16
  #: () -> bool
23
17
  def success? = @error.nil?
24
18
 
25
- # Returns true when parsing or validation failed.
26
- #
27
19
  #--
28
20
  #: () -> bool
29
21
  def failure? = !success?
@@ -3,10 +3,7 @@
3
3
 
4
4
  require "json"
5
5
 
6
- # Parses and validates structured JSON responses against a Riffer::Params
7
- # schema.
8
6
  class Riffer::Agent::StructuredOutput
9
- # The schema parameters.
10
7
  attr_reader :params #: Riffer::Params # @dynamic params
11
8
 
12
9
  #--
@@ -15,16 +12,12 @@ class Riffer::Agent::StructuredOutput
15
12
  @params = params
16
13
  end
17
14
 
18
- # Returns the JSON Schema for this structured output.
19
- #
20
15
  #--
21
16
  #: (?strict: bool) -> Hash[Symbol, untyped]
22
17
  def json_schema(strict: false)
23
18
  @params.to_json_schema(strict: strict)
24
19
  end
25
20
 
26
- # Parses a JSON string and validates it against the schema, returning a
27
- # Result carrying either the validated object or an error message.
28
21
  #--
29
22
  #: (String) -> Riffer::Agent::StructuredOutput::Result
30
23
  def parse_and_validate(json_string)
data/lib/riffer/agent.rb CHANGED
@@ -3,17 +3,6 @@
3
3
 
4
4
  require "json"
5
5
 
6
- # Base class for all agents in the Riffer framework. Subclass it to define an
7
- # agent's model, instructions, tools, and guardrails.
8
- #
9
- # class MyAgent < Riffer::Agent
10
- # model 'openai/gpt-4o'
11
- # instructions 'You are a helpful assistant.'
12
- # end
13
- #
14
- # agent = MyAgent.new
15
- # agent.generate('Hello!')
16
- #
17
6
  class Riffer::Agent
18
7
  extend Riffer::Registrable
19
8
 
@@ -21,66 +10,48 @@ class Riffer::Agent
21
10
 
22
11
  INTERRUPT_MAX_STEPS = :max_steps #: Symbol
23
12
 
24
- # Returns the per-class Riffer::Agent::Config holding every DSL setting.
25
13
  #--
26
14
  #: () -> Riffer::Agent::Config
27
15
  def self.config
28
16
  @config ||= Riffer::Agent::Config.new
29
17
  end
30
18
 
31
- # +identifier+ is cleared because it is configuration here but identity on the
32
- # subclass; two classes claiming one raise Riffer::DuplicateIdentifierError at
33
- # the next registry lookup.
34
19
  #--
35
20
  #: (Class) -> void
36
21
  def self.inherited(subclass)
37
22
  super
38
23
  copy = config.dup
24
+ # The identifier is identity, not inheritable config: two classes claiming
25
+ # one raise Riffer::DuplicateIdentifierError at the next registry lookup.
39
26
  copy.identifier = nil
40
27
  subclass.instance_variable_set(:@config, copy)
41
28
  end
42
29
  private_class_method :inherited
43
30
 
44
- # Gets or sets the agent identifier.
45
- #
46
31
  #--
47
32
  #: (?String?) -> String
48
33
  def self.identifier(value = nil)
49
34
  value.nil? ? (config.identifier || Riffer::Helpers::Identifier.for(self)) : (config.identifier = value)
50
35
  end
51
36
 
52
- # Gets or sets the model string (e.g., "openai/gpt-4o").
53
- #
54
37
  #--
55
38
  #: (?(String | Proc)?) -> (String | Proc)?
56
39
  def self.model(value = nil)
57
40
  value.nil? ? config.model : (config.model = value)
58
41
  end
59
42
 
60
- # Gets or sets the agent instructions. A Proc is called at generate time with
61
- # the +context+ hash (which may be +nil+).
62
- #
63
- # instructions "You are a helpful assistant."
64
- #
65
- # instructions -> (context) {
66
- # "You are assisting #{context[:name]}"
67
- # }
68
- #
69
43
  #--
70
44
  #: (?(String | Proc)?) -> (String | Proc)?
71
45
  def self.instructions(value = nil)
72
46
  value.nil? ? config.instructions : (config.instructions = value)
73
47
  end
74
48
 
75
- # Gets or sets model options passed to generate_text/stream_text.
76
- #
77
49
  #--
78
50
  #: (?Hash[Symbol, untyped]?) -> Hash[Symbol, untyped]
79
51
  def self.model_options(options = nil)
80
52
  options.nil? ? config.model_options : (config.model_options = options)
81
53
  end
82
54
 
83
- # Gets or sets the structured output schema for this agent.
84
55
  #--
85
56
  #: (?Riffer::Params?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
86
57
  def self.structured_output(params = nil, &block)
@@ -92,62 +63,39 @@ class Riffer::Agent
92
63
  config.structured_output
93
64
  end
94
65
 
95
- # Gets or sets the maximum number of LLM call steps in the tool-use loop.
96
- # The splat distinguishes a getter (no argument) from setting the limit to
97
- # +nil+ (unlimited); it defaults to Riffer::Agent::Config::DEFAULT_MAX_STEPS.
98
- #
99
- # max_steps # reads the current limit
100
- # max_steps 8 # cap the loop at 8 steps
101
- # max_steps nil # unlimited
102
- #
103
66
  #--
104
67
  #: (*Numeric?) -> Numeric?
105
68
  def self.max_steps(*value)
69
+ # The splat tells a bare read apart from +max_steps nil+ (unlimited).
106
70
  return config.max_steps if value.empty?
107
71
 
108
72
  config.max_steps = value.first
109
73
  end
110
74
 
111
- # Gets or sets the tools used by this agent.
112
- #
113
75
  #--
114
76
  #: (?(Array[singleton(Riffer::Tool)] | Proc)?) -> (Array[singleton(Riffer::Tool)] | Proc)?
115
77
  def self.uses_tools(value = nil)
116
78
  value.nil? ? config.tools_config : (config.tools_config = value)
117
79
  end
118
80
 
119
- # Opts this agent into MCP tools from registrations matching the given tag.
120
- # Progressive registrations expose +mcp_search+ instead of every schema up front.
121
- #
81
+ # +progressive+ exposes +mcp_search+ instead of every tool schema up front.
82
+ #--
122
83
  #: (String | Symbol, ?progressive: bool) -> void
123
84
  def self.use_mcp(tag, progressive: true)
124
85
  config.add_mcp(tag, progressive: progressive)
125
86
  end
126
87
 
127
- # Returns the accumulated +use_mcp+ configurations for this agent class.
128
- #
129
88
  #: () -> Array[Hash[Symbol, untyped]]
130
89
  def self.mcp_configs
131
90
  config.mcp_configs
132
91
  end
133
92
 
134
- # Gets or sets the tool runtime for this agent; defaults to
135
- # <tt>Riffer.config.tool_runtime</tt> when unset.
136
93
  #--
137
94
  #: (?(singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
138
95
  def self.tool_runtime(value = nil)
139
96
  value.nil? ? config.tool_runtime : (config.tool_runtime = value)
140
97
  end
141
98
 
142
- # Configures skills for this agent via a block DSL, or returns the current
143
- # Riffer::Skills::Config when called without a block.
144
- #
145
- # skills do
146
- # backend Riffer::Skills::FilesystemBackend.new(".skills")
147
- # adapter Riffer::Skills::XmlAdapter
148
- # activate ["code-review"]
149
- # end
150
- #
151
99
  #--
152
100
  #: () ?{ (Riffer::Skills::Config) [self: Riffer::Skills::Config] -> void } -> Riffer::Skills::Config?
153
101
  def self.skills(&block)
@@ -159,21 +107,18 @@ class Riffer::Agent
159
107
  config.skills_config
160
108
  end
161
109
 
162
- # Generates a response using a new agent instance.
163
110
  #--
164
111
  #: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::User::FilePart]?, ?context: Hash[Symbol, untyped]?, ?tags: Hash[(String | Symbol), untyped]) -> Riffer::Agent::Response
165
112
  def self.generate(prompt = nil, files: nil, context: nil, tags: {})
166
113
  new(context: context).generate(prompt, files: files, tags: tags)
167
114
  end
168
115
 
169
- # Streams a response using a new agent instance.
170
116
  #--
171
117
  #: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::User::FilePart]?, ?context: Hash[Symbol, untyped]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base, Riffer::Agent::Response]
172
118
  def self.stream(prompt = nil, files: nil, context: nil, tags: {})
173
119
  new(context: context).stream(prompt, files: files, tags: tags)
174
120
  end
175
121
 
176
- # Reconstructs a runnable agent from a wire hash produced by +#to_h+.
177
122
  #--
178
123
  #: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
179
124
  def self.from_h(
@@ -192,7 +137,6 @@ class Riffer::Agent
192
137
  )
193
138
  end
194
139
 
195
- # Reconstructs a runnable agent from a JSON string produced by +#to_json+.
196
140
  #--
197
141
  #: (String, ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
198
142
  def self.from_json(
@@ -211,70 +155,33 @@ class Riffer::Agent
211
155
  )
212
156
  end
213
157
 
214
- # Registers a guardrail for input, output, or both phases. Raises
215
- # Riffer::ArgumentError unless +phase+ is :before, :after, or :around.
216
158
  #--
217
159
  #: (Symbol, with: singleton(Riffer::Guardrail), **untyped) -> void
218
160
  def self.guardrail(phase, with:, **options)
219
161
  config.add_guardrail(phase, klass: with, options: options)
220
162
  end
221
163
 
222
- # Returns the registered guardrail configs for a given phase.
223
164
  #--
224
165
  #: (Symbol) -> Array[Hash[Symbol, untyped]]
225
166
  def self.guardrails_for(phase)
226
167
  config.guardrails_for(phase)
227
168
  end
228
169
 
229
- # The conversation handle.
230
170
  attr_reader :session #: Riffer::Agent::Session # @dynamic session
231
-
232
- # The per-instance Riffer::Agent::Config.
233
171
  attr_reader :config #: Riffer::Agent::Config # @dynamic config
234
-
235
- # The system message built from the configured +instructions+, or +nil+ when
236
- # none are configured.
237
172
  attr_reader :instruction_message #: Riffer::Messages::System? # @dynamic instruction_message
238
-
239
- # The system message describing the configured skills catalog, or +nil+ when
240
- # skills are unconfigured or the catalog is empty.
241
173
  attr_reader :skills_message #: Riffer::Messages::System? # @dynamic skills_message
242
-
243
- # The mutable runtime context shared with every +Riffer::Agent::Run+ this
244
- # agent executes and threaded through all Proc-based settings.
245
174
  attr_reader :context #: Riffer::Agent::Context # @dynamic context
246
-
247
- # The resolved provider name (the part before "/" in the model string),
248
- # e.g. +"openai"+.
249
175
  attr_reader :provider_name #: String # @dynamic provider_name
250
-
251
- # The resolved model name (the part after "/" in the model string), used as
252
- # the model argument on every LLM call.
253
176
  attr_reader :model_name #: String # @dynamic model_name
254
177
 
255
- # The provider client. Public so tests can pre-queue responses on
256
- # +Riffer::Providers::Mock+ before calling +#generate+.
178
+ # Public so tests can pre-queue responses on Riffer::Providers::Mock.
257
179
  attr_reader :provider #: Riffer::Providers::Base # @dynamic provider
258
180
 
259
- # The +Riffer::Agent::StructuredOutput+ wrapping the configured schema, or
260
- # +nil+ when not configured.
261
181
  attr_reader :structured_output #: Riffer::Agent::StructuredOutput? # @dynamic structured_output
262
-
263
- # The tool classes the LLM sees on every call this agent makes.
264
182
  attr_reader :tools #: Array[singleton(Riffer::Tool)] # @dynamic tools
265
-
266
- # The tool runtime instance used to execute tool calls.
267
183
  attr_reader :tool_runtime #: Riffer::Tools::Runtime # @dynamic tool_runtime
268
184
 
269
- # Initializes a new agent.
270
- #
271
- # A provided +session:+ is used as-is — the caller owns its contents (e.g.
272
- # cross-process resume from persisted history); an omitted one is seeded with
273
- # the instruction and skills messages.
274
- #
275
- # Raises Riffer::ArgumentError unless the configured model string is
276
- # "provider/model" format.
277
- #
278
185
  #--
279
186
  #: (?session: Riffer::Agent::Session?, ?context: Hash[Symbol, untyped]?, ?config: Riffer::Agent::Config?) -> void
280
187
  def initialize(session: nil, context: nil, config: nil)
@@ -293,47 +200,24 @@ class Riffer::Agent
293
200
  @instruction_message = build_instruction_message
294
201
  @skills_message = build_skills_message
295
202
 
203
+ # A caller-supplied session owns its contents (e.g. resumed history), so it
204
+ # is not seeded.
296
205
  @session = session || Riffer::Agent::Session.new(messages: [@instruction_message, @skills_message].compact)
297
206
  @session.set(Riffer::Agent::Session::Repair.prune_orphans(@session.messages))
298
207
  end
299
208
 
300
- # The identifier for this agent: the instance config's identifier when set
301
- # (e.g. an agent restored with +Riffer::Agent.from_h+), otherwise the class
302
- # identifier.
303
- #
304
209
  #--
305
210
  #: () -> String
306
211
  def identifier
307
212
  config.identifier || self.class.identifier
308
213
  end
309
214
 
310
- # Generates a response from the agent.
311
- #
312
- # With +prompt+, a new user message is appended (silently — +on_message+ does
313
- # not fire for user inputs) before the loop runs. Without it, the loop runs
314
- # against the current session, resuming a persisted conversation or pending
315
- # tool calls. +files:+ requires +prompt+.
316
- #
317
- # +tags:+ is an optional flat hash of attribution tags applied to this single
318
- # call: they propagate to the provider's native request-metadata field, and
319
- # are stamped as +riffer.tag.*+ on every span the call emits. See
320
- # +docs/AGENTS.md+ for the per-provider mapping. The reserved key
321
- # +user_id+ also maps to the provider's native user identifier where one
322
- # exists. Riffer adds the default tags +kind+ (+"agent"+) and +agent+ (the
323
- # agent identifier); a caller tag with the same key wins.
324
- #
325
215
  #--
326
216
  #: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::User::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Riffer::Agent::Response
327
217
  def generate(prompt = nil, files: nil, tags: {})
328
218
  Riffer::Agent::Run.generate(agent: self, prompt: prompt, files: files, tags: tags)
329
219
  end
330
220
 
331
- # Streams a response from the agent as an +Enumerator+ of
332
- # +Riffer::StreamEvents+ whose block-form +each+ returns the final
333
- # Riffer::Agent::Response. See +#generate+ for prompt/files/tags semantics.
334
- #
335
- # Raises Riffer::ArgumentError if structured output is configured.
336
- #
337
221
  #--
338
222
  #: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::User::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base, Riffer::Agent::Response]
339
223
  def stream(prompt = nil, files: nil, tags: {})
@@ -345,24 +229,20 @@ class Riffer::Agent
345
229
  Riffer::Agent::Run.stream(agent: self, prompt: prompt, files: files, tags: tags)
346
230
  end
347
231
 
348
- # Interrupts the agent loop from an +on_message+ callback. Equivalent to
349
- # <tt>throw :riffer_interrupt, reason</tt>.
350
232
  #--
351
233
  #: (?(String | Symbol)?) -> void
352
234
  def interrupt!(reason = nil)
353
235
  throw :riffer_interrupt, reason
354
236
  end
355
237
 
356
- # Snapshots this resolved agent into a self-contained, provider-neutral wire
357
- # hash.
358
238
  #--
359
239
  #: () -> Hash[Symbol, untyped]
360
240
  def to_h
361
241
  Riffer::Agent::Serializer.to_h(agent: self)
362
242
  end
363
243
 
364
- # Snapshots this resolved agent into a wire JSON string. The +*+ absorbs the
365
- # JSON generator state argument so <tt>JSON.generate(agent)</tt> works too.
244
+ # The +*+ absorbs JSON's generator-state argument so
245
+ # <tt>JSON.generate(agent)</tt> works.
366
246
  #--
367
247
  #: (*untyped) -> String
368
248
  def to_json(*)
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ class Riffer::Config::AmazonBedrock
5
+ attr_reader :api_token #: String? # @dynamic api_token
6
+
7
+ attr_reader :region #: String? # @dynamic region
8
+
9
+ attr_accessor :client #: untyped # @dynamic client, client=
10
+
11
+ #--
12
+ #: () -> void
13
+ def initialize
14
+ @api_token = nil
15
+ @region = nil
16
+ @client = nil
17
+ end
18
+
19
+ #--
20
+ #: (untyped) -> void
21
+ def api_token=(value)
22
+ @api_token = Riffer::Helpers::Validate.optional_string(value, attribute: "api_token")
23
+ end
24
+
25
+ #--
26
+ #: (untyped) -> void
27
+ def region=(value)
28
+ @region = Riffer::Helpers::Validate.optional_string(value, attribute: "region")
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ class Riffer::Config::Anthropic
5
+ attr_reader :api_key #: String? # @dynamic api_key
6
+
7
+ attr_accessor :client #: untyped # @dynamic client, client=
8
+
9
+ #--
10
+ #: () -> void
11
+ def initialize
12
+ @api_key = nil
13
+ @client = nil
14
+ end
15
+
16
+ #--
17
+ #: (untyped) -> void
18
+ def api_key=(value)
19
+ @api_key = Riffer::Helpers::Validate.optional_string(value, attribute: "api_key")
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ class Riffer::Config::AzureOpenAI
5
+ attr_reader :api_key #: String? # @dynamic api_key
6
+
7
+ attr_reader :endpoint #: String? # @dynamic endpoint
8
+
9
+ attr_accessor :client #: untyped # @dynamic client, client=
10
+
11
+ #--
12
+ #: () -> void
13
+ def initialize
14
+ @api_key = nil
15
+ @endpoint = nil
16
+ @client = nil
17
+ end
18
+
19
+ #--
20
+ #: (untyped) -> void
21
+ def api_key=(value)
22
+ @api_key = Riffer::Helpers::Validate.optional_string(value, attribute: "api_key")
23
+ end
24
+
25
+ #--
26
+ #: (untyped) -> void
27
+ def endpoint=(value)
28
+ @endpoint = Riffer::Helpers::Validate.optional_string(value, attribute: "endpoint")
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ class Riffer::Config::Evals
5
+ attr_reader :judge_model #: String? # @dynamic judge_model
6
+
7
+ #--
8
+ #: () -> void
9
+ def initialize
10
+ @judge_model = nil
11
+ end
12
+
13
+ #--
14
+ #: (untyped) -> void
15
+ def judge_model=(value)
16
+ @judge_model = value.nil? ? nil : Riffer::Helpers::Validate.model_id(value, attribute: "judge_model")
17
+ end
18
+ end