riffer 0.47.1 → 0.47.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/rules/rbs-inline.md +5 -1
  3. data/.release-please-manifest.json +1 -1
  4. data/CHANGELOG.md +8 -0
  5. data/Rakefile +1 -1
  6. data/Steepfile +1 -15
  7. data/docs/AGENTS.md +1 -1
  8. data/docs/STREAM_EVENTS.md +23 -0
  9. data/docs/providers/CUSTOM_PROVIDERS.md +2 -0
  10. data/lib/riffer/agent/config.rb +11 -11
  11. data/lib/riffer/agent/outcome.rb +2 -2
  12. data/lib/riffer/agent/response.rb +9 -9
  13. data/lib/riffer/agent/run.rb +0 -1
  14. data/lib/riffer/agent/session.rb +1 -1
  15. data/lib/riffer/agent/structured_output/result.rb +2 -2
  16. data/lib/riffer/agent/structured_output.rb +1 -1
  17. data/lib/riffer/agent.rb +11 -11
  18. data/lib/riffer/config.rb +30 -30
  19. data/lib/riffer/evals/judge.rb +2 -3
  20. data/lib/riffer/evals/result.rb +6 -6
  21. data/lib/riffer/evals/run_result.rb +1 -1
  22. data/lib/riffer/evals/scenario_result.rb +6 -6
  23. data/lib/riffer/guardrails/modification.rb +3 -3
  24. data/lib/riffer/guardrails/result.rb +3 -3
  25. data/lib/riffer/guardrails/runner.rb +4 -4
  26. data/lib/riffer/guardrails/tripwire.rb +4 -4
  27. data/lib/riffer/mcp/manifest.rb +5 -5
  28. data/lib/riffer/mcp/registration.rb +1 -1
  29. data/lib/riffer/mcp/search_tool.rb +1 -1
  30. data/lib/riffer/messages/assistant.rb +5 -5
  31. data/lib/riffer/messages/base.rb +2 -4
  32. data/lib/riffer/messages/file_part.rb +4 -7
  33. data/lib/riffer/messages/tool.rb +4 -4
  34. data/lib/riffer/messages/user.rb +1 -1
  35. data/lib/riffer/params/param.rb +8 -8
  36. data/lib/riffer/params.rb +1 -1
  37. data/lib/riffer/providers/amazon_bedrock.rb +29 -2
  38. data/lib/riffer/providers/anthropic.rb +7 -0
  39. data/lib/riffer/providers/base.rb +1 -2
  40. data/lib/riffer/providers/finish_reason.rb +2 -2
  41. data/lib/riffer/providers/mock.rb +1 -1
  42. data/lib/riffer/providers/open_ai.rb +8 -0
  43. data/lib/riffer/providers/repository.rb +0 -4
  44. data/lib/riffer/providers/token_usage.rb +5 -5
  45. data/lib/riffer/skills/adapter.rb +1 -1
  46. data/lib/riffer/skills/context.rb +4 -7
  47. data/lib/riffer/skills/filesystem_backend.rb +1 -2
  48. data/lib/riffer/skills/frontmatter.rb +4 -4
  49. data/lib/riffer/skills/xml_adapter.rb +1 -1
  50. data/lib/riffer/stream_events/base.rb +1 -1
  51. data/lib/riffer/stream_events/finish_reason_done.rb +2 -2
  52. data/lib/riffer/stream_events/guardrail_modification.rb +1 -1
  53. data/lib/riffer/stream_events/guardrail_tripwire.rb +1 -1
  54. data/lib/riffer/stream_events/interrupt.rb +2 -2
  55. data/lib/riffer/stream_events/reasoning_delta.rb +1 -1
  56. data/lib/riffer/stream_events/reasoning_done.rb +1 -1
  57. data/lib/riffer/stream_events/skill_activation.rb +1 -1
  58. data/lib/riffer/stream_events/text_delta.rb +1 -1
  59. data/lib/riffer/stream_events/text_done.rb +1 -1
  60. data/lib/riffer/stream_events/token_usage_done.rb +1 -1
  61. data/lib/riffer/stream_events/tool_call_delta.rb +3 -3
  62. data/lib/riffer/stream_events/tool_call_done.rb +4 -4
  63. data/lib/riffer/stream_events/web_search_done.rb +2 -2
  64. data/lib/riffer/stream_events/web_search_status.rb +3 -3
  65. data/lib/riffer/tools/response.rb +4 -4
  66. data/lib/riffer/tracing/stream_recorder.rb +6 -6
  67. data/lib/riffer/version.rb +1 -1
  68. data/lib/riffer.rb +7 -0
  69. data/rbs_collection.lock.yaml +320 -0
  70. data/rbs_collection.yaml +12 -0
  71. data/sig/_private/anthropic.rbs +5 -4
  72. data/sig/_private/aws-sdk-core/event_error.rbs +15 -0
  73. data/sig/generated/riffer/providers/amazon_bedrock.rbs +10 -1
  74. data/sig/generated/riffer/providers/repository.rbs +0 -2
  75. data/sig/generated/riffer.rbs +8 -0
  76. metadata +4 -3
  77. data/sig/_private/async.rbs +0 -28
  78. data/sig/_private/minitest.rbs +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ac69c8c20b892c53242b0fb792aee2ee1ce5da1ab02b1c53536fa8d1c006f08
