riffer 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.agents/code-style.md +63 -4
- data/.agents/rbs-inline.md +1 -6
- data/.release-please-manifest.json +1 -1
- data/AGENTS.md +1 -2
- data/CHANGELOG.md +18 -0
- data/docs/08_MESSAGES.md +1 -1
- data/docs/14_MCP.md +50 -5
- data/docs/providers/02_AMAZON_BEDROCK.md +14 -0
- data/lib/riffer/agent/config.rb +42 -47
- data/lib/riffer/agent/context.rb +70 -50
- data/lib/riffer/agent/response.rb +4 -20
- data/lib/riffer/agent/run.rb +28 -67
- data/lib/riffer/agent/serializer.rb +22 -81
- data/lib/riffer/agent/session/repair.rb +14 -40
- data/lib/riffer/agent/session.rb +25 -67
- data/lib/riffer/agent/structured_output/result.rb +3 -11
- data/lib/riffer/agent/structured_output.rb +5 -13
- data/lib/riffer/agent.rb +74 -192
- data/lib/riffer/config.rb +34 -101
- data/lib/riffer/evals/evaluator.rb +7 -27
- data/lib/riffer/evals/evaluator_runner.rb +11 -19
- data/lib/riffer/evals/judge.rb +4 -25
- data/lib/riffer/evals/result.rb +1 -18
- data/lib/riffer/evals/run_result.rb +0 -11
- data/lib/riffer/evals/scenario_result.rb +0 -14
- data/lib/riffer/evals.rb +0 -6
- data/lib/riffer/guardrail.rb +4 -27
- data/lib/riffer/guardrails/modification.rb +0 -10
- data/lib/riffer/guardrails/result.rb +3 -30
- data/lib/riffer/guardrails/runner.rb +5 -22
- data/lib/riffer/guardrails/tripwire.rb +1 -19
- data/lib/riffer/guardrails.rb +2 -4
- data/lib/riffer/helpers/call_or_value.rb +4 -3
- data/lib/riffer/helpers/class_name_converter.rb +3 -1
- data/lib/riffer/helpers/dependencies.rb +5 -7
- data/lib/riffer/helpers.rb +0 -5
- data/lib/riffer/mcp/authenticated_tool.rb +9 -9
- data/lib/riffer/mcp/client.rb +12 -17
- data/lib/riffer/mcp/manifest.rb +13 -10
- data/lib/riffer/mcp/registration.rb +2 -11
- data/lib/riffer/mcp/registry.rb +44 -52
- data/lib/riffer/mcp/search_tool.rb +53 -0
- data/lib/riffer/mcp/tool_factory.rb +13 -18
- data/lib/riffer/mcp.rb +12 -17
- data/lib/riffer/messages/assistant.rb +2 -9
- data/lib/riffer/messages/base.rb +46 -16
- data/lib/riffer/messages/file_part.rb +32 -24
- data/lib/riffer/messages/system.rb +0 -5
- data/lib/riffer/messages/tool.rb +0 -10
- data/lib/riffer/messages/user.rb +0 -10
- data/lib/riffer/messages.rb +0 -7
- data/lib/riffer/params/boolean.rb +2 -4
- data/lib/riffer/params/param.rb +28 -39
- data/lib/riffer/params.rb +9 -21
- data/lib/riffer/providers/amazon_bedrock.rb +42 -28
- data/lib/riffer/providers/anthropic.rb +4 -9
- data/lib/riffer/providers/azure_open_ai.rb +3 -19
- data/lib/riffer/providers/base.rb +13 -26
- data/lib/riffer/providers/gemini.rb +4 -4
- data/lib/riffer/providers/mock.rb +6 -26
- data/lib/riffer/providers/open_ai.rb +6 -8
- data/lib/riffer/providers/open_router.rb +4 -10
- data/lib/riffer/providers/repository.rb +4 -3
- data/lib/riffer/providers/token_usage.rb +9 -20
- data/lib/riffer/providers.rb +0 -8
- data/lib/riffer/runner/fibers.rb +10 -16
- data/lib/riffer/runner/sequential.rb +1 -4
- data/lib/riffer/runner/threaded.rb +3 -14
- data/lib/riffer/runner.rb +2 -15
- data/lib/riffer/skills/activate_tool.rb +2 -11
- data/lib/riffer/skills/adapter.rb +4 -22
- data/lib/riffer/skills/backend.rb +7 -21
- data/lib/riffer/skills/config.rb +10 -31
- data/lib/riffer/skills/context.rb +5 -20
- data/lib/riffer/skills/filesystem_backend.rb +7 -25
- data/lib/riffer/skills/frontmatter.rb +10 -28
- data/lib/riffer/skills/markdown_adapter.rb +2 -9
- data/lib/riffer/skills/xml_adapter.rb +2 -8
- data/lib/riffer/stream_events/base.rb +1 -6
- data/lib/riffer/stream_events/guardrail_modification.rb +1 -8
- data/lib/riffer/stream_events/guardrail_tripwire.rb +1 -8
- data/lib/riffer/stream_events/interrupt.rb +4 -7
- data/lib/riffer/stream_events/reasoning_delta.rb +2 -4
- data/lib/riffer/stream_events/reasoning_done.rb +2 -4
- data/lib/riffer/stream_events/skill_activation.rb +2 -4
- data/lib/riffer/stream_events/text_delta.rb +0 -2
- data/lib/riffer/stream_events/text_done.rb +1 -3
- data/lib/riffer/stream_events/token_usage_done.rb +1 -8
- data/lib/riffer/stream_events/tool_call_delta.rb +2 -3
- data/lib/riffer/stream_events/tool_call_done.rb +1 -3
- data/lib/riffer/stream_events/web_search_done.rb +1 -3
- data/lib/riffer/stream_events/web_search_status.rb +2 -3
- data/lib/riffer/stream_events.rb +0 -10
- data/lib/riffer/tool.rb +6 -13
- data/lib/riffer/tools/response.rb +8 -4
- data/lib/riffer/tools/runtime/fibers.rb +0 -3
- data/lib/riffer/tools/runtime/inline.rb +1 -4
- data/lib/riffer/tools/runtime/threaded.rb +0 -2
- data/lib/riffer/tools/runtime.rb +5 -38
- data/lib/riffer/tools/toolable.rb +5 -16
- data/lib/riffer/tools.rb +0 -4
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +7 -8
- data/sig/generated/riffer/agent/config.rbs +29 -46
- data/sig/generated/riffer/agent/context.rbs +40 -48
- data/sig/generated/riffer/agent/response.rbs +4 -20
- data/sig/generated/riffer/agent/run.rbs +12 -61
- data/sig/generated/riffer/agent/serializer.rbs +21 -80
- data/sig/generated/riffer/agent/session/repair.rbs +12 -40
- data/sig/generated/riffer/agent/session.rbs +25 -67
- data/sig/generated/riffer/agent/structured_output/result.rbs +2 -10
- data/sig/generated/riffer/agent/structured_output.rbs +5 -12
- data/sig/generated/riffer/agent.rbs +57 -186
- data/sig/generated/riffer/config.rbs +34 -100
- data/sig/generated/riffer/evals/evaluator.rbs +7 -27
- data/sig/generated/riffer/evals/evaluator_runner.rbs +9 -19
- data/sig/generated/riffer/evals/judge.rbs +4 -24
- data/sig/generated/riffer/evals/result.rbs +1 -17
- data/sig/generated/riffer/evals/run_result.rbs +0 -10
- data/sig/generated/riffer/evals/scenario_result.rbs +0 -13
- data/sig/generated/riffer/evals.rbs +0 -6
- data/sig/generated/riffer/guardrail.rbs +4 -27
- data/sig/generated/riffer/guardrails/modification.rbs +0 -10
- data/sig/generated/riffer/guardrails/result.rbs +3 -30
- data/sig/generated/riffer/guardrails/runner.rbs +5 -22
- data/sig/generated/riffer/guardrails/tripwire.rbs +1 -19
- data/sig/generated/riffer/guardrails.rbs +2 -4
- data/sig/generated/riffer/helpers/call_or_value.rbs +4 -3
- data/sig/generated/riffer/helpers/class_name_converter.rbs +1 -1
- data/sig/generated/riffer/helpers/dependencies.rbs +3 -7
- data/sig/generated/riffer/helpers.rbs +0 -5
- data/sig/generated/riffer/mcp/authenticated_tool.rbs +5 -4
- data/sig/generated/riffer/mcp/client.rbs +10 -16
- data/sig/generated/riffer/mcp/manifest.rbs +9 -9
- data/sig/generated/riffer/mcp/registration.rbs +2 -10
- data/sig/generated/riffer/mcp/registry.rbs +11 -18
- data/sig/generated/riffer/mcp/search_tool.rbs +26 -0
- data/sig/generated/riffer/mcp/tool_factory.rbs +10 -15
- data/sig/generated/riffer/mcp.rbs +10 -17
- data/sig/generated/riffer/messages/assistant.rbs +2 -8
- data/sig/generated/riffer/messages/base.rbs +11 -16
- data/sig/generated/riffer/messages/file_part.rbs +13 -23
- data/sig/generated/riffer/messages/system.rbs +0 -4
- data/sig/generated/riffer/messages/tool.rbs +0 -9
- data/sig/generated/riffer/messages/user.rbs +0 -9
- data/sig/generated/riffer/messages.rbs +0 -7
- data/sig/generated/riffer/params/boolean.rbs +2 -4
- data/sig/generated/riffer/params/param.rbs +21 -39
- data/sig/generated/riffer/params.rbs +9 -21
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +21 -25
- data/sig/generated/riffer/providers/anthropic.rbs +2 -7
- data/sig/generated/riffer/providers/azure_open_ai.rbs +3 -18
- data/sig/generated/riffer/providers/base.rbs +9 -25
- data/sig/generated/riffer/providers/gemini.rbs +0 -2
- data/sig/generated/riffer/providers/mock.rbs +6 -26
- data/sig/generated/riffer/providers/open_ai.rbs +1 -5
- data/sig/generated/riffer/providers/open_router.rbs +4 -10
- data/sig/generated/riffer/providers/repository.rbs +2 -3
- data/sig/generated/riffer/providers/token_usage.rbs +6 -16
- data/sig/generated/riffer/providers.rbs +0 -8
- data/sig/generated/riffer/runner/fibers.rbs +8 -15
- data/sig/generated/riffer/runner/sequential.rbs +1 -3
- data/sig/generated/riffer/runner/threaded.rbs +3 -13
- data/sig/generated/riffer/runner.rbs +2 -14
- data/sig/generated/riffer/skills/activate_tool.rbs +2 -11
- data/sig/generated/riffer/skills/adapter.rbs +4 -22
- data/sig/generated/riffer/skills/backend.rbs +7 -21
- data/sig/generated/riffer/skills/config.rbs +10 -31
- data/sig/generated/riffer/skills/context.rbs +5 -20
- data/sig/generated/riffer/skills/filesystem_backend.rbs +7 -24
- data/sig/generated/riffer/skills/frontmatter.rbs +10 -27
- data/sig/generated/riffer/skills/markdown_adapter.rbs +2 -9
- data/sig/generated/riffer/skills/xml_adapter.rbs +2 -8
- data/sig/generated/riffer/stream_events/base.rbs +1 -6
- data/sig/generated/riffer/stream_events/guardrail_modification.rbs +1 -8
- data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +1 -8
- data/sig/generated/riffer/stream_events/interrupt.rbs +4 -7
- data/sig/generated/riffer/stream_events/reasoning_delta.rbs +2 -4
- data/sig/generated/riffer/stream_events/reasoning_done.rbs +2 -4
- data/sig/generated/riffer/stream_events/skill_activation.rbs +2 -4
- data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
- data/sig/generated/riffer/stream_events/text_done.rbs +1 -3
- data/sig/generated/riffer/stream_events/token_usage_done.rbs +1 -7
- data/sig/generated/riffer/stream_events/tool_call_delta.rbs +2 -3
- data/sig/generated/riffer/stream_events/tool_call_done.rbs +1 -3
- data/sig/generated/riffer/stream_events/web_search_done.rbs +1 -3
- data/sig/generated/riffer/stream_events/web_search_status.rbs +2 -3
- data/sig/generated/riffer/stream_events.rbs +0 -10
- data/sig/generated/riffer/tool.rbs +5 -12
- data/sig/generated/riffer/tools/response.rbs +6 -4
- data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -3
- data/sig/generated/riffer/tools/runtime/inline.rbs +1 -3
- data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -2
- data/sig/generated/riffer/tools/runtime.rbs +5 -37
- data/sig/generated/riffer/tools/toolable.rbs +4 -14
- data/sig/generated/riffer/tools.rbs +0 -4
- data/sig/generated/riffer.rbs +5 -4
- data/sig/manual/riffer/agent/session/repair.rbs +5 -0
- data/sig/manual/riffer/evals/evaluator_runner.rbs +5 -0
- data/sig/manual/riffer/helpers/class_name_converter.rbs +5 -0
- data/sig/manual/riffer/helpers/dependencies.rbs +5 -0
- data/sig/manual/riffer/mcp/authenticated_tool.rbs +5 -0
- data/sig/manual/riffer/mcp/registry.rbs +5 -0
- data/sig/manual/riffer/mcp/tool_factory.rbs +5 -0
- data/sig/manual/riffer/mcp.rbs +5 -0
- data/sig/manual/riffer/providers/repository.rbs +5 -0
- data/sig/manual/riffer.rbs +5 -0
- metadata +17 -9
- data/.agents/rdoc.md +0 -69
- data/lib/riffer/messages/converter.rb +0 -90
- data/sig/generated/riffer/messages/converter.rbs +0 -33
- data/sig/manual/riffer/tools/toolable.rbs +0 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0cddafa66f3f24980037c23be5e8584ec888b93c40b2b780c99e7205558d2593
|
|
4
|
+
data.tar.gz: 5aea2edfeb9d47a4246b15d3f4e16a7a3c1d7941a473355495267b9eb93bc3cc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fad79dcfd8036c3249ea17be785cb6acb193e3a1677f23ee052c89daf39fe97840369a4718e65ebb515bbddeb866608a532b8076a89e2f6c724ddd74d2131e85
|
|
7
|
+
data.tar.gz: 0c7b1bdb7383c6f9a2b053d2d811a7c4aa29142f8a44aa3fd07071d88b0dac722268f036c9f45887bbf3b10591cb3067193540c2cff766d6ab91aa4644f0dcb8
|
data/.agents/code-style.md
CHANGED
|
@@ -24,11 +24,66 @@ class MyCustomError < Riffer::Error
|
|
|
24
24
|
end
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
## Comments
|
|
27
|
+
## Comments & Documentation
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
29
|
+
A comment exists to explain a **why** 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 RDoc descriptions on the public API. Types are not prose's job: parameters, return values, and attribute/constant types live in rbs-inline `#:` annotations (see [rbs-inline.md](rbs-inline.md)).
|
|
30
|
+
|
|
31
|
+
**What's a comment (in scope):** RDoc descriptions and inline `#` explanations. **Not comments (never touched):** rbs-inline `#:` annotations, magic comments (`frozen_string_literal`, `rbs_inline`), and RDoc directives (`:nodoc:`, `:nocov:`).
|
|
32
|
+
|
|
33
|
+
### Public surface
|
|
34
|
+
|
|
35
|
+
Everything public — classes, modules, constants, attributes, public methods, and `protected` subclass-contract methods (e.g. the `pass` / `transform` / `block` helpers a custom `Riffer::Guardrail` calls) — gets **at minimum a very brief description**:
|
|
36
|
+
|
|
37
|
+
- **One verb-first sentence, one line.** `Creates a new agent.`, `Serializes the definition to JSON.`
|
|
38
|
+
- An optional **second sentence is reserved strictly for a "why"** — a non-obvious constraint or rationale the code can't convey. Never a second sentence of "how".
|
|
39
|
+
- If a description needs more than one sentence to say _what_ it does, that's a smell the method does too much.
|
|
40
|
+
- **Exempt:** a constant whose name and value already carry the full meaning (`VERSION = "0.30.0"`, `PHASES = %i[before after]`) — describe a constant only when its name doesn't; an empty namespace module (a Zeitwerk placeholder with no usable members of its own, e.g. `module Riffer::Messages; end`) — its children are documented individually; and a constructor (`initialize`) — the class doc and RDoc's `::new` already cover plain construction, so describe it only when it carries a contract or non-obvious construction behavior (a raise, a dup guard).
|
|
41
|
+
|
|
42
|
+
Do not document parameters or return values in prose — the `#:` line is the single source of truth for types. Attributes and constants still carry a brief description on the line above their inline `#:`.
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
# Serializes the agent definition to a transferable JSON payload.
|
|
46
|
+
#--
|
|
47
|
+
#: (Riffer::Agent) -> String
|
|
48
|
+
def serialize(agent)
|
|
49
|
+
|
|
50
|
+
# The agent's display name.
|
|
51
|
+
attr_reader :name #: String
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Private methods
|
|
55
|
+
|
|
56
|
+
A comment survives on a private method **only** if it explains a why a competent reader cannot recover from the code and names alone — a non-local constraint, an external-system quirk, a deliberate non-obvious tradeoff. A description of what it does, or a why that's evident from the code, gets cut.
|
|
57
|
+
|
|
58
|
+
### Inline comments
|
|
59
|
+
|
|
60
|
+
Same bar: kept only to explain the why of something genuinely ambiguous. `TODO` / `FIXME` / `HACK` markers are tracked work and stay; `NOTE` / `REVIEW` are subject to the why-rule.
|
|
61
|
+
|
|
62
|
+
### No history
|
|
63
|
+
|
|
64
|
+
A comment describes the present, never how the code got there. Change narration — "was X, now Y", "previously used Z" — has no place; the reader cares about what is, not what was. The one thing worth stating is a still-true constraint, and it belongs in the present tense ("the API returns null for empty results — guard"), never told as the story of the bug that revealed it.
|
|
65
|
+
|
|
66
|
+
### RDoc mechanics
|
|
67
|
+
|
|
68
|
+
**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.
|
|
69
|
+
|
|
70
|
+
**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. Reserved for public methods. 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 — but keep the runtime constraints a type can't express (enum value sets, coercion rules, validation failure).
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
# Builds a param from a schema hash.
|
|
74
|
+
# Raises Riffer::ArgumentError if the schema is missing a +type+.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Examples.** Include an example only when a **consumer is likely to use the thing themselves** — a public entry point they construct, subclass, or call (an `Agent` subclass, `Riffer::Mcp.register`, the `params` DSL). Skip examples on framework-internal types even though they're technically public (value objects the framework constructs and hands back, internal engines). When included, keep them sparing — only when they teach something the signature can't — and write them as indented code blocks (2 extra spaces of indent). Usage walkthroughs belong in `docs/`.
|
|
78
|
+
|
|
79
|
+
**Inline code formatting.** Use `+word+` for single-word inline code; for multi-word expressions (spaces, colons, brackets) use `<tt>multi word expression</tt>`, e.g. `Equivalent to <tt>throw :riffer_interrupt, reason</tt>`.
|
|
80
|
+
|
|
81
|
+
**Internal APIs.** Mark with `:nodoc:` to exclude from generated documentation:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
def internal_method # :nodoc:
|
|
85
|
+
end
|
|
86
|
+
```
|
|
32
87
|
|
|
33
88
|
## Hash Key Convention
|
|
34
89
|
|
|
@@ -37,6 +92,10 @@ end
|
|
|
37
92
|
- String keys are only used at serialization boundaries (JSON Schema output, external API payloads)
|
|
38
93
|
- Do not write dual-access patterns like `hash[:key] || hash["key"]` — normalize to symbol keys at the boundary instead
|
|
39
94
|
|
|
95
|
+
## Reserved Tool Identifiers
|
|
96
|
+
|
|
97
|
+
- Internal-use-only tools use plain descriptive names without a prefix (e.g. `mcp_search`, `mcp_call`, `evaluation`, `skill_activate`)
|
|
98
|
+
|
|
40
99
|
## Module Structure
|
|
41
100
|
|
|
42
101
|
```ruby
|
data/.agents/rbs-inline.md
CHANGED
|
@@ -4,12 +4,7 @@ Type annotations are added directly in Ruby source files using [rbs-inline](http
|
|
|
4
4
|
|
|
5
5
|
## Magic Comment
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
# frozen_string_literal: true
|
|
11
|
-
# rbs_inline: enabled
|
|
12
|
-
```
|
|
7
|
+
rbs-inline only processes a file when `rbs_inline: enabled` is present on line 2. It ships as part of the required header on every `lib/**/*.rb` file — see [Required Header](code-style.md#required-header).
|
|
13
8
|
|
|
14
9
|
## Annotation Syntax
|
|
15
10
|
|
data/AGENTS.md
CHANGED
|
@@ -14,8 +14,7 @@ Ruby gem framework for building AI-powered agents with LLM provider adapters.
|
|
|
14
14
|
|
|
15
15
|
- [Architecture](.agents/architecture.md) - Core components and project structure
|
|
16
16
|
- [Testing](.agents/testing.md) - Minitest spec DSL and VCR cassettes
|
|
17
|
-
- [Code Style](.agents/code-style.md) - StandardRB and
|
|
18
|
-
- [RDoc](.agents/rdoc.md) - Documentation format for public APIs
|
|
17
|
+
- [Code Style](.agents/code-style.md) - StandardRB, comment, and RDoc conventions
|
|
19
18
|
- [Providers](.agents/providers.md) - Adding new LLM provider adapters
|
|
20
19
|
- [RBS Inline](.agents/rbs-inline.md) - Type annotations with rbs-inline
|
|
21
20
|
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ 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.32.0](https://github.com/janeapp/riffer/compare/riffer/v0.31.0...riffer/v0.32.0) (2026-06-08)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### ⚠ BREAKING CHANGES
|
|
12
|
+
|
|
13
|
+
* `TokenUsage#cache_creation_tokens` is renamed to `cache_write_tokens` (also reflected in `TokenUsage#to_h`). Update any code reading that attribute or hash key.
|
|
14
|
+
* apply module/class conventions consistently ([#298](https://github.com/janeapp/riffer/issues/298))
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* add Bedrock prompt caching and surface cached tokens ([#300](https://github.com/janeapp/riffer/issues/300)) ([407390b](https://github.com/janeapp/riffer/commit/407390b505a3af6b881c5204d856a8e9ceddbed9))
|
|
19
|
+
* add progressive discovery for MCP tools by default ([0a37485](https://github.com/janeapp/riffer/commit/0a37485dd59e08a0ba0ca8d1046e3313914f72a5))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Code Refactoring
|
|
23
|
+
|
|
24
|
+
* apply module/class conventions consistently ([#298](https://github.com/janeapp/riffer/issues/298)) ([76a3131](https://github.com/janeapp/riffer/commit/76a3131f800ba026c07cc194ab2879279134e865))
|
|
25
|
+
|
|
8
26
|
## [0.31.0](https://github.com/janeapp/riffer/compare/riffer/v0.30.0...riffer/v0.31.0) (2026-06-03)
|
|
9
27
|
|
|
10
28
|
|
data/docs/08_MESSAGES.md
CHANGED
|
@@ -196,7 +196,7 @@ agent = MyAgent.new(session: session)
|
|
|
196
196
|
response = agent.generate # session already carries the last user turn
|
|
197
197
|
```
|
|
198
198
|
|
|
199
|
-
`Riffer::Agent::Session.new(messages:)` accepts `Riffer::Messages::Base` objects. If your persistence layer hands back hashes, normalize them first via `Riffer::Messages::
|
|
199
|
+
`Riffer::Agent::Session.new(messages:)` accepts `Riffer::Messages::Base` objects. If your persistence layer hands back hashes, normalize them first via `Riffer::Messages::Base.from_hash` or your own adapter.
|
|
200
200
|
|
|
201
201
|
### Accessing Message History
|
|
202
202
|
|
data/docs/14_MCP.md
CHANGED
|
@@ -36,8 +36,9 @@ When **`Riffer.config.mcp.credentials`** is set to a Proc, each MCP `tools/call`
|
|
|
36
36
|
```ruby
|
|
37
37
|
Riffer.configure do |config|
|
|
38
38
|
config.mcp.credentials = lambda do |manifest:, matched_tags:, context:|
|
|
39
|
-
# return nil to omit this server's tools for this
|
|
40
|
-
#
|
|
39
|
+
# when caller does not have an integration, return nil to omit this server's tools for this run
|
|
40
|
+
# when server is unauthenticated or local, return {} to include this server's tools with no extra headers
|
|
41
|
+
# when authorization headers are required, return Hash<String,String> headers
|
|
41
42
|
end
|
|
42
43
|
end
|
|
43
44
|
```
|
|
@@ -46,9 +47,9 @@ end
|
|
|
46
47
|
- **`matched_tags`** — intersection of the agent's `use_mcp` tags and `manifest.tags` for this registration (unioned across multiple `use_mcp` lines).
|
|
47
48
|
- **`context`** — the same hash passed to `generate` / `stream` for this run.
|
|
48
49
|
|
|
49
|
-
**Resolve time:**
|
|
50
|
+
**Resolve time:** The proc is invoked once per matching registration before tools are exposed to the model.
|
|
50
51
|
|
|
51
|
-
**Call time:** Authenticated tool wrappers invoke the proc again for each execution. If it returns
|
|
52
|
+
**Call time:** Authenticated tool wrappers invoke the proc again for each execution. If it returns `nil`, then `Riffer::Mcp::CredentialsDeniedError` is raised.
|
|
52
53
|
|
|
53
54
|
If **`credentials` is unset**, discovery and `tools/call` share one client built from `discovery_headers` (same behaviour as a single static token for both list and call).
|
|
54
55
|
|
|
@@ -71,6 +72,8 @@ class ResearchAgent < Riffer::Agent
|
|
|
71
72
|
end
|
|
72
73
|
```
|
|
73
74
|
|
|
75
|
+
By default, `use_mcp` uses **progressive discovery**. The agent receives `mcp_search` rather than every schema up front. See [Progressive Tool Discovery](#progressive-tool-discovery).
|
|
76
|
+
|
|
74
77
|
`use_mcp` accepts any tag registered via `Riffer::Mcp.register`. Multiple calls accumulate — the agent receives tools from all matching servers:
|
|
75
78
|
|
|
76
79
|
```ruby
|
|
@@ -90,6 +93,48 @@ Tool names must be unique across `uses_tools` and all included MCP servers; dupl
|
|
|
90
93
|
|
|
91
94
|
Like [`uses_tools`](03_AGENTS.md#uses_tools), **`use_mcp` is not inherited** from the superclass. Declare `use_mcp` on each agent class that should load MCP tools.
|
|
92
95
|
|
|
96
|
+
## Progressive Tool Discovery
|
|
97
|
+
|
|
98
|
+
Progressive discovery is the default. The `use_mcp` instruction exposes **`mcp_search`** instead of flooding the context with every tool schema up front.
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
class ResearchAgent < Riffer::Agent
|
|
102
|
+
model "openai/gpt-4o"
|
|
103
|
+
|
|
104
|
+
use_mcp :github # default: tools discoverable on demand
|
|
105
|
+
use_mcp :jira, progressive: false # opt-out: all Jira tools injected directly
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Only use `progressive: false` when the server has a small, stable set of tools you always want available.
|
|
110
|
+
|
|
111
|
+
**`mcp_search`** — Search for available tools by name or description.
|
|
112
|
+
- `query` (required, non-empty) — filter by name or description substring.
|
|
113
|
+
|
|
114
|
+
On a successful search, matching tools are injected into the agent's active tool list. The model calls them natively on the next turn — no proxy or JSON-encoded arguments.
|
|
115
|
+
|
|
116
|
+
**Example flow:**
|
|
117
|
+
|
|
118
|
+
1. Agent starts — only `mcp_search` appears in the tool list.
|
|
119
|
+
2. LLM calls `mcp_search` with `query: "create pull request"`.
|
|
120
|
+
3. Riffer injects `github__create_pr` into the active tool list and returns an acknowledgment.
|
|
121
|
+
4. LLM calls `github__create_pr` directly with its real schema (e.g. `title:`, `body:`, `base:`).
|
|
122
|
+
5. The provider validates the arguments; the tool executes with all credential handling intact.
|
|
123
|
+
|
|
124
|
+
Injected tools accumulate across turns — tools discovered in one search remain available for subsequent calls without re-searching.
|
|
125
|
+
|
|
126
|
+
**Multiple progressive `use_mcp` calls:** All matching registrations are combined into one `mcp_search`:
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
use_mcp :connectors_a # both default to progressive
|
|
130
|
+
use_mcp :connectors_b
|
|
131
|
+
# → one mcp_search exposing tools from both registrations
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Credential handling:** Progressive tools follow the same credential rules as regular tools — see [Session credentials callback](#session-credentials-callback).
|
|
135
|
+
|
|
136
|
+
**Tool access in tools:** The full discovery pool is available via `context[:mcp_progressive_tools]` and injected tools via `context[:injected_tools]` (both `Array[Riffer::Tool subclass]`). These keys are framework-managed — treat them as read-only from application code.
|
|
137
|
+
|
|
93
138
|
## Unregistering a Server
|
|
94
139
|
|
|
95
140
|
```ruby
|
|
@@ -130,7 +175,7 @@ All inherit from `Riffer::Mcp::Error < Riffer::Error`.
|
|
|
130
175
|
|
|
131
176
|
- **Tool results:** `tools/call` responses are reduced to joined **text** content from MCP `content` items. Non-text parts (e.g. images, embedded resources) are not surfaced in this release.
|
|
132
177
|
- **Session credentials:** When `Riffer.config.mcp.credentials` is set, authenticated tool wrappers may build a **new HTTP client per tool invocation** so headers stay fresh; there is no connection pooling in this release.
|
|
133
|
-
- **Context window / progressive disclosure:**
|
|
178
|
+
- **Context window / progressive disclosure:** `use_mcp` defaults to progressive mode — tools are discovered via `mcp_search` and injected natively for subsequent calls. Use `progressive: false` to inject all schemas directly. See [Progressive Tool Discovery](#progressive-tool-discovery).
|
|
134
179
|
|
|
135
180
|
## Requirements
|
|
136
181
|
|
|
@@ -83,6 +83,20 @@ model_options additional_model_request_fields: {
|
|
|
83
83
|
}
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
### cache_control
|
|
87
|
+
|
|
88
|
+
Enable prompt caching for models that support it (Claude, Nova). Riffer appends a single Converse `cachePoint` to the stable prefix — after the system array, or after the tools when there is no system prompt — so system instructions and tool definitions are reused across the calls in an agent loop and across conversation turns. The volatile message tail is never cached.
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
# 5-minute TTL (default)
|
|
92
|
+
model_options cache_control: {type: "ephemeral"}
|
|
93
|
+
|
|
94
|
+
# 1-hour TTL (model-dependent; Bedrock validates support)
|
|
95
|
+
model_options cache_control: {type: "ephemeral", ttl: "1h"}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Caching is opt-in: omit `cache_control` and no cachePoint is sent. The breakpoint is only honored once the prefix clears the model's minimum token count; on models that don't support `cachePoint`, the Converse request errors. Verify hits via `response.token_usage.cache_read_tokens`.
|
|
99
|
+
|
|
86
100
|
## Example
|
|
87
101
|
|
|
88
102
|
```ruby
|
data/lib/riffer/agent/config.rb
CHANGED
|
@@ -2,38 +2,49 @@
|
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
4
|
# Typed configuration object holding every class-level DSL setting on a
|
|
5
|
-
# Riffer::Agent subclass.
|
|
6
|
-
#
|
|
7
|
-
# Each subclass of Riffer::Agent owns one Config, accessible via the class
|
|
8
|
-
# method <tt>config</tt>. The class-level DSL (+model+, +instructions+, +uses_tools+,
|
|
9
|
-
# etc.) reads and mutates this Config in place. Append-style DSL methods
|
|
10
|
-
# (+use_mcp+, +guardrail+) are handled by the +add_mcp+ and +add_guardrail+
|
|
11
|
-
# helpers below.
|
|
12
|
-
#
|
|
13
|
-
# Config stores Procs unresolved. Per-instance resolution happens elsewhere
|
|
14
|
-
# (instructions, model, tools, tool runtime, skills).
|
|
5
|
+
# Riffer::Agent subclass. Procs are stored unresolved and resolved per-instance
|
|
6
|
+
# later.
|
|
15
7
|
class Riffer::Agent::Config
|
|
16
8
|
DEFAULT_MAX_STEPS = 16 #: Integer
|
|
17
9
|
|
|
10
|
+
# The configured agent identifier.
|
|
18
11
|
attr_reader :identifier #: String?
|
|
12
|
+
|
|
13
|
+
# The configured model.
|
|
19
14
|
attr_reader :model #: (String | Proc)?
|
|
15
|
+
|
|
16
|
+
# The configured instructions.
|
|
20
17
|
attr_reader :instructions #: (String | Proc)?
|
|
18
|
+
|
|
19
|
+
# Options passed to the provider client.
|
|
21
20
|
attr_accessor :provider_options #: Hash[Symbol, untyped]
|
|
21
|
+
|
|
22
|
+
# Options passed to generate_text/stream_text.
|
|
22
23
|
attr_accessor :model_options #: Hash[Symbol, untyped]
|
|
24
|
+
|
|
25
|
+
# The configured structured-output schema.
|
|
23
26
|
attr_reader :structured_output #: Riffer::Params?
|
|
27
|
+
|
|
28
|
+
# The maximum number of LLM call steps in the tool-use loop.
|
|
24
29
|
attr_accessor :max_steps #: Numeric?
|
|
30
|
+
|
|
31
|
+
# The configured tools.
|
|
25
32
|
attr_accessor :tools_config #: (Array[singleton(Riffer::Tool)] | Proc)?
|
|
33
|
+
|
|
34
|
+
# The accumulated +use_mcp+ tag configurations.
|
|
26
35
|
attr_reader :mcp_configs #: Array[Hash[Symbol, untyped]]
|
|
36
|
+
|
|
37
|
+
# The configured tool runtime.
|
|
27
38
|
attr_reader :tool_runtime #: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
|
|
39
|
+
|
|
40
|
+
# The configured skills.
|
|
28
41
|
attr_accessor :skills_config #: Riffer::Skills::Config?
|
|
42
|
+
|
|
43
|
+
# Registered guardrail entries keyed by phase.
|
|
29
44
|
attr_reader :guardrails #: Hash[Symbol, Array[Hash[Symbol, untyped]]]
|
|
30
45
|
|
|
31
|
-
# Builds a new Config.
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# Raises Riffer::ArgumentError if +model+ or +instructions+ is provided
|
|
35
|
-
# as a non-String, non-Proc value (or as an empty String).
|
|
36
|
-
#
|
|
46
|
+
# Builds a new Config. Raises Riffer::ArgumentError if +model+ or
|
|
47
|
+
# +instructions+ is invalid (e.g. an empty string).
|
|
37
48
|
#--
|
|
38
49
|
#: (?identifier: String?, ?model: (String | Proc)?, ?instructions: (String | Proc)?, ?provider_options: Hash[Symbol, untyped], ?model_options: Hash[Symbol, untyped], ?structured_output: Riffer::Params?, ?max_steps: Numeric?, ?tools_config: (Array[singleton(Riffer::Tool)] | Proc)?, ?mcp_configs: Array[Hash[Symbol, untyped]], ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc), ?skills_config: Riffer::Skills::Config?, ?guardrails: Hash[Symbol, Array[Hash[Symbol, untyped]]]) -> void
|
|
39
50
|
def initialize(
|
|
@@ -64,18 +75,14 @@ class Riffer::Agent::Config
|
|
|
64
75
|
self.tool_runtime = tool_runtime
|
|
65
76
|
end
|
|
66
77
|
|
|
67
|
-
# Sets +identifier
|
|
68
|
-
#
|
|
78
|
+
# Sets +identifier+, coercing the value to String.
|
|
69
79
|
#--
|
|
70
80
|
#: (untyped) -> String?
|
|
71
81
|
def identifier=(value)
|
|
72
82
|
@identifier = value&.to_s
|
|
73
83
|
end
|
|
74
84
|
|
|
75
|
-
# Sets +structured_output+.
|
|
76
|
-
#
|
|
77
|
-
# Raises Riffer::ArgumentError on any other type.
|
|
78
|
-
#
|
|
85
|
+
# Sets +structured_output+. Raises Riffer::ArgumentError on an invalid value.
|
|
79
86
|
#--
|
|
80
87
|
#: (Riffer::Params?) -> Riffer::Params?
|
|
81
88
|
def structured_output=(value)
|
|
@@ -83,11 +90,7 @@ class Riffer::Agent::Config
|
|
|
83
90
|
@structured_output = value
|
|
84
91
|
end
|
|
85
92
|
|
|
86
|
-
# Sets +tool_runtime+.
|
|
87
|
-
# Riffer::Tools::Runtime instance, or a Proc.
|
|
88
|
-
#
|
|
89
|
-
# Raises Riffer::ArgumentError on any other type.
|
|
90
|
-
#
|
|
93
|
+
# Sets +tool_runtime+. Raises Riffer::ArgumentError on an invalid value.
|
|
91
94
|
#--
|
|
92
95
|
#: ((singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)) -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
|
|
93
96
|
def tool_runtime=(value)
|
|
@@ -96,10 +99,8 @@ class Riffer::Agent::Config
|
|
|
96
99
|
@tool_runtime = value
|
|
97
100
|
end
|
|
98
101
|
|
|
99
|
-
# Sets +model+.
|
|
100
|
-
#
|
|
101
|
-
# Raises Riffer::ArgumentError on non-String, non-Proc, or empty-String values.
|
|
102
|
-
#
|
|
102
|
+
# Sets +model+. Raises Riffer::ArgumentError on an invalid value (e.g. an
|
|
103
|
+
# empty string).
|
|
103
104
|
#--
|
|
104
105
|
#: ((String | Proc)?) -> (String | Proc)?
|
|
105
106
|
def model=(value)
|
|
@@ -107,10 +108,8 @@ class Riffer::Agent::Config
|
|
|
107
108
|
@model = value
|
|
108
109
|
end
|
|
109
110
|
|
|
110
|
-
# Sets +instructions+.
|
|
111
|
-
#
|
|
112
|
-
# Raises Riffer::ArgumentError on non-String, non-Proc, or empty-String values.
|
|
113
|
-
#
|
|
111
|
+
# Sets +instructions+. Raises Riffer::ArgumentError on an invalid value (e.g.
|
|
112
|
+
# an empty string).
|
|
114
113
|
#--
|
|
115
114
|
#: ((String | Proc)?) -> (String | Proc)?
|
|
116
115
|
def instructions=(value)
|
|
@@ -121,20 +120,15 @@ class Riffer::Agent::Config
|
|
|
121
120
|
# Appends an MCP tag entry to +mcp_configs+.
|
|
122
121
|
#
|
|
123
122
|
#--
|
|
124
|
-
#: (String | Symbol) -> Array[Hash[Symbol, untyped]]
|
|
125
|
-
def add_mcp(tag)
|
|
126
|
-
|
|
123
|
+
#: (String | Symbol, ?progressive: bool) -> Array[Hash[Symbol, untyped]]
|
|
124
|
+
def add_mcp(tag, progressive: true)
|
|
125
|
+
raise Riffer::ArgumentError, "progressive must be a boolean" unless progressive == true || progressive == false
|
|
126
|
+
@mcp_configs << {tags: [tag.to_sym], progressive: progressive}
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
-
# Appends a guardrail entry to +guardrails+ for the given phase
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
# +:before+ and +:after+.
|
|
133
|
-
# [klass] the Riffer::Guardrail subclass to register.
|
|
134
|
-
# [options] options forwarded to the guardrail at runtime.
|
|
135
|
-
#
|
|
136
|
-
# Raises Riffer::ArgumentError on an invalid phase or non-Guardrail class.
|
|
137
|
-
#
|
|
129
|
+
# Appends a guardrail entry to +guardrails+ for the given phase; +:around+
|
|
130
|
+
# appends to both +:before+ and +:after+. Raises Riffer::ArgumentError unless
|
|
131
|
+
# +phase+ is :before, :after, or :around.
|
|
138
132
|
#--
|
|
139
133
|
#: (Symbol, klass: singleton(Riffer::Guardrail), ?options: Hash[Symbol, untyped]) -> void
|
|
140
134
|
def add_guardrail(phase, klass:, options: {})
|
|
@@ -164,6 +158,7 @@ class Riffer::Agent::Config
|
|
|
164
158
|
|
|
165
159
|
private
|
|
166
160
|
|
|
161
|
+
#--
|
|
167
162
|
#: (untyped, String) -> void
|
|
168
163
|
def validate_string_or_proc!(value, name)
|
|
169
164
|
return if value.nil? || value.is_a?(Proc)
|
data/lib/riffer/agent/context.rb
CHANGED
|
@@ -1,38 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Typed value object wrapping the runtime context Hash held by a
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# reads so tools (which receive +context:+ as a keyword) keep working
|
|
8
|
-
# with both built-in and caller-provided keys.
|
|
9
|
-
#
|
|
10
|
-
# Reserved keys (+:skills+, +:token_usage+) cannot be set by the caller
|
|
11
|
-
# at construction; they are owned by Riffer and written through the typed
|
|
12
|
-
# setters. Type invariants are enforced on write — +skills+ must be a
|
|
13
|
-
# +Riffer::Skills::Context+ (or nil); +token_usage+ must be a
|
|
14
|
-
# +Riffer::Providers::TokenUsage+ (or nil).
|
|
15
|
-
#
|
|
16
|
-
# context = Riffer::Agent::Context.new(user_id: 42)
|
|
17
|
-
# context[:user_id] # => 42
|
|
18
|
-
# context.skills # => nil
|
|
19
|
-
# context.token_usage # => nil
|
|
20
|
-
#
|
|
4
|
+
# Typed value object wrapping the runtime context Hash held by a Riffer::Agent.
|
|
5
|
+
# Exposes typed +skills+, +token_usage+, +mcp_progressive_tools+, and
|
|
6
|
+
# +discovered_tools+ accessors while preserving +#[]+ / +#dig+ for caller-provided keys.
|
|
21
7
|
class Riffer::Agent::Context
|
|
22
8
|
# @rbs @data: Hash[Symbol, untyped]
|
|
23
9
|
|
|
24
|
-
|
|
25
|
-
# constructor raises +Riffer::ArgumentError+.
|
|
26
|
-
RESERVED_KEYS = [:skills, :token_usage].freeze #: Array[Symbol]
|
|
10
|
+
RESERVED_KEYS = [:skills, :token_usage, :mcp_progressive_tools, :discovered_tools].freeze #: Array[Symbol]
|
|
27
11
|
|
|
28
|
-
# Builds a new context.
|
|
29
|
-
#
|
|
30
|
-
# [data] caller-provided Hash passed as <tt>Agent.new(context:)</tt>.
|
|
31
|
-
# Duped before storage so caller mutations do not affect the
|
|
32
|
-
# agent. Must not contain any +RESERVED_KEYS+.
|
|
33
|
-
#
|
|
34
|
-
# Raises Riffer::ArgumentError when +data+ contains a reserved key.
|
|
35
|
-
#
|
|
12
|
+
# Builds a new context. The caller Hash is duped so later caller mutations
|
|
13
|
+
# don't leak in. Raises Riffer::ArgumentError if it contains a reserved key.
|
|
36
14
|
#--
|
|
37
15
|
#: (?Hash[Symbol, untyped]) -> void
|
|
38
16
|
def initialize(data = {})
|
|
@@ -45,6 +23,8 @@ class Riffer::Agent::Context
|
|
|
45
23
|
@data = data.dup
|
|
46
24
|
@data[:skills] = nil
|
|
47
25
|
@data[:token_usage] = nil
|
|
26
|
+
@data[:mcp_progressive_tools] = nil
|
|
27
|
+
@data[:discovered_tools] = nil
|
|
48
28
|
end
|
|
49
29
|
|
|
50
30
|
# The agent's resolved +Riffer::Skills::Context+, or +nil+ when skills
|
|
@@ -56,12 +36,8 @@ class Riffer::Agent::Context
|
|
|
56
36
|
@data[:skills]
|
|
57
37
|
end
|
|
58
38
|
|
|
59
|
-
# Sets the resolved skills context.
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# Raises Riffer::ArgumentError if +value+ is neither +nil+ nor a
|
|
63
|
-
# +Riffer::Skills::Context+.
|
|
64
|
-
#
|
|
39
|
+
# Sets the resolved skills context. Raises Riffer::ArgumentError on an
|
|
40
|
+
# invalid value.
|
|
65
41
|
#--
|
|
66
42
|
#: (Riffer::Skills::Context?) -> Riffer::Skills::Context?
|
|
67
43
|
def skills=(value)
|
|
@@ -81,12 +57,8 @@ class Riffer::Agent::Context
|
|
|
81
57
|
@data[:token_usage]
|
|
82
58
|
end
|
|
83
59
|
|
|
84
|
-
# Sets the cumulative token usage.
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
# Raises Riffer::ArgumentError if +value+ is neither +nil+ nor a
|
|
88
|
-
# +Riffer::Providers::TokenUsage+.
|
|
89
|
-
#
|
|
60
|
+
# Sets the cumulative token usage. Raises Riffer::ArgumentError on an invalid
|
|
61
|
+
# value.
|
|
90
62
|
#--
|
|
91
63
|
#: (Riffer::Providers::TokenUsage?) -> Riffer::Providers::TokenUsage?
|
|
92
64
|
def token_usage=(value)
|
|
@@ -97,28 +69,76 @@ class Riffer::Agent::Context
|
|
|
97
69
|
@data[:token_usage] = value
|
|
98
70
|
end
|
|
99
71
|
|
|
100
|
-
# Hash-style read
|
|
101
|
-
#
|
|
102
|
-
# <tt>context[:tenant]</tt> keep working unchanged.
|
|
103
|
-
#
|
|
72
|
+
# Hash-style read, preserved so tools can pull caller-provided keys via
|
|
73
|
+
# <tt>context[:agent]</tt>.
|
|
104
74
|
#--
|
|
105
75
|
#: (Symbol) -> untyped
|
|
106
76
|
def [](key)
|
|
107
77
|
@data[key]
|
|
108
78
|
end
|
|
109
79
|
|
|
110
|
-
#
|
|
111
|
-
|
|
112
|
-
|
|
80
|
+
# Auth-wrapped MCP tool classes for progressive discovery, or +nil+.
|
|
81
|
+
#--
|
|
82
|
+
#: () -> Array[singleton(Riffer::Tool)]?
|
|
83
|
+
def mcp_progressive_tools
|
|
84
|
+
@data[:mcp_progressive_tools]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Sets progressive MCP tools. Raises Riffer::ArgumentError on an invalid value.
|
|
88
|
+
#--
|
|
89
|
+
#: (Array[singleton(Riffer::Tool)]?) -> Array[singleton(Riffer::Tool)]?
|
|
90
|
+
def mcp_progressive_tools=(value)
|
|
91
|
+
valid = value.nil? || (
|
|
92
|
+
value.is_a?(Array) &&
|
|
93
|
+
value.all? { |tool| tool.is_a?(Class) && tool < Riffer::Tool }
|
|
94
|
+
)
|
|
95
|
+
unless valid
|
|
96
|
+
raise Riffer::ArgumentError,
|
|
97
|
+
"mcp_progressive_tools must be an Array of Riffer::Tool subclasses or nil, got #{value.class}"
|
|
98
|
+
end
|
|
99
|
+
@data[:mcp_progressive_tools] = value
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# MCP tool classes discovered during progressive search. Accumulates across
|
|
103
|
+
# +generate+ calls and is merged into the active tool list on every LLM call.
|
|
104
|
+
#--
|
|
105
|
+
#: () -> Array[singleton(Riffer::Tool)]?
|
|
106
|
+
def discovered_tools
|
|
107
|
+
@data[:discovered_tools]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Sets the discovered tools array. Raises Riffer::ArgumentError on an invalid value.
|
|
111
|
+
#--
|
|
112
|
+
#: (Array[singleton(Riffer::Tool)]?) -> Array[singleton(Riffer::Tool)]?
|
|
113
|
+
def discovered_tools=(value)
|
|
114
|
+
valid = value.nil? || (
|
|
115
|
+
value.is_a?(Array) &&
|
|
116
|
+
value.all? { |tool| tool.is_a?(Class) && tool < Riffer::Tool }
|
|
117
|
+
)
|
|
118
|
+
unless valid
|
|
119
|
+
raise Riffer::ArgumentError,
|
|
120
|
+
"discovered_tools must be an Array of Riffer::Tool subclasses or nil, got #{value.class}"
|
|
121
|
+
end
|
|
122
|
+
@data[:discovered_tools] = value
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Accumulates newly discovered MCP tool classes, deduplicating by name.
|
|
126
|
+
# Each call extends the existing set; calling multiple times is safe.
|
|
127
|
+
#--
|
|
128
|
+
#: (Array[singleton(Riffer::Tool)]) -> Array[singleton(Riffer::Tool)]
|
|
129
|
+
def discover_tools(tools)
|
|
130
|
+
existing = @data[:discovered_tools] || []
|
|
131
|
+
@data[:discovered_tools] = (existing + tools).uniq(&:name)
|
|
132
|
+
end
|
|
133
|
+
|
|
113
134
|
#--
|
|
114
135
|
#: (*Symbol) -> untyped
|
|
115
136
|
def dig(*keys)
|
|
116
137
|
@data.dig(*keys)
|
|
117
138
|
end
|
|
118
139
|
|
|
119
|
-
# Returns a copy of the underlying Hash
|
|
120
|
-
#
|
|
121
|
-
#
|
|
140
|
+
# Returns a copy of the underlying Hash; mutating it does not affect this
|
|
141
|
+
# context.
|
|
122
142
|
#--
|
|
123
143
|
#: () -> Hash[Symbol, untyped]
|
|
124
144
|
def to_h
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Wraps agent generation
|
|
5
|
-
#
|
|
6
|
-
# When guardrails block execution, the response will contain a tripwire
|
|
7
|
-
# with details about the block. The content will be empty for blocked responses.
|
|
4
|
+
# Wraps an agent generation response. When a guardrail blocks execution,
|
|
5
|
+
# +content+ is empty and +tripwire+ carries the block details.
|
|
8
6
|
#
|
|
9
7
|
# response = agent.generate("Hello")
|
|
10
8
|
# if response.blocked?
|
|
@@ -33,24 +31,10 @@ class Riffer::Agent::Response
|
|
|
33
31
|
# The full message history from the agent conversation.
|
|
34
32
|
attr_reader :messages #: Array[Riffer::Messages::Base]
|
|
35
33
|
|
|
36
|
-
# Call ids of tool_use blocks
|
|
37
|
-
#
|
|
38
|
-
# unanswered and +Riffer.config.experimental_history_healing+ is on.
|
|
39
|
-
# Empty otherwise.
|
|
34
|
+
# Call ids of tool_use blocks riffer filled with placeholder results this
|
|
35
|
+
# turn (when an interrupt left them unanswered and history healing is on).
|
|
40
36
|
attr_reader :healed_tool_call_ids #: Array[String]
|
|
41
37
|
|
|
42
|
-
# Creates a new response.
|
|
43
|
-
#
|
|
44
|
-
# [content] the response content.
|
|
45
|
-
# [tripwire] optional tripwire for blocked responses.
|
|
46
|
-
# [modifications] guardrail modifications applied during processing.
|
|
47
|
-
# [interrupted] whether the agent loop was interrupted by a callback.
|
|
48
|
-
# [interrupt_reason] optional reason passed via <tt>throw :riffer_interrupt, reason</tt>.
|
|
49
|
-
# [structured_output] parsed structured output when structured output is configured.
|
|
50
|
-
# [messages] the full message history from the agent conversation.
|
|
51
|
-
# [healed_tool_call_ids] call ids filled with placeholder tool results
|
|
52
|
-
# when history healing is enabled.
|
|
53
|
-
#
|
|
54
38
|
#--
|
|
55
39
|
#: (String, ?tripwire: Riffer::Guardrails::Tripwire?, ?modifications: Array[Riffer::Guardrails::Modification], ?interrupted: bool, ?interrupt_reason: (String | Symbol)?, ?structured_output: Hash[Symbol, untyped]?, ?messages: Array[Riffer::Messages::Base], ?healed_tool_call_ids: Array[String]) -> void
|
|
56
40
|
def initialize(content, tripwire: nil, modifications: [], interrupted: false, interrupt_reason: nil, structured_output: nil, messages: [], healed_tool_call_ids: [])
|