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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5975a19de149eced0122ccf94f08666f66377c395b3a414db48158fcba352807
4
- data.tar.gz: fe48ac55158f5bd1f4818651988b68ed2e45fa985c1ebe5a386d636decd15ff2
3
+ metadata.gz: b1bd95697d75079325d4a057c0f2bb17bc8fee9a3864c611967e78d8e1637ef8
4
+ data.tar.gz: 84ddb8c10cef0b5711b00793b3a19de2713fcf8d7985166dfaa248fa7bbe58f0
5
5
  SHA512:
6
- metadata.gz: 7e506427c0b0272fb2c6f044def74b74d423e92bb23fbe95e02c5678d48b8b98ff2e8920807b23e4d8fb2db94d509937643813d9248eb109a51d03559a5ecd48
7
- data.tar.gz: 3553da8e73fa137a4070baac5548fbd3a345316f0941e9f8b8490d2790d17a5e47b5eb154de6613897874a0a2bd3adaff4b1167d55fe21b99a1d0840d9786733
6
+ metadata.gz: 375f9c10e95e1dbd60da4acd0209bdc5707a30dff00e742815fb874cb00755b187602967433d5677444e790b9d94153c1408e9bc8f91a80e5879aa696de0a550
7
+ data.tar.gz: a05871a1bcd23100fa5d0018438810476560c19f87e82ebaaa6b7322e81b03577f12923845716792deb479c86f59a49127e65b2f862f4a84b95e11d75a6cb942
@@ -6,8 +6,6 @@ paths: ["**/*.rb", "**/*.rake", "**/Gemfile"]
6
6
 
7
7
  A comment exists to explain a **why** when the code itself cannot — never a **how**, and never a restatement of what the code already says. This bar governs all prose, from inline comments to docstrings.
8
8
 
9
- - **Internal and private code** — everything in an application, plus a library's non-exported internals — is self-documenting via clear names and strong types. A comment survives only when it explains something a competent reader cannot recover from the code alone: a non-local constraint, an external-system quirk, a deliberate non-obvious tradeoff. A description of _what_ the code does, or a why that's evident from reading it, gets cut.
10
- - **A published library's public surface** gets one verb-first sentence per exported symbol ("Serializes the definition to JSON."). An optional second sentence is reserved strictly for a why — a non-obvious constraint or rationale — never a second sentence of how. Needing more than one sentence to say _what_ it does is a smell the symbol does too much.
11
- - **Types are not prose's job.** Parameters, return values, and field types live in the type system (TypeScript types, rbs-inline `#:` annotations) — never restated in comments that duplicate them.
12
- - **Markers.** `TODO` / `FIXME` / `HACK` are tracked work and stay; `NOTE` / `REVIEW` meet the same why-bar as any other comment.
9
+ - **The default is no comment.** Names, types, and structure carry the meaning; when they don't, fix them rather than explain them. Delete the comment and read the code cold: if the intent is still recoverable, it stays deleted. Keep only a _why_ the code can't show — a non-local constraint, an external quirk, a deliberate tradeoff, a safety invariant — and put it at the line it explains, not in a header.
10
+
13
11
  - **No history.** A comment describes the present, never how the code got there — no "was X, now Y", no story of the bug that revealed a constraint. State a still-true constraint in the present tense ("the API returns null for empty results — guard").
@@ -26,28 +26,18 @@ module Riffer
26
26
  end
27
27
  ```
28
28
 
29
- ## RDoc Conventions
29
+ ## The `#--` stop directive
30
30
 
31
- **The `#--` stop directive.** Place `#--` on the line immediately before a **standalone** `#:` type annotation. Without it, RDoc treats `#:` as a label-list marker and corrupts the preceding description into a `<pre>` block. Inline `#:` on the same line as code (attributes, constants) does not need it.
31
+ Place `#--` on the line immediately before a **standalone** `#:` type annotation, even when no comment precedes it. Without it, RDoc treats `#:` as a label-list marker and mangles the method's rendered docs. Inline `#:` on the same line as code (attributes, constants) does not need it.
32
32
 
33
33
  ```ruby
34
- # Serializes the agent definition to a transferable JSON payload.
35
34
  #--
36
35
  #: (Riffer::Agent) -> String
37
36
  def serialize(agent)
38
37
 
39
- # The agent's display name.
40
38
  attr_reader :name #: String
41
39
  ```
42
40
 
43
- **Raises.** Document a raise **only when it's part of the caller's contract** — something a caller should reasonably anticipate and handle. Skip programmer-error guards and "should never happen" assertions. When the raise condition merely restates the declared `#:` type, phrase it by intent ("Raises Riffer::ArgumentError on an invalid value") rather than re-listing the type union.
44
-
45
- **Examples.** Include an example only when a **consumer is likely to use the thing themselves** — a public entry point they construct, subclass, or call. Keep them sparing and write them as indented code blocks (2 extra spaces of indent). Usage walkthroughs belong in `docs/`.
46
-
47
- **Inline code formatting.** Use `+word+` for single-word inline code; for multi-word expressions (spaces, colons, brackets) use `<tt>multi word expression</tt>`.
48
-
49
- **Internal APIs.** Mark with `# :nodoc:` to exclude from generated documentation.
50
-
51
41
  ## Optional-dependency types (consumer-safe signatures)
52
42
 