4
- data.tar.gz: 0fa02ebc739e1968167e517a41d4f0a2cabff5a5845862ac78980d207638c6cd
3
+ metadata.gz: b5e4c3861f4218fa075b5abd666be63eee6432db05c9a6fff3ab4285748ed7c5
4
+ data.tar.gz: a945c6c549c4c18b4e228cbcce1a03a48eb2b263a20bb18c9c1c51088c6a3e8c
5
5
  SHA512:
6
- metadata.gz: 5c0363ce9dad27ccfc899d6c7f4e71123eaa9ae5ceec8e50387575f0642c7696f07de37cb987c45099aee999fe5ac39c8635f07f36efb784c3341bd8a4c6a6aa
7
- data.tar.gz: 4b5d6209255a46d9abd52275021709740bf0e87676c20c3bd9240f12acddc7768a050391530970c2a2ed407dbdd27fb50a1b03e43caff8361ad6f28cc67884db
6
+ metadata.gz: 308010c98f1f44a9a13032b39fea4a2636977d9b6f232ccb9aa040ef6485f0b2d51ec3d66a69be0e4a522759cc86e42fa8b94eae6b2446a5c61ec6a3f6388892
7
+ data.tar.gz: dd7710eb0ed144e1dfaf49d063e5d4845068e590927092dd5b3f946b0cc0f49533262c3dc83e12dbb87fdc8356eb83890177ccf8eacc485028ca4ce7684ce01e
@@ -78,7 +78,11 @@ end
78
78
 
79
79
  ### Where stubs and stdlib deps live
80
80
 
