riffer 0.48.0 → 0.50.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.
- checksums.yaml +4 -4
- data/.claude/rules/comments.md +2 -4
- data/.claude/rules/rbs-inline.md +2 -12
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +32 -0
- data/docs/AGENTS.md +3 -5
- data/docs/AGENT_LIFECYCLE.md +13 -16
- data/docs/CONFIGURATION.md +22 -33
- data/docs/STREAM_EVENTS.md +3 -3
- data/docs/TOOL_ADVANCED.md +1 -3
- data/docs/TRACING.md +1 -1
- data/docs/providers/AMAZON_BEDROCK.md +31 -0
- data/docs/providers/ANTHROPIC.md +10 -0
- data/docs/providers/AZURE_OPENAI.md +2 -0
- data/docs/providers/OPENAI.md +22 -0
- data/docs/providers/OPENROUTER.md +18 -1
- data/docs-site/build.rb +0 -6
- data/docs-site/check.rb +1 -5
- data/lib/riffer/agent/config.rb +4 -45
- data/lib/riffer/agent/context.rb +2 -26
- data/lib/riffer/agent/outcome.rb +0 -20
- data/lib/riffer/agent/response.rb +0 -38
- data/lib/riffer/agent/run.rb +11 -43
- data/lib/riffer/agent/serializer.rb +10 -39
- data/lib/riffer/agent/session/repair.rb +3 -15
- data/lib/riffer/agent/session.rb +25 -38
- data/lib/riffer/agent/structured_output/result.rb +0 -8
- data/lib/riffer/agent/structured_output.rb +0 -7
- data/lib/riffer/agent.rb +10 -130
- data/lib/riffer/config/amazon_bedrock.rb +30 -0
- data/lib/riffer/config/anthropic.rb +21 -0
- data/lib/riffer/config/azure_open_ai.rb +30 -0
- data/lib/riffer/config/evals.rb +18 -0
- data/lib/riffer/config/files.rb +61 -0
- data/lib/riffer/config/gemini.rb +21 -0
- data/lib/riffer/config/mcp.rb +31 -0
- data/lib/riffer/config/open_ai.rb +30 -0
- data/lib/riffer/config/open_router.rb +21 -0
- data/lib/riffer/config/pricing/rates.rb +36 -0
- data/lib/riffer/config/pricing.rb +64 -0
- data/lib/riffer/config/skills.rb +38 -0
- data/lib/riffer/config/tracing.rb +43 -0
- data/lib/riffer/config.rb +3 -362
- data/lib/riffer/evals/evaluator.rb +1 -29
- data/lib/riffer/evals/evaluator_runner.rb +0 -15
- data/lib/riffer/evals/judge.rb +0 -8
- data/lib/riffer/evals/result.rb +1 -11
- data/lib/riffer/evals/run_result.rb +0 -12
- data/lib/riffer/evals/scenario_result.rb +0 -19
- data/lib/riffer/files/downloader.rb +2 -3
- data/lib/riffer/files/resolver.rb +2 -7
- data/lib/riffer/guardrail.rb +2 -22
- data/lib/riffer/guardrails/modification.rb +0 -8
- data/lib/riffer/guardrails/result.rb +0 -17
- data/lib/riffer/guardrails/runner.rb +2 -11
- data/lib/riffer/guardrails/tripwire.rb +0 -8
- data/lib/riffer/guardrails.rb +0 -2
- data/lib/riffer/helpers/boolean.rb +0 -4
- data/lib/riffer/helpers/call_or_value.rb +0 -3
- data/lib/riffer/helpers/deep_dup.rb +3 -8
- data/lib/riffer/helpers/dependencies.rb +0 -4
- data/lib/riffer/helpers/identifier.rb +3 -12
- data/lib/riffer/helpers/validate.rb +42 -0
- data/lib/riffer/mcp/authenticated_tool.rb +4 -12
- data/lib/riffer/mcp/client.rb +0 -7
- data/lib/riffer/mcp/manifest.rb +3 -7
- data/lib/riffer/mcp/registration.rb +0 -10
- data/lib/riffer/mcp/registry.rb +0 -9
- data/lib/riffer/mcp/search_tool.rb +0 -4
- data/lib/riffer/mcp/tool.rb +1 -4
- data/lib/riffer/mcp/tool_factory.rb +3 -6
- data/lib/riffer/mcp.rb +2 -23
- data/lib/riffer/messages/assistant/reasoning_part.rb +4 -21
- data/lib/riffer/messages/assistant/tool_call.rb +1 -9
- data/lib/riffer/messages/assistant.rb +1 -21
- data/lib/riffer/messages/base.rb +0 -12
- data/lib/riffer/messages/system.rb +0 -3
- data/lib/riffer/messages/tool.rb +0 -15
- data/lib/riffer/messages/user/file_part.rb +2 -30
- data/lib/riffer/messages/user.rb +0 -4
- data/lib/riffer/params/boolean.rb +1 -5
- data/lib/riffer/params/param.rb +3 -31
- data/lib/riffer/params.rb +8 -41
- data/lib/riffer/providers/amazon_bedrock.rb +98 -46
- data/lib/riffer/providers/anthropic.rb +65 -65
- data/lib/riffer/providers/azure_open_ai.rb +10 -8
- data/lib/riffer/providers/base.rb +7 -28
- data/lib/riffer/providers/finish_reason.rb +0 -6
- data/lib/riffer/providers/gemini/client.rb +4 -17
- data/lib/riffer/providers/gemini.rb +4 -10
- data/lib/riffer/providers/mock.rb +1 -26
- data/lib/riffer/providers/open_ai.rb +94 -46
- data/lib/riffer/providers/open_router.rb +105 -30
- data/lib/riffer/providers/repository.rb +2 -14
- data/lib/riffer/providers/token_usage.rb +5 -14
- data/lib/riffer/registrable.rb +11 -45
- data/lib/riffer/runner/fibers.rb +1 -6
- data/lib/riffer/runner/sequential.rb +0 -1
- data/lib/riffer/runner/threaded.rb +0 -3
- data/lib/riffer/runner.rb +0 -3
- data/lib/riffer/skills/activate_tool.rb +0 -3
- data/lib/riffer/skills/adapter.rb +0 -8
- data/lib/riffer/skills/backend.rb +2 -7
- data/lib/riffer/skills/config.rb +4 -20
- data/lib/riffer/skills/context.rb +0 -29
- data/lib/riffer/skills/filesystem_backend.rb +0 -7
- data/lib/riffer/skills/frontmatter.rb +2 -16
- data/lib/riffer/skills/markdown_adapter.rb +3 -6
- data/lib/riffer/skills/xml_adapter.rb +0 -3
- data/lib/riffer/stream_events/base.rb +0 -3
- data/lib/riffer/stream_events/finish_reason_done.rb +1 -6
- data/lib/riffer/stream_events/guardrail_modification.rb +0 -10
- data/lib/riffer/stream_events/guardrail_tripwire.rb +0 -10
- data/lib/riffer/stream_events/interrupt.rb +2 -12
- data/lib/riffer/stream_events/reasoning_delta.rb +0 -3
- data/lib/riffer/stream_events/reasoning_done.rb +1 -5
- data/lib/riffer/stream_events/skill_activation.rb +0 -3
- data/lib/riffer/stream_events/text_delta.rb +0 -2
- data/lib/riffer/stream_events/text_done.rb +0 -2
- data/lib/riffer/stream_events/token_usage_done.rb +0 -2
- data/lib/riffer/stream_events/tool_call_delta.rb +1 -5
- data/lib/riffer/stream_events/tool_call_done.rb +0 -5
- data/lib/riffer/stream_events/web_search_done.rb +0 -3
- data/lib/riffer/stream_events/web_search_status.rb +1 -5
- data/lib/riffer/testing/minitest.rb +4 -5
- data/lib/riffer/testing.rb +5 -38
- data/lib/riffer/tool.rb +2 -28
- data/lib/riffer/tools/response.rb +3 -32
- data/lib/riffer/tools/runtime/fibers.rb +0 -6
- data/lib/riffer/tools/runtime/inline.rb +0 -1
- data/lib/riffer/tools/runtime/threaded.rb +0 -6
- data/lib/riffer/tools/runtime.rb +5 -26
- data/lib/riffer/tools/toolable.rb +0 -37
- data/lib/riffer/tracing/capture.rb +3 -5
- data/lib/riffer/tracing/no_op.rb +0 -7
- data/lib/riffer/tracing/otel.rb +7 -16
- data/lib/riffer/tracing/stream_recorder.rb +0 -7
- data/lib/riffer/tracing.rb +4 -27
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +2 -30
- data/sig/generated/riffer/agent/config.rbs +12 -48
- data/sig/generated/riffer/agent/context.rbs +2 -26
- data/sig/generated/riffer/agent/outcome.rbs +0 -20
- data/sig/generated/riffer/agent/response.rbs +1 -29
- data/sig/generated/riffer/agent/run.rbs +1 -27
- data/sig/generated/riffer/agent/serializer.rbs +2 -27
- data/sig/generated/riffer/agent/session/repair.rbs +2 -10
- data/sig/generated/riffer/agent/session.rbs +10 -36
- data/sig/generated/riffer/agent/structured_output/result.rbs +0 -7
- data/sig/generated/riffer/agent/structured_output.rbs +0 -7
- data/sig/generated/riffer/agent.rbs +5 -121
- data/sig/generated/riffer/config/amazon_bedrock.rbs +21 -0
- data/sig/generated/riffer/config/anthropic.rbs +15 -0
- data/sig/generated/riffer/config/azure_open_ai.rbs +21 -0
- data/sig/generated/riffer/config/evals.rbs +13 -0
- data/sig/generated/riffer/config/files.rbs +43 -0
- data/sig/generated/riffer/config/gemini.rbs +15 -0
- data/sig/generated/riffer/config/mcp.rbs +19 -0
- data/sig/generated/riffer/config/open_ai.rbs +21 -0
- data/sig/generated/riffer/config/open_router.rbs +15 -0
- data/sig/generated/riffer/config/pricing/rates.rbs +19 -0
- data/sig/generated/riffer/config/pricing.rbs +31 -0
- data/sig/generated/riffer/config/skills.rbs +19 -0
- data/sig/generated/riffer/config/tracing.rbs +25 -0
- data/sig/generated/riffer/config.rbs +2 -307
- data/sig/generated/riffer/evals/evaluator.rbs +0 -28
- data/sig/generated/riffer/evals/evaluator_runner.rbs +0 -14
- data/sig/generated/riffer/evals/judge.rbs +0 -7
- data/sig/generated/riffer/evals/result.rbs +1 -11
- data/sig/generated/riffer/evals/run_result.rbs +0 -12
- data/sig/generated/riffer/evals/scenario_result.rbs +0 -14
- data/sig/generated/riffer/files/resolver.rbs +0 -7
- data/sig/generated/riffer/guardrail.rbs +2 -22
- data/sig/generated/riffer/guardrails/modification.rbs +0 -6
- data/sig/generated/riffer/guardrails/result.rbs +0 -15
- data/sig/generated/riffer/guardrails/runner.rbs +0 -11
- data/sig/generated/riffer/guardrails/tripwire.rbs +0 -8
- data/sig/generated/riffer/guardrails.rbs +0 -2
- data/sig/generated/riffer/helpers/boolean.rbs +0 -4
- data/sig/generated/riffer/helpers/call_or_value.rbs +0 -3
- data/sig/generated/riffer/helpers/deep_dup.rbs +0 -8
- data/sig/generated/riffer/helpers/dependencies.rbs +0 -4
- data/sig/generated/riffer/helpers/identifier.rbs +0 -12
- data/sig/generated/riffer/helpers/validate.rbs +21 -0
- data/sig/generated/riffer/mcp/authenticated_tool.rbs +0 -5
- data/sig/generated/riffer/mcp/client.rbs +0 -7
- data/sig/generated/riffer/mcp/manifest.rbs +3 -7
- data/sig/generated/riffer/mcp/registration.rbs +0 -10
- data/sig/generated/riffer/mcp/registry.rbs +0 -9
- data/sig/generated/riffer/mcp/search_tool.rbs +0 -4
- data/sig/generated/riffer/mcp/tool.rbs +0 -4
- data/sig/generated/riffer/mcp/tool_factory.rbs +0 -6
- data/sig/generated/riffer/mcp.rbs +2 -22
- data/sig/generated/riffer/messages/assistant/reasoning_part.rbs +4 -17
- data/sig/generated/riffer/messages/assistant/tool_call.rbs +1 -9
- data/sig/generated/riffer/messages/assistant.rbs +10 -30
- data/sig/generated/riffer/messages/base.rbs +0 -12
- data/sig/generated/riffer/messages/system.rbs +0 -3
- data/sig/generated/riffer/messages/tool.rbs +0 -12
- data/sig/generated/riffer/messages/user/file_part.rbs +0 -30
- data/sig/generated/riffer/messages/user.rbs +0 -4
- data/sig/generated/riffer/params/boolean.rbs +1 -4
- data/sig/generated/riffer/params/param.rbs +0 -31
- data/sig/generated/riffer/params.rbs +4 -40
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +26 -26
- data/sig/generated/riffer/providers/anthropic.rbs +23 -22
- data/sig/generated/riffer/providers/azure_open_ai.rbs +5 -8
- data/sig/generated/riffer/providers/base.rbs +0 -28
- data/sig/generated/riffer/providers/finish_reason.rbs +0 -6
- data/sig/generated/riffer/providers/gemini/client.rbs +4 -17
- data/sig/generated/riffer/providers/gemini.rbs +0 -6
- data/sig/generated/riffer/providers/mock.rbs +0 -26
- data/sig/generated/riffer/providers/open_ai.rbs +36 -19
- data/sig/generated/riffer/providers/open_router.rbs +33 -14
- data/sig/generated/riffer/providers/repository.rbs +2 -14
- data/sig/generated/riffer/providers/token_usage.rbs +5 -14
- data/sig/generated/riffer/registrable.rbs +0 -45
- data/sig/generated/riffer/runner/fibers.rbs +0 -6
- data/sig/generated/riffer/runner/sequential.rbs +0 -1
- data/sig/generated/riffer/runner/threaded.rbs +0 -3
- data/sig/generated/riffer/runner.rbs +0 -3
- data/sig/generated/riffer/skills/activate_tool.rbs +0 -3
- data/sig/generated/riffer/skills/adapter.rbs +0 -8
- data/sig/generated/riffer/skills/backend.rbs +2 -7
- data/sig/generated/riffer/skills/config.rbs +0 -20
- data/sig/generated/riffer/skills/context.rbs +0 -29
- data/sig/generated/riffer/skills/filesystem_backend.rbs +0 -7
- data/sig/generated/riffer/skills/frontmatter.rbs +2 -16
- data/sig/generated/riffer/skills/markdown_adapter.rbs +0 -6
- data/sig/generated/riffer/skills/xml_adapter.rbs +0 -3
- data/sig/generated/riffer/stream_events/base.rbs +0 -3
- data/sig/generated/riffer/stream_events/finish_reason_done.rbs +1 -6
- data/sig/generated/riffer/stream_events/guardrail_modification.rbs +0 -10
- data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +0 -10
- data/sig/generated/riffer/stream_events/interrupt.rbs +2 -10
- data/sig/generated/riffer/stream_events/reasoning_delta.rbs +0 -3
- data/sig/generated/riffer/stream_events/reasoning_done.rbs +1 -5
- data/sig/generated/riffer/stream_events/skill_activation.rbs +0 -3
- data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
- data/sig/generated/riffer/stream_events/text_done.rbs +0 -2
- data/sig/generated/riffer/stream_events/token_usage_done.rbs +0 -2
- data/sig/generated/riffer/stream_events/tool_call_delta.rbs +1 -5
- data/sig/generated/riffer/stream_events/tool_call_done.rbs +0 -5
- data/sig/generated/riffer/stream_events/web_search_done.rbs +0 -3
- data/sig/generated/riffer/stream_events/web_search_status.rbs +1 -5
- data/sig/generated/riffer/testing.rbs +0 -38
- data/sig/generated/riffer/tool.rbs +0 -27
- data/sig/generated/riffer/tools/response.rbs +3 -29
- data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -5
- data/sig/generated/riffer/tools/runtime/inline.rbs +0 -1
- data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -5
- data/sig/generated/riffer/tools/runtime.rbs +2 -24
- data/sig/generated/riffer/tools/toolable.rbs +0 -37
- data/sig/generated/riffer/tracing/capture.rbs +2 -5
- data/sig/generated/riffer/tracing/no_op.rbs +0 -7
- data/sig/generated/riffer/tracing/otel.rbs +7 -16
- data/sig/generated/riffer/tracing/stream_recorder.rbs +0 -2
- data/sig/generated/riffer/tracing.rbs +4 -23
- data/sig/generated/riffer.rbs +2 -29
- data/sig/manual/riffer/helpers/validate.rbs +5 -0
- metadata +30 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1700c8028d7de9b0e109fc259f904c8a8ff1e92964e23c90cd00574cd43a0bc2
|
|
4
|
+
data.tar.gz: 1245b96f6a1490aa5cedd3d5e60ff062a8d70cc7330bd7ec01d0f027e05d65fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dc40857b3085259180f014442cd8d2ec4c6f6f85d2c577cacbff6332176eb39e948de9e20b22732730e52ea4151016fcf23c3387de181f8b8ff27537df8cc21f
|
|
7
|
+
data.tar.gz: '0152801a535973a01216aac4ff1a5b07554b2e8bde282a9dd3fc2c09408b0ddb7890953e7d60c21102d0889896190adf88d6822ef0ca0ff4a6564b688f7b4dd2'
|
data/.claude/rules/comments.md
CHANGED
|
@@ -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
|
-
- **
|
|
10
|
-
|
|
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").
|
data/.claude/rules/rbs-inline.md
CHANGED
|
@@ -26,28 +26,18 @@ module Riffer
|
|
|
26
26
|
end
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## The `#--` stop directive
|
|
30
30
|
|
|
31
|
-
|
|
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.
|
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,38 @@ 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.50.0](https://github.com/janeapp/riffer/compare/riffer/v0.49.0...riffer/v0.50.0) (2026-09-28)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **anthropic:** capture and replay thinking blocks ([#463](https://github.com/janeapp/riffer/issues/463)) ([4a25851](https://github.com/janeapp/riffer/commit/4a25851141cb9b859778e38e58d701cbfe72939f))
|
|
14
|
+
* **open_ai:** capture and replay reasoning items ([#464](https://github.com/janeapp/riffer/issues/464)) ([03d54a9](https://github.com/janeapp/riffer/commit/03d54a90547797a8c56321527fd73ae1d3b9becc))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* **providers:** keep every text block in assistant content ([#468](https://github.com/janeapp/riffer/issues/468)) ([db14e7a](https://github.com/janeapp/riffer/commit/db14e7ac35bd764c1efa7eb2c7bea95c8050a984))
|
|
20
|
+
|
|
21
|
+
## [0.49.0](https://github.com/janeapp/riffer/compare/riffer/v0.48.0...riffer/v0.49.0) (2026-09-27)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### ⚠ BREAKING CHANGES
|
|
25
|
+
|
|
26
|
+
* **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.
|
|
27
|
+
* **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.
|
|
28
|
+
|
|
29
|
+
### Features
|
|
30
|
+
|
|
31
|
+
* **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))
|
|
32
|
+
* **amazon_bedrock:** capture and replay reasoning content ([#459](https://github.com/janeapp/riffer/issues/459)) ([5048a8c](https://github.com/janeapp/riffer/commit/5048a8ce2aba35ad7af2eeee018dc381ca77eb00))
|
|
33
|
+
* **open_router:** capture and replay reasoning_details ([#453](https://github.com/janeapp/riffer/issues/453)) ([a3e2523](https://github.com/janeapp/riffer/commit/a3e252324de97d3f03942c60a892126975bb1a60))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Code Refactoring
|
|
37
|
+
|
|
38
|
+
* **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))
|
|
39
|
+
|
|
8
40
|
## [0.48.0](https://github.com/janeapp/riffer/compare/riffer/v0.47.2...riffer/v0.48.0) (2026-09-23)
|
|
9
41
|
|
|
10
42
|
|
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
|
|
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
|
|
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
|
|
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) |
|
data/docs/AGENT_LIFECYCLE.md
CHANGED
|
@@ -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.
|
|
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
|
-
####
|
|
247
|
+
#### Discarding pending tool calls after an interrupt
|
|
248
248
|
|
|
249
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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).
|
|
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
|
|
data/docs/CONFIGURATION.md
CHANGED
|
@@ -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
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
|
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:
|
data/docs/STREAM_EVENTS.md
CHANGED
|
@@ -53,7 +53,7 @@ event.content # => "Hello, how can I help you?"
|
|
|
53
53
|
event.to_h # => {role: :assistant, content: "Hello, how can I help you?"}
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
Fires once per model response, after its last `TextDelta`, and contains the response's full text: every text block the provider returned, concatenated in order, so it always equals the joined `TextDelta` contents. A response that carries no text (for example, only tool calls) emits no `TextDone`.
|
|
57
57
|
|
|
58
58
|
### ToolCallDelta
|
|
59
59
|
|
|
@@ -303,8 +303,8 @@ end
|
|
|
303
303
|
|
|
304
304
|
When an agent uses tools during streaming, the flow is:
|
|
305
305
|
|
|
306
|
-
1.
|
|
307
|
-
2. If
|
|
306
|
+
1. `TextDelta`, `ToolCallDelta` and `ToolCallDone` events stream in, in whatever order the model produces them. Text can come before, between or after tool calls.
|
|
307
|
+
2. If the response contained any text, one `TextDone` follows with the full text. A response that only calls tools has no `TextDone`.
|
|
308
308
|
3. Agent executes tools internally
|
|
309
309
|
4. Agent sends results back to LLM
|
|
310
310
|
5. More text events stream in
|
data/docs/TOOL_ADVANCED.md
CHANGED
|
@@ -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
|
|
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
|
|
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:
|
data/docs/providers/ANTHROPIC.md
CHANGED
|
@@ -187,6 +187,16 @@ agent.stream("Solve this complex math problem").each do |event|
|
|
|
187
187
|
end
|
|
188
188
|
```
|
|
189
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. Each `thinking` block becomes a `:text` part carrying its `signature`, and each `redacted_thinking` block becomes an `:encrypted` part whose `data` is the block's opaque payload. `reasoning_text` leaves the redacted parts out, since they have no readable text. Every part is tagged `format: "anthropic-messages-v1"` (`Riffer::Providers::Anthropic::REASONING_FORMAT`).
|
|
191
|
+
|
|
192
|
+
### Reasoning Replay
|
|
193
|
+
|
|
194
|
+
Riffer sends the reasoning back to Anthropic on every later turn, as `thinking` and `redacted_thinking` blocks ahead of the message's text and `tool_use` blocks, in their original order and unchanged. For tool calls, sending it back is required: when thinking is enabled, Claude needs its earlier thinking blocks 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 this provider produced is sent back to Anthropic. A conversation that switches providers midway still works: reasoning from other providers (including OpenRouter's `anthropic-claude-v1` parts) is kept on the messages but left out of Anthropic requests.
|
|
199
|
+
|
|
190
200
|
## Web Search
|
|
191
201
|
|
|
192
202
|
Web search allows Claude to search the web for up-to-date information. When enabled, the provider injects the `web_search_20250305` server tool into the request.
|
|
@@ -87,6 +87,8 @@ Enables extended thinking (for supported models):
|
|
|
87
87
|
model_options reasoning: 'medium' # 'low', 'medium', or 'high'
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
+
Reasoning is captured and replayed on later turns exactly as with the [OpenAI provider](OPENAI.md#reasoning-replay), except that parts are tagged `format: "azure-openai-v1"`. Azure only accepts reasoning produced by the same resource, so parts from the OpenAI provider are never sent to Azure, and Azure's are never sent to OpenAI.
|
|
91
|
+
|
|
90
92
|
### structured_output
|
|
91
93
|
|
|
92
94
|
Structured JSON output works identically to the OpenAI provider.
|
data/docs/providers/OPENAI.md
CHANGED
|
@@ -82,6 +82,28 @@ model_options reasoning: 'medium' # 'low', 'medium', or 'high'
|
|
|
82
82
|
|
|
83
83
|
When reasoning is enabled, you'll receive `ReasoningDelta` and `ReasoningDone` events during streaming.
|
|
84
84
|
|
|
85
|
+
#### Reasoning Replay
|
|
86
|
+
|
|
87
|
+
Each `reasoning` item in a Responses API output becomes [reasoning parts](../MESSAGES.md#reasoning) on the assistant message, on both `generate_text` and `stream_text`, all tagged `format: "openai-v1"` and carrying the item's `id`:
|
|
88
|
+
|
|
89
|
+
| Reasoning item field | `ReasoningPart` |
|
|
90
|
+
| -------------------- | --------------------------------------- |
|
|
91
|
+
| each `summary` entry | a `:summary` part with its `text` |
|
|
92
|
+
| each `content` entry | a `:text` part with its `text` |
|
|
93
|
+
| `encrypted_content` | one `:encrypted` part with it as `data` |
|
|
94
|
+
|
|
95
|
+
The `:encrypted` part is always present, even when the response carried no `encrypted_content`, so the item's `id` is kept. When streaming, the parts are yielded as `ReasoningDone` events once the item completes; summary text still arrives as `ReasoningDelta` events before that.
|
|
96
|
+
|
|
97
|
+
On the next request, parts sharing an `id` go back as one `reasoning` item, ahead of the assistant's text and function calls, in their original order and unchanged. This lets reasoning models carry their reasoning across turns and tool-call loops. You don't have to do anything as long as the assistant messages stay in the history; if you persist sessions, keep the `reasoning` key when you store them.
|
|
98
|
+
|
|
99
|
+
With `store` left on (the API default), OpenAI can resolve a replayed item from its `id` alone. With `model_options store: false`, the item can only be replayed from its encrypted payload, so request it explicitly:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
model_options reasoning: 'medium', store: false, include: ['reasoning.encrypted_content']
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Only parts tagged `openai-v1` are sent. Reasoning from other providers stays on the messages but is left out, including OpenRouter's `openai-responses-v1` parts, which are encrypted under OpenRouter's organization rather than yours.
|
|
106
|
+
|
|
85
107
|
### web_search
|
|
86
108
|
|
|
87
109
|
Enable server-side web search using OpenAI's `web_search_preview` tool. Pass `true` to use defaults or a hash to merge with the tool definition:
|
|
@@ -173,7 +173,7 @@ end
|
|
|
173
173
|
|
|
174
174
|
## Reasoning Models
|
|
175
175
|
|
|
176
|
-
Reasoning models surface their thought process via OpenRouter's normalised `
|
|
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
|