53
43
  `sig/generated/` ships with the gem and is loaded by downstream projects (`rbs collection` / `rbs -r riffer`). rbs-inline copies a method's `#:` signature **verbatim** into the shipped sig, so **never name an optional-dependency type in a `#:` signature** — `OpenAI::*`, `Anthropic::*`, `Aws::*`, `MCP::*`, `Async::*`, `Zeitwerk::*`, etc. A consumer who installs riffer without that gem would hit `Cannot find type`, because those providers are pluggable and the gems ship no usable RBS of their own.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.48.0"
2
+ ".": "0.49.0"
3
3
  }
data/.rubocop.yml CHANGED
@@ -110,3 +110,8 @@ Performance/Sum:
110
110
  - lib/riffer/providers/base.rb
111
111
  - lib/riffer/evals/run_result.rb
112
112
  - lib/riffer/evals/scenario_result.rb
113
+
114
+ # Each accessor carries its own trailing rbs-inline `#:` type; grouping them
115
+ # onto one line drops every type but the first.
116
+ Style/AccessorGrouping:
117
+ EnforcedStyle: separated
data/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.49.0](https://github.com/janeapp/riffer/compare/riffer/v0.48.0...riffer/v0.49.0) (2026-09-27)
9
+
10
+
11
+ ### ⚠ BREAKING CHANGES
12
+
13
+ * **config:** provider string settings must be a String or nil; evals.judge_model must be in "provider/model" form; mcp.credentials must respond to #call; mcp.discovery_runner must be a Riffer::Runner. Struct behaviour ([] / to_a / members / value equality) is gone from those sections. Riffer::Tracing.reset! is removed; the tracing backend is read from config on every call instead of being cached.
14
+ * **agent:** Riffer.config.experimental_history_healing is removed, as are Response#healed_tool_call_ids and StreamEvents::Interrupt#healed_tool_call_ids (and its to_h key). Orphan pruning on Agent.new is now always on. Interrupts never fill placeholders; call agent.session.discard_pending_tool_calls instead and use its return value for the filled call ids.
15
+
16
+ ### Features
17
+
18
+ * **agent:** always repair seeded sessions, add discard_pending_tool_calls ([#456](https://github.com/janeapp/riffer/issues/456)) ([b1e8933](https://github.com/janeapp/riffer/commit/b1e89330f2b2f59b74f794d5a21d31b833cb64d8))
19
+ * **amazon_bedrock:** capture and replay reasoning content ([#459](https://github.com/janeapp/riffer/issues/459)) ([5048a8c](https://github.com/janeapp/riffer/commit/5048a8ce2aba35ad7af2eeee018dc381ca77eb00))
20
+ * **open_router:** capture and replay reasoning_details ([#453](https://github.com/janeapp/riffer/issues/453)) ([a3e2523](https://github.com/janeapp/riffer/commit/a3e252324de97d3f03942c60a892126975bb1a60))
21
+
22
+
23
+ ### Code Refactoring
24
+
25
+ * **config:** split Riffer::Config into one file per section ([#458](https://github.com/janeapp/riffer/issues/458)) ([c493684](https://github.com/janeapp/riffer/commit/c4936848c5c34903c160f90786e7493831ab21dd))
26
+
8
27
  ## [0.48.0](https://github.com/janeapp/riffer/compare/riffer/v0.47.2...riffer/v0.48.0) (2026-09-23)
9
28
 
10
29
 
data/docs/AGENTS.md CHANGED
@@ -251,9 +251,7 @@ A `Hash` param requires a block, and an `Array` param requires a block or `of:`.
251
251
 
252
252
  Structured output is not compatible with streaming — calling `stream` on an agent with structured output configured raises `Riffer::ArgumentError`.
253
253
 
254
- ### tool_runtime (Experimental)
255
-
256
- > **Warning:** This feature is experimental and may be removed or changed without warning in a future release.
254
+ ### tool_runtime
257
255
 
258
256
  Configures how tool calls are executed. Defaults to sequential (inline) execution:
259
257
 
@@ -265,7 +263,7 @@ class MyAgent < Riffer::Agent
265
263
  end
266
264
  ```
267
265
 
268
- Accepts a `Riffer::Tools::Runtime` subclass, a `Riffer::Tools::Runtime` instance, or a `Proc`. When unset, reads `Riffer.config.tool_runtime` at the point of use, so an agent that declares none follows a later change to the global. See [Tools — Tool Runtime](TOOL_ADVANCED.md#tool-runtime-experimental) for details.
266
+ Accepts a `Riffer::Tools::Runtime` subclass, a `Riffer::Tools::Runtime` instance, or a `Proc`. When unset, reads `Riffer.config.tool_runtime` at the point of use, so an agent that declares none follows a later change to the global. See [Tools — Tool Runtime](TOOL_ADVANCED.md#tool-runtime) for details.
269
267
 
270
268
  ### guardrail
271
269
 
@@ -446,5 +444,5 @@ Riffer does not validate tag count, key/value length, or charset — it forwards
446
444
  | Add packaged capabilities | Skills | [Skills](SKILLS.md) |
447
445
  | Control the tool-use loop | Agent Loop | [Agent Loop](AGENT_LOOP.md) |
448
446
  | Human-in-the-loop approval | Interrupts | [Agent Lifecycle](AGENT_LIFECYCLE.md#interrupting-the-agent-loop) |
449
- | Run tools concurrently | Tool Runtime | [Advanced Tools](TOOL_ADVANCED.md#tool-runtime-experimental) |
447
+ | Run tools concurrently | Tool Runtime | [Advanced Tools](TOOL_ADVANCED.md#tool-runtime) |
450
448
  | Stream responses in real time | Streaming | [Agent Lifecycle](AGENT_LIFECYCLE.md#stream) |
@@ -8,7 +8,7 @@
8
8
  Agent.new(session: nil, context: nil)
9
9
  ```
10
10
 
11
- - **`session:`** — an existing `Riffer::Agent::Session`. When given, the agent uses it as-is (no system/skills seeding). Typical use case: cross-process resume from persisted history. With `Riffer.config.experimental_history_healing` on, a provided session is healed at construction time so the `tool_use` ↔ `tool_result` invariant holds before the next inference call.
11
+ - **`session:`** — an existing `Riffer::Agent::Session`. When given, the agent uses it as-is (no system/skills seeding). Typical use case: cross-process resume from persisted history. A provided session is repaired at construction time so the `tool_use` ↔ `tool_result` invariant holds before the next inference call: orphaned `tool_use` exchanges (an assistant `tool_call` with no matching `Tool` result) and parentless `Tool` messages are dropped. Pending tool calls on the **resume boundary** — the last assistant whose tail is purely `Tool` results (or none) — are preserved so `generate`/`stream` can execute them.
12
12
  - **`context:`** — a `Hash` carried for the lifetime of the agent. Used to evaluate Proc-based `instructions`, `model`, `uses_tools`, and skill activation at construction time, and threaded through tool execution and guardrails on every `generate`/`stream` call.
13
13
 
14
14
  When `session:` is omitted, the agent constructs a fresh session and seeds it with `[instruction_message, skills_message].compact` eagerly. To swap context, construct a new agent — context is fixed for the lifetime of an agent instance.
@@ -187,7 +187,7 @@ end
187
187
 
188
188
  There are two ways to resume after an interrupt, depending on whether the agent is still in memory or you're restoring from persisted data.
189
189
 
190
- **In-memory resume** — call `generate` (or `stream`) again. With a prompt, the new user message is appended and the loop runs. Without a prompt, the loop runs against the current session — useful for picking up pending tool calls after the user has approved.
190
+ **In-memory resume** — call `generate` (or `stream`) again. With a prompt, the new user message is appended and the loop runs; results for any pending tool calls are placed ahead of it, directly after the assistant message that requested them. Without a prompt, the loop runs against the current session — useful for picking up pending tool calls after the user has approved.
191
191
 
192
192
  ```ruby
193
193
  agent = MyAgent.new(context: {user_id: 123})
@@ -244,38 +244,36 @@ agent.session.on_message do |msg|
244
244
  end
245
245
  ```
246
246
 
247
- #### Healing pending tool results on interrupt (experimental)
247
+ #### Discarding pending tool calls after an interrupt
248
248
 
249
- When an interrupt fires while the assistant has a `tool_use` block that hasn't been answered yet, the LLM will reject the next request unless every `tool_use` has a matching `tool_result`. By default, the next `generate` call re-executes those pending tools (see "Resuming an Interrupted Loop" above).
249
+ An interrupt only stops the loop. Any `tool_use` the assistant emitted that hasn't been answered yet stays in history, and the next `generate`/`stream` call executes it (see "Resuming an Interrupted Loop" above). This applies to caller-issued `interrupt!` and the built-in `INTERRUPT_MAX_STEPS` ceiling alike.
250
250
 
251
- When the interrupt represents a course-change rather than a pause — e.g. a voice barge-in where the user has moved on — re-execution is the wrong behavior. Opt into history healing to have riffer fill any orphan `tool_use` with a placeholder `Riffer::Messages::Tool` carrying `error_type: :interrupted`, leaving history valid for the next turn:
251
+ When the interrupt represents a course-change rather than a pause — e.g. a voice barge-in or a cancel where the user has moved on — re-execution is the wrong behavior. Call `agent.session.discard_pending_tool_calls` to answer every unanswered `tool_use` with a placeholder `Riffer::Messages::Tool` carrying `error_type: :interrupted`, leaving history valid for the next turn. It returns the filled `call_id`s:
252
252
 
253
253
  ```ruby
254
- Riffer.configure { |c| c.experimental_history_healing = true }
255
-
256
254
  agent.session.on_message do |msg|
257
255
  agent.interrupt!(:user_interrupt) if msg.is_a?(Riffer::Messages::Assistant) && barge_in?
258
256
  end
259
257
 
260
258
  response = agent.generate("Tell me a story")
261
- response.healed_tool_call_ids # => ["call_abc123", ...]
259
+ agent.session.discard_pending_tool_calls # => ["call_abc123", ...]
260
+ agent.generate("Actually, tell me a joke")
262
261
  ```
263
262
 
264
- The placeholder content is fixed: `"Tool call interrupted before completion."` with `error_type: :interrupted`. Each placeholder is inserted immediately after its parent assistant message. The list of filled `call_id`s is exposed on `response.healed_tool_call_ids` (and on `Riffer::StreamEvents::Interrupt#healed_tool_call_ids` when streaming).
263
+ The placeholder content is fixed: `"Tool call interrupted before completion."` with `error_type: :interrupted`. Each placeholder is inserted immediately after its parent assistant message. When nothing is pending, the method returns `[]` and leaves history untouched.
265
264
 
266
- Healing covers all interrupts uniformly — caller-issued `interrupt!` and the built-in `INTERRUPT_MAX_STEPS` ceiling alike. When the flag is off (the default), orphans remain in history and `execute_pending_tool_calls` re-runs them on the next `generate` call.
265
+ The method works purely on the session's current messages, so it also applies when a run was cancelled without going through riffer's interrupt handling (e.g. the surrounding task was stopped with `Async::Stop` or an exception escaped a callback).
267
266
 
268
- If you need finer control over placeholder content (per-call shape, structured metadata, etc.), use the `update` mutator below to upgrade a placeholder after the interrupt returns.
267
+ If you need finer control over placeholder content (per-call shape, structured metadata, etc.), use the `update` mutator below to upgrade a placeholder afterwards.
269
268
 
270
269
  ### Mutating history
271
270
 
272
271
  The session exposes a small set of in-place mutators that enforce the `tool_use` ↔ `tool_result` invariant on every operation. Use these to align history with external state (persisted transcript, partial output that wasn't actually delivered, etc.) without rebuilding the agent.
273
272
 
274
273
  - **`agent.session.update(id:, **attrs)`** — In-place partial update. Looks up by message `id:`; builds a replacement of the same type with `attrs` overlaid on the existing fields. Use this to edit assistant content (`update(id:, content:)`), restate a system message, etc. When the target is an assistant and the update drops entries from `tool_calls`, matching `Tool` children are removed atomically.
275
- - **`agent.session.update(tool_call_id:, **attrs)`** — Same as above but looks up the tool result by `tool_call_id:`. Preserves `name` and `id`. Use this to upgrade an interrupt-time placeholder once the real result is available (`update(tool_call_id:, content:, error: nil, error_type: nil)`).
274
+ - **`agent.session.update(tool_call_id:, **attrs)`** — Same as above but looks up the tool result by `tool_call_id:`. Preserves `name` and `id`. Use this to upgrade a `discard_pending_tool_calls` placeholder once the real result is available (`update(tool_call_id:, content:, error: nil, error_type: nil)`).
276
275
  - **`agent.session.remove(id:)`** — Removes a message; cascades to its `Tool` children when the target carries `tool_calls`. Raises if called on a `Tool` message (use `update(tool_call_id:, ...)` to rewrite a tool result instead).
277
-
278
- Bulk filling of orphan `tool_use` blocks is handled by `Riffer.config.experimental_history_healing` (see "Healing pending tool results on interrupt" above) — there is no public synthesizer hook.
276
+ - **`agent.session.discard_pending_tool_calls`** — Answers every unanswered `tool_use` with an `:interrupted` placeholder result and returns the filled `call_id`s (see "Discarding pending tool calls after an interrupt" above).
279
277
 
280
278
  Lookup patterns that pair with the mutators (via `Enumerable`):
281
279
 
@@ -288,7 +286,7 @@ agent.session.orphaned_tool_call_ids
288
286
 
289
287
  Mutating history while a `stream` enumerator is being consumed is undefined; mutators are intended for use between turns.
290
288
 
291
- Mutators do **not** fire `on_message` — that callback is reserved for messages produced by inference (LLM responses, tool execution results). Healing placeholders bypass `on_message` for the same reason; consumers learn that healing happened via `Response#healed_tool_call_ids` (and `StreamEvents::Interrupt#healed_tool_call_ids`).
289
+ Mutators do **not** fire `on_message` — that callback is reserved for messages produced by inference (LLM responses, tool execution results). Placeholders added by `discard_pending_tool_calls` bypass `on_message` for the same reason; use its return value to learn which calls were filled.
292
290
 
293
291
  ### context
294
292
 
@@ -320,7 +318,6 @@ agent.context[:skills] # the Skills::Context, if skills configured
320
318
  | `modified?` | `Boolean` | `true` if a guardrail modified the content |
321
319
  | `modifications` | `Array` | List of guardrail modifications applied |
322
320
  | `messages` | `Array` | Full message history from the conversation |
323
- | `healed_tool_call_ids` | `Array[String]` | `tool_call` ids filled with placeholder results during interrupt healing (else `[]`) |
324
321
  | `token_usage` | `TokenUsage` / `nil` | Aggregate `Riffer::Providers::TokenUsage` across this run's LLM calls (`nil` when none reported) |
325
322
  | `steps` | `Integer` | LLM calls made during this run (`0` when a before-guardrail blocks first); not the session's cumulative count |
326
323
 
@@ -18,6 +18,8 @@ end
18
18
 
19
19
  Providers take no constructor arguments — these settings are the only way to give a provider its credentials.
20
20
 
21
+ Credential and endpoint settings — `amazon_bedrock.api_token` / `.region`, `anthropic.api_key`, `azure_openai.api_key` / `.endpoint`, `gemini.api_key`, `openai.api_key` / `.base_url`, `openrouter.api_key` — accept a `String` or `nil` and raise `Riffer::ArgumentError` naming the setting for anything else. An empty string is accepted; Amazon Bedrock treats it as unset. `client` is not validated (see [Provider Clients](#provider-clients)).
22
+
21
23
  ## Accessing Configuration
22
24
 
23
25
  Access the current configuration via `Riffer.config`:
@@ -101,10 +103,10 @@ The Gemini provider has no vendor SDK, so riffer ships its own transport: `Riffe
101
103
 
102
104
  Optional settings for [MCP server integrations](MCP.md):
103
105
 
104
- | Option | Description |
105
- | ------------------ | --------------------------------------------------------------------------------------------------------------- |
106
- | `credentials` | Optional `Proc` for per-run `tools/call` HTTP headers: `->(manifest:, matched_tags:, context:) { Hash or nil }` |
107
- | `discovery_runner` | `Riffer::Runner` instance for tool discovery (default `Runner::Sequential.new`) |
106
+ | Option | Description |
107
+ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
108
+ | `credentials` | Optional callable for per-run `tools/call` HTTP headers: `->(manifest:, matched_tags:, context:) { Hash or nil }`. Raises `Riffer::ArgumentError` unless `nil` or it responds to `#call`. |
109
+ | `discovery_runner` | `Riffer::Runner` instance for tool discovery (default `Runner::Sequential.new`). Raises `Riffer::ArgumentError` for anything that isn't a `Riffer::Runner` instance. |
108
110
 
109
111
  ```ruby
110
112
  Riffer.configure do |config|
@@ -116,9 +118,17 @@ end
116
118
 
117
119
  See [MCP](MCP.md) for registration, tags, and agent `use_mcp`.
118
120
 
119
- ### Tool Runtime (Experimental)
121
+ ### Evals
122
+
123
+ ```ruby
124
+ Riffer.configure do |config|
125
+ config.evals.judge_model = "anthropic/claude-opus-4-5-20251101"
126
+ end
127
+ ```
128
+
129
+ `judge_model` is the default model evaluators use as the judge when they don't set their own. It must be a `provider/model` string or `nil` (the default); anything else raises `Riffer::ArgumentError`. See [Evals](EVALS.md).
120
130
 
121
- > **Warning:** This feature is experimental and may be removed or changed without warning in a future release.
131
+ ### Tool Runtime
122
132
 
123
133
  Configure the default tool runtime for all agents:
124
134
 
@@ -134,7 +144,7 @@ end
134
144
  | `Riffer::Tools::Runtime` instance | Custom runtime with specific options |
135
145
  | `Proc` | Dynamic resolution |
136
146
 
137
- Per-agent configuration overrides this global default. See [Advanced Tool Configuration — Tool Runtime](TOOL_ADVANCED.md#tool-runtime-experimental) for details.
147
+ Per-agent configuration overrides this global default. See [Advanced Tool Configuration — Tool Runtime](TOOL_ADVANCED.md#tool-runtime) for details.
138
148
 
139
149
  ### Skills
140
150
 
@@ -176,11 +186,11 @@ Riffer.configure do |config|
176
186
  end
177
187
  ```
178
188
 
179
- | Option | Description |
180
- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
181
- | `enabled` | The kill switch, consulted on every span — flipping it at runtime takes effect immediately, short-circuiting to a no-op ahead of the backend. Accepts booleans or `'true'`/`'false'`/`'1'`/`'0'`. Defaults to `true`. |
182
- | `capture_messages` | Opt-in capture of full message content on LLM-call spans (`gen_ai.input.messages`, `gen_ai.output.messages`, `gen_ai.system_instructions`) as GenAI-semconv JSON. Defaults to `false` — message content routinely carries sensitive data. File attachments serialize as metadata-only stubs (media type and name, never bytes), and riffer applies no size limit of its own — cap oversized attributes with the OTEL SDK attribute length limits. |
183
- | `backend` | The backend riffer routes spans through. Assign `Riffer::Tracing::Otel.build` (pass `provider:` to override the global tracer provider — e.g. an in-memory provider in tests), or any object satisfying the duck-typed contract (`in_span` / `current_context` / `with_context`) to route into a non-OTEL system (e.g. Datadog APM). Defaults to `nil` — a no-op. Raises `Riffer::ArgumentError` unless the value is `nil` or responds to `in_span`. See [Tracing → Routing to a non-OpenTelemetry backend](TRACING.md#routing-to-a-non-opentelemetry-backend). |
189
+ | Option | Description |
190
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
191
+ | `enabled` | The kill switch, consulted on every span — flipping it at runtime takes effect immediately, short-circuiting to a no-op ahead of the backend. Accepts booleans or `'true'`/`'false'`/`'1'`/`'0'`. Defaults to `true`. |
192
+ | `capture_messages` | Opt-in capture of full message content on LLM-call spans (`gen_ai.input.messages`, `gen_ai.output.messages`, `gen_ai.system_instructions`) as GenAI-semconv JSON. Defaults to `false` — message content routinely carries sensitive data. File attachments serialize as metadata-only stubs (media type and name, never bytes), and riffer applies no size limit of its own — cap oversized attributes with the OTEL SDK attribute length limits. |
193
+ | `backend` | The backend riffer routes spans through. Assign `Riffer::Tracing::Otel.build` (pass `provider:` to override the global tracer provider — e.g. an in-memory provider in tests), or any object satisfying the duck-typed contract (`in_span` / `current_context` / `with_context`) to route into a non-OTEL system (e.g. Datadog APM). Defaults to `nil` — a no-op. Raises `Riffer::ArgumentError` unless the value is `nil` or responds to all of `in_span`, `current_context`, and `with_context`. See [Tracing → Routing to a non-OpenTelemetry backend](TRACING.md#routing-to-a-non-opentelemetry-backend). |
184
194
 
185
195
  ### File Downloads
186
196
 
@@ -270,27 +280,6 @@ When constructing a `Riffer::Agent::Session` from persisted history with the str
270
280
 
271
281
  See [Messages — IDs](MESSAGES.md#ids) for more details.
272
282
 
273
- ### Experimental: History Healing
274
-
275
- > **Warning:** This feature is experimental and may change without notice.
276
-
277
- Opts the agent into keeping the `tool_use` ↔ `tool_result` invariant intact on its own:
278
-
279
- ```ruby
280
- Riffer.configure do |config|
281
- config.experimental_history_healing = true
282
- end
283
- ```
284
-
285
- When enabled, two repairs run automatically:
286
-
287
- 1. **Seeded session.** Passing a pre-populated `Riffer::Agent::Session` to `Agent.new(session: ...)` silently drops orphaned `tool_use` exchanges (assistant `tool_call` with no matching `Tool` result) and parentless `Tool` messages before the next inference call. Pending tool calls on the **resume boundary** — the last assistant whose tail is purely `Tool` results (or none) — are preserved; `execute_pending_tool_calls` runs them on the next LLM call.
288
- 2. **Interrupts.** Any orphan `tool_use` left when the loop is interrupted (caller-issued `interrupt!` or the built-in `INTERRUPT_MAX_STEPS` ceiling) is filled with a placeholder `Riffer::Messages::Tool` carrying `error_type: :interrupted` and the content `"Tool call interrupted before completion."`. Filled `call_id`s are exposed on `Riffer::Agent::Response#healed_tool_call_ids` (and `Riffer::StreamEvents::Interrupt#healed_tool_call_ids` when streaming).
289
-
290
- Defaults to `false` — pre-healing behavior. Seeded sessions pass through untouched, and orphan `tool_use` left by an interrupt remain in history for `execute_pending_tool_calls` to re-run on the next call.
291
-
292
- There is no per-call override and no customizable placeholder. Callers needing finer control can call `agent.session.update(tool_call_id:, ...)` after the interrupt returns to upgrade a placeholder in place. See [Agent Lifecycle — Healing pending tool results on interrupt](AGENT_LIFECYCLE.md#healing-pending-tool-results-on-interrupt-experimental).
293
-
294
283
  ## Agent-Level Configuration
295
284
 
296
285
  Override global configuration at the agent level:
@@ -112,9 +112,7 @@ For expected failures, return `error(...)` or raise `Riffer::ToolExecutionError`
112
112
 
113
113
  The LLM receives the error message and can decide how to respond (retry, apologize, ask for different input, etc.).
114
114
 
115
- ## Tool Runtime (Experimental)
116
-
117
- > **Warning:** This feature is experimental and may be removed or changed without warning in a future release.
115
+ ## Tool Runtime
118
116
 
119
117
  By default, tool calls are executed sequentially in the current thread using `Riffer::Tools::Runtime::Inline`. You can change how tool calls are executed by configuring a different tool runtime.
120
118
 
data/docs/TRACING.md CHANGED
@@ -58,7 +58,7 @@ Riffer.configure do |config|
58
58
  end
59
59
  ```
60
60
 
61
- The backend is duck-typed — any object satisfying the contract works, and the setter validates only that it responds to `in_span` (otherwise it raises `Riffer::ArgumentError`). It must respond to:
61
+ The backend is duck-typed — any object satisfying the contract works, and the setter validates that it responds to `in_span`, `current_context`, and `with_context` (otherwise it raises `Riffer::ArgumentError`). It must respond to:
62
62
 
63
63
  - `in_span(name, attributes:, kind:) { |span| … }` — open a span around the block, yield a span object, and return the block's value.
64
64
  - `current_context` — return the active trace context (for re-attaching across fiber/thread boundaries), or `nil` when there is none.
@@ -166,6 +166,37 @@ class AWSAgent < Riffer::Agent
166
166
  end
167
167
  ```
168
168
 
169
+ ## Reasoning Models
170
+
171
+ Claude models on Bedrock can think before they answer. Enable extended thinking through `additional_model_request_fields`:
172
+
173
+ ```ruby
174
+ class ThinkAgent < Riffer::Agent
175
+ model 'amazon_bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0'
176
+ model_options inference_config: {max_tokens: 2048},
177
+ additional_model_request_fields: {thinking: {type: "enabled", budget_tokens: 1024}}
178
+ end
179
+
180
+ ThinkAgent.new.stream('What is 2+2? Think step by step.').each do |event|
181
+ case event
182
+ when Riffer::StreamEvents::ReasoningDelta
183
+ print "[reasoning] #{event.content}"
184
+ when Riffer::StreamEvents::TextDelta
185
+ print event.content
186
+ end
187
+ end
188
+ ```
189
+
190
+ The reasoning is kept on the assistant message as [reasoning parts](../MESSAGES.md#reasoning), whether you call `generate` or `stream`. Read it with `response.reasoning`, or as plain text with `reasoning_text` on the message. Bedrock sometimes redacts part of Claude's reasoning. Those parts are stored and sent back like any other, but they have no readable text, so `reasoning_text` leaves them out.
191
+
192
+ ### Reasoning Replay
193
+
194
+ Riffer sends the reasoning back to Bedrock on every later turn. How much of it Claude uses depends on the model; some only use the reasoning from the current tool-calling turn. For tool calls, sending it back is required: when thinking is enabled, Claude needs its earlier reasoning back to carry on after a tool result. You don't have to do anything; it happens as long as the assistant messages stay in the history.
195
+
196
+ If you persist sessions, keep the `reasoning` key when you store messages (see [Messages — Reasoning](../MESSAGES.md#reasoning)). If you drop it, later turns lose the model's earlier reasoning, and a tool-calling turn with thinking enabled may be rejected.
197
+
198
+ Only reasoning that Bedrock produced is sent back to Bedrock. A conversation that switches providers midway still works: reasoning from other providers is kept on the messages but left out of Bedrock requests.
199
+
169
200
  ## File Support
170
201
 
171
202
  Bedrock accepts file attachments either as raw bytes, or as `s3://` URIs passed straight through to Converse — Bedrock fetches the S3 object itself:
@@ -173,7 +173,7 @@ end
173
173
 
174
174
  ## Reasoning Models
175
175
 
176
- Reasoning models surface their thought process via OpenRouter's normalised `reasoning` field. Enable it with the `reasoning` option:
176
+ Reasoning models surface their thought process via OpenRouter's normalised `reasoning_details` field. Enable it with the `reasoning` option:
177
177
 
178
178
  ```ruby
179
179
  class ThinkAgent < Riffer::Agent
@@ -191,6 +191,23 @@ ThinkAgent.new.stream('What is 2+2? Think step by step.').each do |event|
191
191
  end
192
192
  ```
193
193
 
194
+ ### Reasoning Replay
195
+
196
+ Each entry in OpenRouter's `reasoning_details` becomes a [`ReasoningPart`](../MESSAGES.md#reasoning) on the assistant message, on both `generate_text` and `stream_text`, with nothing dropped:
197
+
198
+ | `reasoning_details` field | `ReasoningPart` field |
199
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------- |
200
+ | `type` | `type`: `reasoning.text` → `:text`, `reasoning.summary` → `:summary`, `reasoning.encrypted` → `:encrypted` |
201
+ | `text` / `summary` | `text` |
202
+ | `data` | `data` |
203
+ | `signature` | `signature` |
204
+ | `id` | `id` |
205
+ | `format` | `format` (e.g. `"anthropic-claude-v1"`, `"openai-responses-v1"`, `"unknown"`) |
206
+
207
+ When streaming, OpenRouter splits one block into many fragments that share an `index`. The provider concatenates their `text`, `summary`, and `data` and keeps the `signature`, `id`, and `format` that arrive along the way, so each block ends as one `ReasoningDone` part. Each non-empty `text` or `summary` fragment is also yielded as a `ReasoningDelta`. Detail types riffer doesn't know are skipped.
208
+
209
+ On the next request, the assistant message's parts go back as `reasoning_details` in their original order and unchanged. This is what lets Anthropic and Gemini models continue signed thinking across a tool-call loop, and lets OpenAI models reuse their encrypted reasoning. Following the [replay contract](../MESSAGES.md#reasoning), only parts whose `format` is one OpenRouter documents are sent: `unknown`, `openai-responses-v1`, `azure-openai-responses-v1`, `bedrock-openai-responses-v1`, `bedrock-xai-responses-v1`, `xai-responses-v1`, `meta-responses-v1`, `anthropic-claude-v1`, and `google-gemini-v1` (listed in `Riffer::Providers::OpenRouter::REASONING_FORMATS`). Parts with no `format`, or one produced by another adapter such as `mock-v1`, are skipped. The `index` is not stored, since the parts' order already carries it.
210
+
194
211
  ## Routing & Fallbacks
195
212
 
196
213
  Survive an upstream outage by chaining models:
data/docs-site/build.rb CHANGED
@@ -1,10 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Builds the docs site — landing page, guide pages, 404, and assets — into
5
- # _site/ at the repo root. Pages are declared in manifest.yml; the build fails
6
- # if the manifest and docs/**/*.md ever disagree.
7
-
8
4
  require "erb"
9
5
  require "fileutils"
10
6
  require "yaml"
@@ -89,8 +85,6 @@ def build_groups(manifest)
89
85
  end
90
86
  end
91
87
 
92
- # Numbering restarts per docs/ subdirectory, so the providers pages read as
93
- # their own sequence rather than continuing the main chapters.
94
88
  def chapter_numbers(manifest)
95
89
  manifest.
96
90
  flat_map { |group| group[:pages] }.
data/docs-site/check.rb CHANGED
@@ -1,9 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Validates every internal link and anchor in the built _site/ HTML. Exits
5
- # non-zero with a list of broken links on failure.
6
-
7
4
  SITE = Pathname(__dir__).join("../_site").expand_path
8
5
 
9
6
  EXTERNAL = %r{\A(?:https?:|mailto:|//)}
@@ -33,11 +30,10 @@ def ids(html)
33
30
  html.scan(/\bid="([^"]+)"/).flatten
34
31
  end
35
32
 
36
- # Links into /api/ are skipped: RDoc builds that tree in a separate task, so
37
- # it is absent when only the site has been built.
38
33
  def page_errors(file, id_index)
39
34
  links(file.read).
40
35
  grep_v(EXTERNAL).
36
+ # RDoc builds /api/ in a separate task, so it is absent when only the site has been built.
41
37
  reject { |link| link.start_with?("/api/") }.
42
38
  filter_map { |link| link_error(file, link, id_index) }
43
39
  end
@@ -1,46 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Typed configuration object holding every class-level DSL setting on a
5
- # Riffer::Agent subclass. Procs are stored unresolved and resolved per-instance
6
- # later.
7
4
  class Riffer::Agent::Config
8
5
  DEFAULT_MAX_STEPS = 16 #: Integer
9
6
 
10
7
  # @rbs @tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?
11
8
 
12
- # The configured agent identifier.
13
9
  attr_reader :identifier #: String? # @dynamic identifier
14
-
15
- # The configured model.
16
10
  attr_reader :model #: (String | Proc)? # @dynamic model
17
-
18
- # The configured instructions.
19
11
  attr_reader :instructions #: (String | Proc)? # @dynamic instructions
20
-
21
- # Options passed to generate_text/stream_text.
22
12
  attr_accessor :model_options #: Hash[Symbol, untyped] # @dynamic model_options, model_options=
23
-
24
- # The configured structured-output schema.
25
13
  attr_reader :structured_output #: Riffer::Params? # @dynamic structured_output
26
-
27
- # The maximum number of LLM call steps in the tool-use loop.
28
14
  attr_accessor :max_steps #: Numeric? # @dynamic max_steps, max_steps=
29
-
30
- # The configured tools.
31
15
  attr_accessor :tools_config #: (Array[singleton(Riffer::Tool)] | Proc)? # @dynamic tools_config, tools_config=
32
-
33
- # The accumulated +use_mcp+ tag configurations.
34
16
  attr_reader :mcp_configs #: Array[Hash[Symbol, untyped]] # @dynamic mcp_configs
35
-
36
- # The configured skills.
37
17
  attr_accessor :skills_config #: Riffer::Skills::Config? # @dynamic skills_config, skills_config=
38
-
39
- # Registered guardrail entries keyed by phase.
40
18
  attr_reader :guardrails #: Hash[Symbol, Array[Hash[Symbol, untyped]]] # @dynamic guardrails
41
19
 
42
- # Builds a new Config. Raises Riffer::ArgumentError if +model+ or
43
- # +instructions+ is invalid (e.g. an empty string).
44
20
  #--
45
21
  #: (
46
22
  # ?identifier: String?,
@@ -81,14 +57,12 @@ class Riffer::Agent::Config
81
57
  self.tool_runtime = tool_runtime if tool_runtime
82
58
  end
83
59
 
84
- # Sets +identifier+, coercing the value to String.
85
60
  #--
86
61
  #: (untyped) -> String?
87
62
  def identifier=(value)
88
63
  @identifier = value&.to_s
89
64
  end
90
65
 
91
- # Sets +structured_output+. Raises Riffer::ArgumentError on an invalid value.
92
66
  #--
93
67
  #: (Riffer::Params?) -> Riffer::Params?
94
68
  def structured_output=(value)
@@ -99,16 +73,14 @@ class Riffer::Agent::Config
99
73
  @structured_output = value
100
74
  end
101
75
 
102
- # Returns the declared tool runtime, or +Riffer.config.tool_runtime+ when none
103
- # was declared. Resolving the global here rather than at construction is what
104
- # lets a copy tell an inherited runtime from a defaulted one.
105
76
  #--
106
77
  #: () -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
107
78
  def tool_runtime
79
+ # Resolved here rather than at construction so a copy can tell an inherited
80
+ # runtime from a defaulted one.
108
81
  @tool_runtime || Riffer.config.tool_runtime
109
82
  end
110
83
 
111
- # Sets +tool_runtime+. Raises Riffer::ArgumentError on an invalid value.
112
84
  #--
113
85
  #: ((singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)) -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
114
86
  def tool_runtime=(value)
@@ -122,8 +94,6 @@ class Riffer::Agent::Config
122
94
  @tool_runtime = value
123
95
  end
124
96
 
125
- # Sets +model+. Raises Riffer::ArgumentError on an invalid value (e.g. an
126
- # empty string).
127
97
  #--
128
98
  #: ((String | Proc)?) -> (String | Proc)?
129
99
  def model=(value)
@@ -131,8 +101,6 @@ class Riffer::Agent::Config
131
101
  @model = value
132
102
  end
133
103
 
134
- # Sets +instructions+. Raises Riffer::ArgumentError on an invalid value (e.g.
135
- # an empty string).
136
104
  #--
137
105
  #: ((String | Proc)?) -> (String | Proc)?
138
106
  def instructions=(value)
@@ -140,8 +108,6 @@ class Riffer::Agent::Config
140
108
  @instructions = value
141
109
  end
142
110
 
143
- # Appends an MCP tag entry to +mcp_configs+.
144
- #
145
111
  #--
146
112
  #: (String | Symbol, ?progressive: bool) -> Array[Hash[Symbol, untyped]]
147
113
  def add_mcp(tag, progressive: true)
@@ -150,9 +116,6 @@ class Riffer::Agent::Config
150
116
  @mcp_configs << { tags: [tag.to_sym], progressive: progressive }
151
117
  end
152
118
 
153
- # Appends a guardrail entry to +guardrails+ for the given phase; +:around+
154
- # appends to both +:before+ and +:after+. Raises Riffer::ArgumentError unless
155
- # +phase+ is :before, :after, or :around.
156
119
  #--
157
120
  #: (Symbol, klass: singleton(Riffer::Guardrail), ?options: Hash[Symbol, untyped]) -> void
158
121
  def add_guardrail(phase, klass:, options: {})
@@ -175,8 +138,6 @@ class Riffer::Agent::Config
175
138
  end
176
139
  end
177
140
 
178
- # Returns the guardrail entries for the given phase, or +[]+ if none.
179
- #
180
141
  #--
181
142
  #: (Symbol) -> Array[Hash[Symbol, untyped]]
182
143
  def guardrails_for(phase)
@@ -185,14 +146,12 @@ class Riffer::Agent::Config
185
146
 
186
147
  private
187
148
 
188
- # +dup+ would leave the copy sharing every collection with this one, so a
189
- # declaration on either would reach the other. The nesting runs deeper than one
190
- # level: an mcp entry holds its own +:tags+ array, a guardrail entry its own
191
- # +:options+ hash, and +model_options+ is arbitrary.
192
149
  #--
193
150
  #: (Riffer::Agent::Config) -> void
194
151
  def initialize_copy(source)
195
152
  super
153
+ # A shallow copy would share collections, letting a declaration on either
154
+ # config reach the other; entries nest (mcp +:tags+, guardrail +:options+).
196
155
  @model_options = Riffer::Helpers::DeepDup.call(source.model_options)
197
156
  @mcp_configs = Riffer::Helpers::DeepDup.call(source.mcp_configs)
198
157
  @guardrails = Riffer::Helpers::DeepDup.call(source.guardrails)