81
- - `sig/_private/` — signatures that must **not** ship. RBS **skips** `_`-prefixed directories in library mode, so consumers never load them; riffer's own `steep check` does (via the `Steepfile`). Two kinds, by predictable path: external-gem signatures are named by gem at the top level (`async.rbs`, `mcp.rbs`, `zeitwerk.rbs`, `openai.rbs`, `anthropic.rbs`, `aws-sdk-core/*` — full stubs for RBS-less gems plus arity patches for the provider SDKs); riffer's own hidden stubs mirror `lib/` under `riffer/` (e.g. `riffer/providers/anthropic.rbs` narrows the private `client` method to the SDK-typed client).
81
+ Dependency signatures come from [`rbs collection`](https://github.com/ruby/gem_rbs_collection): `rbs_collection.yaml` pins the upstream repo to a commit SHA, the gem set is derived from `Gemfile.lock`, and `rbs_collection.lock.yaml` is committed (the installed `.gem_rbs_collection/` is gitignored). Steep auto-detects the config, so the `Steepfile` declares no `library` lines. `yaml` is the one hand-listed entry under `gems:` — it's stdlib, so it never appears in `Gemfile.lock`.
82
+
83
+ `bin/typecheck` runs `rbs collection update` before `steep check`, keeping the lock in sync with `Gemfile.lock`; CI fails on lock drift. So after a Dependabot gem bump, run `bin/typecheck` and commit the refreshed lock. To pull newer upstream stubs, bump `revision` in `rbs_collection.yaml` and run `bin/typecheck` — pinning is what makes `update` deterministic.
84
+
85
+ - `sig/_private/` — signatures that must **not** ship. RBS **skips** `_`-prefixed directories in library mode, so consumers never load them; riffer's own `steep check` does (via the `Steepfile`). Two kinds, by predictable path: external-gem signatures are named by gem at the top level — full stubs for the gems upstream has nothing for (`mcp.rbs`, `zeitwerk.rbs`, `opentelemetry.rbs`, `rspec.rbs`, `aws-sdk-core/*`) plus arity patches for the provider SDKs (`openai.rbs`, `anthropic.rbs`); riffer's own hidden stubs mirror `lib/` under `riffer/` (e.g. `riffer/providers/anthropic.rbs` narrows the private `client` method to the SDK-typed client). A stub must be **deleted** once the collection covers that gem, or RBS raises `DuplicatedMethodDefinitionError`.
82
86
  - `sig/manual/` — hand-written riffer-only signatures that are **safe to ship**, for the few things rbs-inline can't generate _at all_ (mirroring `lib/`). In practice that's `extend self` modules (`riffer/agent/run.rbs`, `riffer/helpers/call_or_value.rbs`) and modeling an include applied dynamically (`riffer/tools/toolable.rbs`). SDK-free ivars are **not** hand-written here — declare them inline with `# @rbs` (see "Annotation Conventions"). SDK-typed signatures can't ship, so they go in `_private/riffer/providers/` (the narrowed `client`).
83
87
  - `sig/manifest.yaml` — declares the **stdlib** RBS the shipped sigs reference (`uri`, `net-http`) so `rbs -r riffer` resolves them.
84
88
 
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.47.1"
2
+ ".": "0.47.2"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ 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.47.2](https://github.com/janeapp/riffer/compare/riffer/v0.47.1...riffer/v0.47.2) (2026-09-21)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **providers:** raise IncompleteStreamError when a stream ends without its terminal event ([#445](https://github.com/janeapp/riffer/issues/445)) ([d142e23](https://github.com/janeapp/riffer/commit/d142e23e80d30ce73e81474eee35c71df1856c40))
14
+ * stop relying on the transitive cgi gem for CGI.escapeHTML ([#443](https://github.com/janeapp/riffer/issues/443)) ([821f3e3](https://github.com/janeapp/riffer/commit/821f3e3e405845b2cc275f86344e526cc2cf183a))
15
+
8
16
  ## [0.47.1](https://github.com/janeapp/riffer/compare/riffer/v0.47.0...riffer/v0.47.1) (2026-09-11)
9
17
 
10
18
 
data/Rakefile CHANGED
@@ -58,7 +58,7 @@ end
58
58
  namespace :steep do
59
59
  desc "Run Steep type checker"
60
60
  task :check do
61
- sh "bundle exec steep check"
61
+ sh "bin/typecheck"
62
62
  end
63
63
  end
64
64
 
data/Steepfile CHANGED
@@ -9,19 +9,5 @@ target :lib do
9
9
 
10
10
  check "lib"
11
11
 
12
- library "anthropic"
13
- library "aws-sdk-bedrockruntime"
14
- library "aws-sdk-core"
15
- library "base64"
16
- library "cgi"
17
- library "digest"
18
- library "json"
19
- library "logger"
20
- library "net-http"
21
- library "openai"
22
- library "securerandom"
23
- library "uri"
24
- library "yaml"
25
-
26
- configure_code_diagnostics(D::Ruby.strict)
12
+ configure_code_diagnostics(D::Ruby.all_error)
27
13
  end
data/docs/AGENTS.md CHANGED
@@ -372,7 +372,7 @@ agent.generate("Summarize this ticket.",
372
372
  agent.stream("...", tags: {team: "growth", environment: "production"})
373
373
  ```
374
374
 
375
- Keys and values may be `String` or `Symbol`; both are stringified, and entries with a `nil` value are dropped. Passing a non-`Hash` raises `Riffer::ArgumentError`. An omitted or empty `tags:` is a complete no-op.
375
+ Keys and values may be `String` or `Symbol`; both are stringified, and entries with a `nil` value are dropped. An omitted or empty `tags:` is a complete no-op.
376
376
 
377
377
  Tags propagate to **two** places:
378
378
 
@@ -272,6 +272,29 @@ event.to_h # => {role: :assistant, finish_reason: :length, raw_fin
272
272
 
273
273
  The agent loop stamps this value onto the accumulated assistant message's `finish_reason`.
274
274
 
275
+ ## Incomplete Streams
276
+
277
+ If a provider's stream ends before its terminal event, the enumerator raises `Riffer::IncompleteStreamError` (a `Riffer::Error` subclass) instead of finishing normally, so a truncated or empty response is never returned as a complete message. Events already yielded before the raise were delivered as usual, but nothing from the failed step is added to the session: there is no partial assistant message to resume from, and messages from earlier completed steps (tool calls and their results) are untouched.
278
+
279
+ Supported on Amazon Bedrock, Anthropic, and OpenAI / Azure OpenAI.
280
+
281
+ The user prompt is added to the session before the run starts and stays there after the failure, so retry with `agent.stream` and no prompt. Passing the prompt again would add a second user turn.
282
+
283
+ ```ruby
284
+ attempts = 0
285
+ prompt = "Tell me a story"
286
+ begin
287
+ agent.stream(prompt).each do |event|
288
+ print event.content if event.is_a?(Riffer::StreamEvents::TextDelta)
289
+ end
290
+ rescue Riffer::IncompleteStreamError => e
291
+ warn "stream ended early: #{e.message}"
292
+ prompt = nil # already in the session; re-run on the existing history
293
+ retry if (attempts += 1) < 3
294
+ raise
295
+ end
296
+ ```
297
+
275
298
  ## Streaming with Tools
276
299
 
277
300
  When an agent uses tools during streaming, the flow is:
@@ -274,6 +274,8 @@ For streaming, emit a `FinishReasonDone` event near the end of `execute_stream`:
274
274
  yielder << Riffer::StreamEvents::FinishReasonDone.new(finish_reason: :stop, raw_finish_reason: "done")
275
275
  ```
276
276
 
277
+ Also have `execute_stream` raise `Riffer::IncompleteStreamError` when the stream ends without the provider's terminal event, rather than returning normally. Otherwise a connection that drops mid-response looks identical to a finished one, and the agent loop accepts a truncated message as complete.
278
+
277
279
  ## Trace Provider Name
278
280
 
279
281
  LLM-call and agent-run spans stamp `gen_ai.provider.name` from the `semconv_provider_name` class method. The default is your snake_cased class name; override it when a [GenAI semconv well-known value](https://opentelemetry.io/docs/specs/semconv/gen-ai/) exists for your provider:
@@ -8,37 +8,37 @@ class Riffer::Agent::Config
8
8
  DEFAULT_MAX_STEPS = 16 #: Integer
9
9
 
10
10
  # The configured agent identifier.
11
- attr_reader :identifier #: String?
11
+ attr_reader :identifier #: String? # @dynamic identifier
12
12
 
13
13
  # The configured model.
14
- attr_reader :model #: (String | Proc)?
14
+ attr_reader :model #: (String | Proc)? # @dynamic model
15
15
 
16
16
  # The configured instructions.
17
- attr_reader :instructions #: (String | Proc)?
17
+ attr_reader :instructions #: (String | Proc)? # @dynamic instructions
18
18
 
19
19
  # Options passed to generate_text/stream_text.
20
- attr_accessor :model_options #: Hash[Symbol, untyped]
20
+ attr_accessor :model_options #: Hash[Symbol, untyped] # @dynamic model_options, model_options=
21
21
 
22
22
  # The configured structured-output schema.
23
- attr_reader :structured_output #: Riffer::Params?
23
+ attr_reader :structured_output #: Riffer::Params? # @dynamic structured_output
24
24
 
25
25
  # The maximum number of LLM call steps in the tool-use loop.
26
- attr_accessor :max_steps #: Numeric?
26
+ attr_accessor :max_steps #: Numeric? # @dynamic max_steps, max_steps=
27
27
 
28
28
  # The configured tools.
29
- attr_accessor :tools_config #: (Array[singleton(Riffer::Tool)] | Proc)?
29
+ attr_accessor :tools_config #: (Array[singleton(Riffer::Tool)] | Proc)? # @dynamic tools_config, tools_config=
30
30
 
31
31
  # The accumulated +use_mcp+ tag configurations.
32
- attr_reader :mcp_configs #: Array[Hash[Symbol, untyped]]
32
+ attr_reader :mcp_configs #: Array[Hash[Symbol, untyped]] # @dynamic mcp_configs
33
33
 
34
34
  # The configured tool runtime.
35
- attr_reader :tool_runtime #: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
35
+ attr_reader :tool_runtime #: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc) # @dynamic tool_runtime
36
36
 
37
37
  # The configured skills.
38
- attr_accessor :skills_config #: Riffer::Skills::Config?
38
+ attr_accessor :skills_config #: Riffer::Skills::Config? # @dynamic skills_config, skills_config=
39
39
 
40
40
  # Registered guardrail entries keyed by phase.
41
- attr_reader :guardrails #: Hash[Symbol, Array[Hash[Symbol, untyped]]]
41
+ attr_reader :guardrails #: Hash[Symbol, Array[Hash[Symbol, untyped]]] # @dynamic guardrails
42
42
 
43
43
  # Builds a new Config. Raises Riffer::ArgumentError if +model+ or
44
44
  # +instructions+ is invalid (e.g. an empty string).
@@ -25,10 +25,10 @@ class Riffer::Agent::Outcome
25
25
  PROVIDER_STOP_REASONS).freeze #: Array[Symbol]
26
26
 
27
27
  # Why the run ended.
28
- attr_reader :reason #: Symbol
28
+ attr_reader :reason #: Symbol # @dynamic reason
29
29
 
30
30
  # Human-readable specifics for +reason+, when there are any.
31
- attr_reader :detail #: String?
31
+ attr_reader :detail #: String? # @dynamic detail
32
32
 
33
33
  # Raises Riffer::ArgumentError when +reason+ is outside VALUES.
34
34
  #--
@@ -13,33 +13,33 @@
13
13
  # end
14
14
  class Riffer::Agent::Response
15
15
  # The response content.
16
- attr_reader :content #: String
16
+ attr_reader :content #: String # @dynamic content
17
17
 
18
18
  # How the run ended.
19
- attr_reader :outcome #: Riffer::Agent::Outcome
19
+ attr_reader :outcome #: Riffer::Agent::Outcome # @dynamic outcome
20
20
 
21
21
  # The tripwire if execution was blocked.
22
- attr_reader :tripwire #: Riffer::Guardrails::Tripwire?
22
+ attr_reader :tripwire #: Riffer::Guardrails::Tripwire? # @dynamic tripwire
23
23
 
24
24
  # The modifications made by guardrails during processing.
25
- attr_reader :modifications #: Array[Riffer::Guardrails::Modification]
25
+ attr_reader :modifications #: Array[Riffer::Guardrails::Modification] # @dynamic modifications
26
26
 
27
27
  # The parsed structured output, if structured output was configured.
28
- attr_reader :structured_output #: Hash[Symbol, untyped]?
28
+ attr_reader :structured_output #: Hash[Symbol, untyped]? # @dynamic structured_output
29
29
 
30
30
  # The aggregate token usage across this run's LLM calls, if any was reported.
31
- attr_reader :token_usage #: Riffer::Providers::TokenUsage?
31
+ attr_reader :token_usage #: Riffer::Providers::TokenUsage? # @dynamic token_usage
32
32
 
33
33
  # The number of LLM calls made during this run (0 when a before-guardrail
34
34
  # blocks before any call). Distinct from the session's cumulative step count.
35
- attr_reader :steps #: Integer
35
+ attr_reader :steps #: Integer # @dynamic steps
36
36
 
37
37
  # The full message history from the agent conversation.
38
- attr_reader :messages #: Array[Riffer::Messages::Base]
38
+ attr_reader :messages #: Array[Riffer::Messages::Base] # @dynamic messages
39
39
 
40
40
  # Call ids of tool_use blocks riffer filled with placeholder results this
41
41
  # turn (when an interrupt left them unanswered and history healing is on).
42
- attr_reader :healed_tool_call_ids #: Array[String]
42
+ attr_reader :healed_tool_call_ids #: Array[String] # @dynamic healed_tool_call_ids
43
43
 
44
44
  #--
45
45
  #: (
@@ -461,7 +461,6 @@ module Riffer::Agent::Run
461
461
  #: (Hash[(String | Symbol), untyped]?) -> Hash[String, String]
462
462
  def normalize_tags(tags)
463
463
  return {} if tags.nil?
464
- raise Riffer::ArgumentError, "tags: must be a Hash, got #{tags.class}" unless tags.is_a?(Hash)
465
464
 
466
465
  result = {} #: Hash[String, String]
467
466
  tags.each do |key, value|
@@ -18,7 +18,7 @@ class Riffer::Agent::Session
18
18
  # @rbs @callbacks: Array[^(Riffer::Messages::Base) -> void]
19
19
 
20
20
  # The message history.
21
- attr_reader :messages #: Array[Riffer::Messages::Base]
21
+ attr_reader :messages #: Array[Riffer::Messages::Base] # @dynamic messages
22
22
 
23
23
  #--
24
24
  #: (?messages: Array[Riffer::Messages::Base]) -> void
@@ -4,10 +4,10 @@
4
4
  # Wraps the result of structured output parsing and validation.
5
5
  class Riffer::Agent::StructuredOutput::Result
6
6
  # The validated object, or +nil+ on failure.
7
- attr_reader :object #: Hash[Symbol, untyped]?
7
+ attr_reader :object #: Hash[Symbol, untyped]? # @dynamic object
8
8
 
9
9
  # The error message, or +nil+ on success.
10
- attr_reader :error #: String?
10
+ attr_reader :error #: String? # @dynamic error
11
11
 
12
12
  #--
13
13
  #: (?object: Hash[Symbol, untyped]?, ?error: String?) -> void
@@ -7,7 +7,7 @@ require "json"
7
7
  # schema.
8
8
  class Riffer::Agent::StructuredOutput
9
9
  # The schema parameters.
10
- attr_reader :params #: Riffer::Params
10
+ attr_reader :params #: Riffer::Params # @dynamic params
11
11
 
12
12
  #--
13
13
  #: (Riffer::Params) -> void
data/lib/riffer/agent.rb CHANGED
@@ -214,44 +214,44 @@ class Riffer::Agent
214
214
  end
215
215
 
216
216
  # The conversation handle.
217
- attr_reader :session #: Riffer::Agent::Session
217
+ attr_reader :session #: Riffer::Agent::Session # @dynamic session
218
218
 
219
219
  # The per-instance Riffer::Agent::Config.
220
- attr_reader :config #: Riffer::Agent::Config
220
+ attr_reader :config #: Riffer::Agent::Config # @dynamic config
221
221
 
222
222
  # The system message built from the configured +instructions+, or +nil+ when
223
223
  # none are configured.
224
- attr_reader :instruction_message #: Riffer::Messages::System?
224
+ attr_reader :instruction_message #: Riffer::Messages::System? # @dynamic instruction_message
225
225
 
226
226
  # The system message describing the configured skills catalog, or +nil+ when
227
227
  # skills are unconfigured or the catalog is empty.
228
- attr_reader :skills_message #: Riffer::Messages::System?
228
+ attr_reader :skills_message #: Riffer::Messages::System? # @dynamic skills_message
229
229
 
230
230
  # The mutable runtime context shared with every +Riffer::Agent::Run+ this
231
231
  # agent executes and threaded through all Proc-based settings.
232
- attr_reader :context #: Riffer::Agent::Context
232
+ attr_reader :context #: Riffer::Agent::Context # @dynamic context
233
233
 
234
234
  # The resolved provider name (the part before "/" in the model string),
235
235
  # e.g. +"openai"+.
236
- attr_reader :provider_name #: String
236
+ attr_reader :provider_name #: String # @dynamic provider_name
237
237
 
238
238
  # The resolved model name (the part after "/" in the model string), used as
239
239
  # the model argument on every LLM call.
240
- attr_reader :model_name #: String
240
+ attr_reader :model_name #: String # @dynamic model_name
241
241
 
242
242
  # The provider client. Public so tests can pre-queue responses on
243
243
  # +Riffer::Providers::Mock+ before calling +#generate+.
244
- attr_reader :provider #: Riffer::Providers::Base
244
+ attr_reader :provider #: Riffer::Providers::Base # @dynamic provider
245
245
 
246
246
  # The +Riffer::Agent::StructuredOutput+ wrapping the configured schema, or
247
247
  # +nil+ when not configured.
248
- attr_reader :structured_output #: Riffer::Agent::StructuredOutput?
248
+ attr_reader :structured_output #: Riffer::Agent::StructuredOutput? # @dynamic structured_output
249
249
 
250
250
  # The tool classes the LLM sees on every call this agent makes.
251
- attr_reader :tools #: Array[singleton(Riffer::Tool)]
251
+ attr_reader :tools #: Array[singleton(Riffer::Tool)] # @dynamic tools
252
252
 
253
253
  # The tool runtime instance used to execute tool calls.
254
- attr_reader :tool_runtime #: Riffer::Tools::Runtime
254
+ attr_reader :tool_runtime #: Riffer::Tools::Runtime # @dynamic tool_runtime
255
255
 
256
256
  # Initializes a new agent.
257
257
  #
data/lib/riffer/config.rb CHANGED
@@ -16,11 +16,11 @@ class Riffer::Config
16
16
  class Skills
17
17
  # The tool class the LLM calls to activate a skill; defaults to
18
18
  # <tt>Riffer::Skills::ActivateTool</tt>.
19
- attr_reader :default_activate_tool #: singleton(Riffer::Tool)
19
+ attr_reader :default_activate_tool #: singleton(Riffer::Tool) # @dynamic default_activate_tool
20
20
 
21
21
  # Default skills backend for agents that declare a +skills+ block without
22
22
  # one; defaults to +nil+.
23
- attr_reader :default_backend #: (Riffer::Skills::Backend | Proc)?
23
+ attr_reader :default_backend #: (Riffer::Skills::Backend | Proc)? # @dynamic default_backend
24
24
 
25
25
  #--
26
26
  #: () -> void
@@ -61,17 +61,17 @@ class Riffer::Config
61
61
  class Tracing
62
62
  # Whether riffer emits OTEL spans; defaults to +true+, a no-op until a
63
63
  # host wires an OTEL SDK.
64
- attr_reader :enabled #: bool
64
+ attr_reader :enabled #: bool # @dynamic enabled
65
65
 
66
66
  # Whether LLM-call spans capture full message content
67
67
  # (<tt>gen_ai.input.messages</tt>, <tt>gen_ai.output.messages</tt>,
68
68
  # <tt>gen_ai.system_instructions</tt>); defaults to +false+ — message
69
69
  # content routinely carries sensitive data.
70
- attr_reader :capture_messages #: bool
70
+ attr_reader :capture_messages #: bool # @dynamic capture_messages
71
71
 
72
72
  # The backend riffer routes spans through; defaults to +nil+, a no-op.
73
73
  # Riffer auto-detects no backend; assigning one is opt-in.
74
- attr_reader :backend #: untyped
74
+ attr_reader :backend #: untyped # @dynamic backend
75
75
 
76
76
  #--
77
77
  #: () -> void
@@ -118,17 +118,17 @@ class Riffer::Config
118
118
  # File-attachment-download policy for +Riffer::Messages::FilePart+ URL sources
119
119
  class Files
120
120
  # Allow file attachments to be downloaded to send to providers.
121
- attr_reader :allow_downloads #: bool
121
+ attr_reader :allow_downloads #: bool # @dynamic allow_downloads
122
122
  # Maximum file size to download before failing.
123
- attr_reader :max_bytes #: Integer
123
+ attr_reader :max_bytes #: Integer # @dynamic max_bytes
124
124
  # Maximum amount of time to spend downloading a file before failing.
125
- attr_reader :timeout #: Integer
125
+ attr_reader :timeout #: Integer # @dynamic timeout
126
126
  # Maximum number of files to include in an individual message.
127
- attr_reader :max_per_message #: Integer?
127
+ attr_reader :max_per_message #: Integer? # @dynamic max_per_message
128
128
  # Execution pattern for downloading files.
129
- attr_reader :runner #: Riffer::Runner
129
+ attr_reader :runner #: Riffer::Runner # @dynamic runner
130
130
  # The object used to fetch a URL source's bytes
131
- attr_reader :downloader #: untyped
131
+ attr_reader :downloader #: untyped # @dynamic downloader
132
132
 
133
133
  #--
134
134
  #: () -> void
@@ -215,16 +215,16 @@ class Riffer::Config
215
215
  # and +cache_write+ fall back to the +input+ rate when unset.
216
216
  class Rates
217
217
  # Input rate per million tokens.
218
- attr_reader :input #: Float
218
+ attr_reader :input #: Float # @dynamic input
219
219
 
220
220
  # Output rate per million tokens.
221
- attr_reader :output #: Float
221
+ attr_reader :output #: Float # @dynamic output
222
222
 
223
223
  # Cache-read rate per million tokens.
224
- attr_reader :cache_read #: Float?
224
+ attr_reader :cache_read #: Float? # @dynamic cache_read
225
225
 
226
226
  # Cache-write rate per million tokens.
227
- attr_reader :cache_write #: Float?
227
+ attr_reader :cache_write #: Float? # @dynamic cache_write
228
228
 
229
229
  #--
230
230
  #: (input: Float, output: Float, ?cache_read: Float?, ?cache_write: Float?) -> void
@@ -329,34 +329,34 @@ class Riffer::Config
329
329
  VALID_MESSAGE_ID_STRATEGIES = %i[none uuid uuidv7].freeze
330
330
 
331
331
  # Amazon Bedrock configuration.
332
- attr_reader :amazon_bedrock #: Riffer::Config::AmazonBedrock
332
+ attr_reader :amazon_bedrock #: Riffer::Config::AmazonBedrock # @dynamic amazon_bedrock
333
333
 
334
334
  # Anthropic configuration.
335
- attr_reader :anthropic #: Riffer::Config::Anthropic
335
+ attr_reader :anthropic #: Riffer::Config::Anthropic # @dynamic anthropic
336
336
 
337
337
  # Azure OpenAI configuration.
338
- attr_reader :azure_openai #: Riffer::Config::AzureOpenAI
338
+ attr_reader :azure_openai #: Riffer::Config::AzureOpenAI # @dynamic azure_openai
339
339
 
340
340
  # Google Gemini configuration.
341
- attr_reader :gemini #: Riffer::Config::Gemini
341
+ attr_reader :gemini #: Riffer::Config::Gemini # @dynamic gemini
342
342
 
343
343
  # OpenAI configuration.
344
- attr_reader :openai #: Riffer::Config::OpenAI
344
+ attr_reader :openai #: Riffer::Config::OpenAI # @dynamic openai
345
345
 
346
346
  # OpenRouter configuration.
347
- attr_reader :openrouter #: Riffer::Config::OpenRouter
347
+ attr_reader :openrouter #: Riffer::Config::OpenRouter # @dynamic openrouter
348
348
 
349
349
  # Evals configuration.
350
- attr_reader :evals #: Riffer::Config::Evals
350
+ attr_reader :evals #: Riffer::Config::Evals # @dynamic evals
351
351
 
352
352
  # MCP configuration. +credentials+ is an optional Proc returning per-run
353
353
  # +tools/call+ headers (or +nil+ to deny); +discovery_runner+ runs tool
354
354
  # discovery.
355
- attr_reader :mcp #: Riffer::Config::Mcp
355
+ attr_reader :mcp #: Riffer::Config::Mcp # @dynamic mcp
356
356
 
357
357
  # Global tool runtime configuration (experimental); defaults to
358
358
  # <tt>Riffer::Tools::Runtime::Inline.new</tt>.
359
- attr_reader :tool_runtime #: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
359
+ attr_reader :tool_runtime #: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc) # @dynamic tool_runtime
360
360
 
361
361
  # Sets the global tool runtime. Raises Riffer::ArgumentError on an invalid
362
362
  # value.
@@ -374,20 +374,20 @@ class Riffer::Config
374
374
  end
375
375
 
376
376
  # Skills-related global configuration.
377
- attr_reader :skills #: Riffer::Config::Skills
377
+ attr_reader :skills #: Riffer::Config::Skills # @dynamic skills
378
378
 
379
379
  # Tracing-related global configuration.
380
- attr_reader :tracing #: Riffer::Config::Tracing
380
+ attr_reader :tracing #: Riffer::Config::Tracing # @dynamic tracing
381
381
 
382
- attr_reader :files #: Riffer::Config::Files
382
+ attr_reader :files #: Riffer::Config::Files # @dynamic files
383
383
 
384
384
  # Consumer-configured per-model token pricing.
385
- attr_reader :pricing #: Riffer::Config::Pricing
385
+ attr_reader :pricing #: Riffer::Config::Pricing # @dynamic pricing
386
386
 
387
387
  # Strategy for auto-generating message ids: +:none+ (default), +:uuid+, or
388
388
  # +:uuidv7+. When not +:none+, messages get an +id+ at construction, and
389
389
  # seeded messages passed to +Riffer::Agent#generate+ must carry their own.
390
- attr_reader :message_id_strategy #: Symbol
390
+ attr_reader :message_id_strategy #: Symbol # @dynamic message_id_strategy
391
391
 
392
392
  # Sets the message id strategy. Raises Riffer::ArgumentError unless the value
393
393
  # is +:none+, +:uuid+, or +:uuidv7+.
@@ -404,7 +404,7 @@ class Riffer::Config
404
404
  # Experimental: when +true+, riffer maintains the +tool_use+ ↔ +tool_result+
405
405
  # invariant itself — stripping orphaned exchanges and filling interrupted
406
406
  # ones. Defaults to +false+; the surface may change without notice.
407
- attr_reader :experimental_history_healing #: bool
407
+ attr_reader :experimental_history_healing #: bool # @dynamic experimental_history_healing
408
408
 
409
409
  # Sets the +experimental_history_healing+ flag, coercing boolean-ish values so
410
410
  # an env-var +"false"+ (truthy in Ruby) doesn't silently enable healing.
@@ -28,7 +28,7 @@ class Riffer::Evals::Judge
28
28
  end
29
29
 
30
30
  # The model string (provider/model format).
31
- attr_reader :model #: String
31
+ attr_reader :model #: String # @dynamic model
32
32
 
33
33
  # Raises Riffer::ArgumentError unless +model+ is "provider/model" format.
34
34
  #--
@@ -109,8 +109,7 @@ class Riffer::Evals::Judge
109
109
  #--
110
110
  #: (Riffer::Messages::Assistant) -> Hash[Symbol, untyped]
111
111
  def parse_tool_response(response)
112
- tool_call = response.tool_calls.first
113
- raise Riffer::Error, "Invalid judge response: no tool call found" unless tool_call
112
+ tool_call = response.tool_calls.fetch(0) { raise Riffer::Error, "Invalid judge response: no tool call found" }
114
113
 
115
114
  parsed = JSON.parse(tool_call[:arguments], symbolize_names: true)
116
115
  score = parsed[:score]
@@ -4,23 +4,23 @@
4
4
  # Represents the result of a single evaluation.
5
5
  class Riffer::Evals::Result
6
6
  # The evaluator class that produced this result.
7
- attr_reader :evaluator #: singleton(Riffer::Evals::Evaluator)
7
+ attr_reader :evaluator #: singleton(Riffer::Evals::Evaluator) # @dynamic evaluator
8
8
 
9
9
  # The evaluation score (0.0 to 1.0).
10
- attr_reader :score #: Float
10
+ attr_reader :score #: Float # @dynamic score
11
11
 
12
12
  # Human-readable explanation of the score.
13
- attr_reader :reason #: String?
13
+ attr_reader :reason #: String? # @dynamic reason
14
14
 
15
15
  # Additional metadata from the evaluation.
16
- attr_reader :metadata #: Hash[Symbol, untyped]
16
+ attr_reader :metadata #: Hash[Symbol, untyped] # @dynamic metadata
17
17
 
18
18
  # Whether higher scores are better for this evaluator.
19
- attr_reader :higher_is_better #: bool
19
+ attr_reader :higher_is_better #: bool # @dynamic higher_is_better
20
20
 
21
21
  # Token usage for the judge call that produced this result, when the
22
22
  # evaluator used an LLM. Nil for rule-based evaluators.
23
- attr_reader :token_usage #: Riffer::Providers::TokenUsage?
23
+ attr_reader :token_usage #: Riffer::Providers::TokenUsage? # @dynamic token_usage
24
24
 
25
25
  # Raises Riffer::ArgumentError if +score+ is not between 0.0 and 1.0.
26
26
  #--
@@ -4,7 +4,7 @@
4
4
  # Represents the complete result of an evaluation run across multiple scenarios.
5
5
  class Riffer::Evals::RunResult
6
6
  # Per-scenario evaluation results.
7
- attr_reader :scenario_results #: Array[Riffer::Evals::ScenarioResult]
7
+ attr_reader :scenario_results #: Array[Riffer::Evals::ScenarioResult] # @dynamic scenario_results
8
8
 
9
9
  #--
10
10
  #: (scenario_results: Array[Riffer::Evals::ScenarioResult]) -> void
@@ -4,22 +4,22 @@
4
4
  # Represents the result of evaluating a single scenario.
5
5
  class Riffer::Evals::ScenarioResult
6
6
  # The input that was evaluated.
7
- attr_reader :input #: String
7
+ attr_reader :input #: String # @dynamic input
8
8
 
9
9
  # The agent output for this scenario.
10
- attr_reader :output #: String
10
+ attr_reader :output #: String # @dynamic output
11
11
 
12
12
  # The ground truth used during evaluation.
13
- attr_reader :ground_truth #: String?
13
+ attr_reader :ground_truth #: String? # @dynamic ground_truth
14
14
 
15
15
  # Individual evaluation results.
16
- attr_reader :results #: Array[Riffer::Evals::Result]
16
+ attr_reader :results #: Array[Riffer::Evals::Result] # @dynamic results
17
17
 
18
18
  # The full message history from the agent conversation.
19
- attr_reader :messages #: Array[Riffer::Messages::Base]
19
+ attr_reader :messages #: Array[Riffer::Messages::Base] # @dynamic messages
20
20
 
21
21
  # Token usage the agent under test spent generating this scenario's output.
22
- attr_reader :token_usage #: Riffer::Providers::TokenUsage?
22
+ attr_reader :token_usage #: Riffer::Providers::TokenUsage? # @dynamic token_usage
23
23
 
24
24
  #--
25
25
  #: (input: String, output: String, ground_truth: String?, results: Array[Riffer::Evals::Result], ?messages: Array[Riffer::Messages::Base], ?token_usage: Riffer::Providers::TokenUsage?) -> void
@@ -4,13 +4,13 @@
4
4
  # Records a guardrail transformation event.
5
5
  class Riffer::Guardrails::Modification
6
6
  # The guardrail class that transformed data.
7
- attr_reader :guardrail #: singleton(Riffer::Guardrail)
7
+ attr_reader :guardrail #: singleton(Riffer::Guardrail) # @dynamic guardrail
8
8
 
9
9
  # The phase when the transformation occurred (:before or :after).
10
- attr_reader :phase #: Symbol
10
+ attr_reader :phase #: Symbol # @dynamic phase
11
11
 
12
12
  # The indices of messages that were changed.
13
- attr_reader :message_indices #: Array[Integer]
13
+ attr_reader :message_indices #: Array[Integer] # @dynamic message_indices
14
14
 
15
15
  #--
16
16
  #: (guardrail: singleton(Riffer::Guardrail), phase: Symbol, message_indices: Array[Integer]) -> void
@@ -7,13 +7,13 @@ class Riffer::Guardrails::Result
7
7
  TYPES = %i[pass transform block].freeze #: Array[Symbol]
8
8
 
9
9
  # The result type (:pass, :transform, or :block).
10
- attr_reader :type #: Symbol
10
+ attr_reader :type #: Symbol # @dynamic type
11
11
 
12
12
  # The data (for pass/transform) or reason (for block).
13
- attr_reader :data #: untyped
13
+ attr_reader :data #: untyped # @dynamic data
14
14
 
15
15
  # Optional metadata for block results.
16
- attr_reader :metadata #: Hash[Symbol, untyped]?
16
+ attr_reader :metadata #: Hash[Symbol, untyped]? # @dynamic metadata
17
17
 
18
18
  class << self
19
19
  # Creates a pass result that continues with unchanged data.