riffer 0.43.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +15 -0
  4. data/Steepfile +1 -0
  5. data/docs/AGENTS.md +23 -2
  6. data/docs/CONFIGURATION.md +32 -0
  7. data/docs/MESSAGES.md +22 -10
  8. data/docs/OVERVIEW.md +1 -0
  9. data/docs/TESTING.md +129 -0
  10. data/docs/TOOLS.md +23 -2
  11. data/docs/TRACING.md +1 -1
  12. data/docs/providers/AMAZON_BEDROCK.md +17 -0
  13. data/docs/providers/CUSTOM_PROVIDERS.md +1 -1
  14. data/docs/providers/GEMINI.md +2 -3
  15. data/docs-site/manifest.yml +3 -0
  16. data/lib/riffer/config.rb +94 -0
  17. data/lib/riffer/files/downloader.rb +78 -0
  18. data/lib/riffer/files/resolver.rb +97 -0
  19. data/lib/riffer/files.rb +5 -0
  20. data/lib/riffer/helpers/identifier.rb +11 -3
  21. data/lib/riffer/messages/file_part.rb +61 -9
  22. data/lib/riffer/providers/amazon_bedrock.rb +10 -7
  23. data/lib/riffer/providers/anthropic.rb +4 -0
  24. data/lib/riffer/providers/base.rb +8 -0
  25. data/lib/riffer/providers/finish_reason.rb +1 -1
  26. data/lib/riffer/providers/gemini.rb +16 -6
  27. data/lib/riffer/providers/open_ai.rb +36 -23
  28. data/lib/riffer/providers/open_router.rb +27 -7
  29. data/lib/riffer/registrable.rb +105 -22
  30. data/lib/riffer/testing/minitest.rb +21 -0
  31. data/lib/riffer/testing/rspec.rb +11 -0
  32. data/lib/riffer/testing.rb +130 -0
  33. data/lib/riffer/version.rb +1 -1
  34. data/lib/riffer.rb +31 -0
  35. data/sig/_private/minitest.rbs +9 -0
  36. data/sig/_private/riffer/testing/minitest.rbs +6 -0
  37. data/sig/_private/rspec.rbs +7 -0
  38. data/sig/_private/zeitwerk.rbs +2 -0
  39. data/sig/generated/riffer/config.rbs +64 -0
  40. data/sig/generated/riffer/files/downloader.rbs +16 -0
  41. data/sig/generated/riffer/files/resolver.rbs +36 -0
  42. data/sig/generated/riffer/files.rbs +4 -0
  43. data/sig/generated/riffer/helpers/identifier.rbs +8 -0
  44. data/sig/generated/riffer/messages/file_part.rbs +38 -6
  45. data/sig/generated/riffer/providers/amazon_bedrock.rbs +4 -0
  46. data/sig/generated/riffer/providers/base.rbs +4 -0
  47. data/sig/generated/riffer/providers/gemini.rbs +4 -0
  48. data/sig/generated/riffer/providers/open_ai.rbs +12 -3
  49. data/sig/generated/riffer/providers/open_router.rbs +14 -2
  50. data/sig/generated/riffer/registrable.rbs +57 -9
  51. data/sig/generated/riffer/testing/rspec.rbs +2 -0
  52. data/sig/generated/riffer/testing.rbs +75 -0
  53. data/sig/generated/riffer.rbs +32 -0
  54. data/sig/manifest.yaml +1 -0
  55. data/sig/manual/riffer/testing.rbs +5 -0
  56. metadata +31 -1
@@ -11,6 +11,10 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
11
11
  # : () -> String
12
12
  def self.semconv_provider_name: () -> String
13
13
 
14
+ # --
15
+ # : (Riffer::Messages::FilePart) -> Symbol
16
+ def file_delivery: (Riffer::Messages::FilePart) -> Symbol
17
+
14
18
  private
15
19
 
16
20
  # --
@@ -4,6 +4,11 @@
4
4
  class Riffer::Providers::OpenAI < Riffer::Providers::Base
5
5
  WEB_SEARCH_TOOL_TYPE: String
6
6
 
7
+ # The Responses API has no finish_reason field. The response +status+ is
8
+ # the primary signal; an +incomplete+ status is only meaningful together
9
+ # with <tt>incomplete_details.reason</tt>, so that branch nests one level.
10
+ FINISH_REASONS: Hash[String, Symbol | Hash[String, Symbol]]
11
+
7
12
  # The GenAI semconv well-known provider name.
8
13
  # --
9
14
  # : () -> String
@@ -13,6 +18,10 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
13
18
  # : () -> void
14
19
  def initialize: () -> void
15
20
 
21
+ # --
22
+ # : (Riffer::Messages::FilePart) -> Symbol
23
+ def file_delivery: (Riffer::Messages::FilePart) -> Symbol
24
+
16
25
  private
17
26
 
18
27
  # --
@@ -46,14 +55,14 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
46
55
  # : (untyped) -> Riffer::Providers::FinishReason?
47
56
  def extract_finish_reason: (untyped) -> Riffer::Providers::FinishReason?
48
57
 
49
- # The Responses API reports no finish_reason field, so one is derived.
50
58
  # --
51
59
  # : (untyped) -> Riffer::Providers::FinishReason?
52
60
  def build_finish_reason: (untyped) -> Riffer::Providers::FinishReason?
53
61
 
62
+ # The nested field that names the cause behind an ambiguous status.
54
63
  # --
55
- # : (untyped) -> Riffer::Providers::FinishReason
56
- def incomplete_finish_reason: (untyped) -> Riffer::Providers::FinishReason
64
+ # : (untyped, String) -> String?
65
+ def finish_detail: (untyped, String) -> String?
57
66
 
58
67
  # --
59
68
  # : (untyped) -> String
@@ -18,6 +18,10 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
18
18
  # : () -> void
19
19
  def initialize: () -> void
20
20
 
21
+ # --
22
+ # : (Riffer::Messages::FilePart) -> Symbol
23
+ def file_delivery: (Riffer::Messages::FilePart) -> Symbol
24
+
21
25
  private
22
26
 
23
27
  # --
@@ -53,9 +57,17 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
53
57
  # : (untyped) -> Riffer::Providers::FinishReason?
54
58
  def extract_finish_reason: (untyped) -> Riffer::Providers::FinishReason?
55
59
 
60
+ # +native+ is the upstream model's own finish reason, which OpenRouter
61
+ # reports alongside its normalized one; it wins as +raw+ when present.
56
62
  # --
57
- # : (untyped) -> Riffer::Providers::FinishReason?
58
- def build_finish_reason: (untyped) -> Riffer::Providers::FinishReason?
63
+ # : (untyped, ?native: untyped) -> Riffer::Providers::FinishReason?
64
+ def build_finish_reason: (untyped, ?native: untyped) -> Riffer::Providers::FinishReason?
65
+
66
+ # +native_finish_reason+ is outside the OpenAI schema, so it is only
67
+ # reachable through the SDK model's raw data hash.
68
+ # --
69
+ # : (untyped) -> untyped
70
+ def native_finish_reason: (untyped) -> untyped
59
71
 
60
72
  # --
61
73
  # : (untyped) -> String
@@ -1,7 +1,11 @@
1
1
  # Generated from lib/riffer/registrable.rb with RBS::Inline
2
2
 
3
- # Registry of a class's named direct subclasses, keyed by identifier. Extend it
3
+ # Registry of a class's direct subclasses, keyed by identifier. Extend it
4
4
  # onto a base class to look up subclasses in constant time via +find+ and +all+.
5
+ # Subclasses join implicitly by inheriting; +register+ adds one explicitly, for
6
+ # ephemeral classes a test suite builds and tears down. Registration is not
7
+ # synchronized — register during boot or from a single-threaded test, before
8
+ # concurrent lookups begin.
5
9
  #
6
10
  # class Riffer::Tool
7
11
  # extend Riffer::Registrable
@@ -13,25 +17,47 @@
13
17
  module Riffer::Registrable : Class
14
18
  @identifier_registry: Hash[String, Class]?
15
19
 
20
+ @explicit_registrations: Hash[String, Class]?
21
+
16
22
  # Finds a registered subclass by identifier, or +nil+ when none matches.
17
- # Only *named direct* subclasses are registered: grandchildren are not
18
- # visible to a grandparent's +find+ (call +find+ on their direct parent
19
- # instead), anonymous classes are never registered, and duplicate identifiers
20
- # raise Riffer::DuplicateIdentifierError at first lookup.
23
+ # Implicit registration covers only *named direct* subclasses: grandchildren
24
+ # are not visible to a grandparent's +find+ (call +find+ on their direct
25
+ # parent instead), anonymous classes are never registered implicitly, and a
26
+ # subclass whose name no longer resolves back to it is dropped at the next
27
+ # registry rebuild. Duplicate identifiers raise
28
+ # Riffer::DuplicateIdentifierError at first lookup.
21
29
  #
22
30
  # --
23
31
  # : (String | Symbol) -> Class?
24
32
  def find: (String | Symbol) -> Class?
25
33
 
26
- # Returns all registered subclasses. Only *named direct* subclasses are
27
- # registered: grandchildren are not included (call +all+ on their direct
28
- # parent instead), anonymous classes are never registered, and duplicate
29
- # identifiers raise Riffer::DuplicateIdentifierError at first lookup.
34
+ # Returns all registered subclasses, implicit and explicit. Carries the same
35
+ # registration rules as +find+.
30
36
  #
31
37
  # --
32
38
  # : () -> Array[Class]
33
39
  def all: () -> Array[Class]
34
40
 
41
+ # Registers a direct subclass under its +identifier+, whether or not it is
42
+ # named — unlike implicit registration, it survives a name that no longer
43
+ # resolves, so an ephemeral class stays findable until +unregister+. Prefer
44
+ # Riffer::Testing for ordinary test setup, which stubs and cleans up
45
+ # automatically.
46
+ #
47
+ # Raises Riffer::ArgumentError when the identifier is blank or the class is
48
+ # not a direct subclass, and Riffer::DuplicateIdentifierError when the
49
+ # identifier is already taken — including by this same class.
50
+ #
51
+ # --
52
+ # : (Class) -> void
53
+ def register: (Class) -> void
54
+
55
+ # Removes an explicit registration of +klass+, leaving implicit registrations
56
+ # untouched.
57
+ # --
58
+ # : (Class) -> void
59
+ def unregister: (Class) -> void
60
+
35
61
  private
36
62
 
37
63
  # Ruby invokes +inherited+ with +self+ bound to the direct superclass — the
@@ -45,7 +71,29 @@ module Riffer::Registrable : Class
45
71
  # : () -> Hash[String, Class]
46
72
  def identifier_registry: () -> Hash[String, Class]
47
73
 
74
+ # --
75
+ # : () -> Hash[String, Class]
76
+ def explicit_registrations: () -> Hash[String, Class]
77
+
48
78
  # --
49
79
  # : () -> Hash[String, Class]
50
80
  def build_identifier_registry: () -> Hash[String, Class]
81
+
82
+ # Class#subclasses keeps returning superseded generations of a reloaded or
83
+ # stubbed class, so a subclass counts only while its own name still resolves
84
+ # back to it. An anonymous class has no name to resolve and is skipped even
85
+ # with an explicit identifier — the MCP factory and serializer shells
86
+ # synthesize short-lived anonymous classes whose registration would flake
87
+ # with GC timing.
88
+ # --
89
+ # : (Class) -> bool
90
+ def live?: (Class) -> bool
91
+
92
+ # --
93
+ # : (Class) -> String
94
+ def identifier_key: (Class) -> String
95
+
96
+ # --
97
+ # : (String, Class, Class) -> void
98
+ def raise_duplicate_identifier!: (String, Class, Class) -> void
51
99
  end
@@ -0,0 +1,2 @@
1
+ # Generated from lib/riffer/testing/rspec.rb with RBS::Inline
2
+
@@ -0,0 +1,75 @@
1
+ # Generated from lib/riffer/testing.rb with RBS::Inline
2
+
3
+ # Builds throwaway agents and tools a test suite can resolve by identifier or
4
+ # constant name, and removes them again. Require <tt>riffer/testing/rspec</tt>
5
+ # or <tt>riffer/testing/minitest</tt> to get +stub_agent+/+stub_tool+ in every
6
+ # example plus per-test cleanup; otherwise include this module and call +reset!+
7
+ # from your own teardown, or call the methods on the module directly.
8
+ #
9
+ # Tracking is not synchronized — stub from a single-threaded test, before
10
+ # concurrent lookups begin.
11
+ module Riffer::Testing
12
+ self.@registrations: Array[[ Class, String? ]]?
13
+
14
+ CONST_NAME_PATTERN: Regexp
15
+
16
+ # Builds an agent class, evaluates the optional body in it, and makes it
17
+ # resolvable until the next +reset!+. A +name+ assigns a top-level constant
18
+ # and derives the identifier; the body may set +identifier+ like any other
19
+ # agent config.
20
+ #
21
+ # agent = stub_agent("SupportAgent") { model "mock/gpt-5-mini" }
22
+ #
23
+ # Raises Riffer::ArgumentError when the constant is already defined or the
24
+ # stub ends up with no identifier, and Riffer::DuplicateIdentifierError when
25
+ # another agent already holds the identifier.
26
+ #
27
+ # --
28
+ # : (?(String | Symbol)?, ?base: singleton(Riffer::Agent)) ?{ () [self: singleton(Riffer::Agent)] -> void } -> singleton(Riffer::Agent)
29
+ def stub_agent: (?(String | Symbol)?, ?base: singleton(Riffer::Agent)) ?{ () [self: singleton(Riffer::Agent)] -> void } -> singleton(Riffer::Agent)
30
+
31
+ # Builds a tool class, evaluates the optional body in it, and makes it
32
+ # resolvable until the next +reset!+. A +name+ assigns a top-level constant
33
+ # and derives the identifier; the body may set +identifier+ like any other
34
+ # tool config.
35
+ #
36
+ # tool = stub_tool("KbSearch") { def call(context:, **) = text("stubbed") }
37
+ #
38
+ # Raises Riffer::ArgumentError when the constant is already defined or the
39
+ # stub ends up with no identifier, and Riffer::DuplicateIdentifierError when
40
+ # another tool already holds the identifier.
41
+ #
42
+ # --
43
+ # : (?(String | Symbol)?, ?base: singleton(Riffer::Tool)) ?{ () [self: singleton(Riffer::Tool)] -> void } -> singleton(Riffer::Tool)
44
+ def stub_tool: (?(String | Symbol)?, ?base: singleton(Riffer::Tool)) ?{ () [self: singleton(Riffer::Tool)] -> void } -> singleton(Riffer::Tool)
45
+
46
+ # Removes every stub built since the last reset — its registration and any
47
+ # constant it created — newest first, and forgets them. A no-op when nothing
48
+ # has been stubbed.
49
+ # --
50
+ # : () -> void
51
+ def reset!: () -> void
52
+
53
+ # The stub classes awaiting cleanup. Lives on the module rather than the
54
+ # caller so an including test case and a direct
55
+ # <tt>Riffer::Testing.stub_*</tt> call share one list.
56
+ # --
57
+ # : () -> Array[[Class, String?]]
58
+ def self.registrations: () -> Array[[ Class, String? ]]
59
+
60
+ private
61
+
62
+ # --
63
+ # : ((String | Symbol)?, base: Class) ?{ () [self: untyped] -> void } -> Class
64
+ def build_stub: ((String | Symbol)?, base: Class) ?{ () [self: untyped] -> void } -> Class
65
+
66
+ # --
67
+ # : ((String | Symbol)?) -> String?
68
+ def validate_const_name: ((String | Symbol)?) -> String?
69
+
70
+ # A test may have removed or replaced the constant itself, so never clobber
71
+ # one that no longer points at the stub.
72
+ # --
73
+ # : (String, Class) -> void
74
+ def remove_stub_const: (String, Class) -> void
75
+ end
@@ -25,6 +25,38 @@ module Riffer
25
25
  class ToolExecutionError < Error
26
26
  end
27
27
 
28
+ # Base error for a file attachment that can't be resolved for the provider.
29
+ class FileError < Error
30
+ end
31
+
32
+ # Raised when the provider can't accept the file at all.
33
+ class FileUnsupportedError < FileError
34
+ end
35
+
36
+ # Raised when a download is required but Riffer.config.files.allow_downloads is false.
37
+ class FileDownloadsDisabledError < FileError
38
+ end
39
+
40
+ # Raised when a message carries more files than Riffer.config.files.max_per_message allows.
41
+ class TooManyFilesError < FileError
42
+ end
43
+
44
+ # Raised when there's an issue downloading a file
45
+ class FileDownloadError < FileError
46
+ end
47
+
48
+ # Raised when a file size exceeds Riffer.config.files.max_bytes
49
+ class FileTooLargeError < FileError
50
+ end
51
+
52
+ # Raised when a downloaded or inline file's sha256 doesn't match.
53
+ class FileChecksumMismatchError < FileError
54
+ end
55
+
56
+ # Raised when a FilePart's data isn't valid base64
57
+ class FileEncodingError < FileError
58
+ end
59
+
28
60
  # Raised when two registered subclasses share the same identifier.
29
61
  class DuplicateIdentifierError < Error
30
62
  end
data/sig/manifest.yaml CHANGED
@@ -1,3 +1,4 @@
1
1
  dependencies:
2
2
  - name: uri
3
3
  - name: net-http
4
+ - name: digest
@@ -0,0 +1,5 @@
1
+ # `Riffer::Testing` uses `extend self`; rbs-inline doesn't emit that, so
2
+ # re-extend here to expose its instance methods as singleton methods.
3
+ module Riffer::Testing
4
+ extend ::Riffer::Testing
5
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: riffer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.43.0
4
+ version: 0.45.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Bottrall
@@ -29,6 +29,20 @@ dependencies:
29
29
  - - ">="
30
30
  - !ruby/object:Gem::Version
31
31
  version: 2.6.0
32
+ - !ruby/object:Gem::Dependency
33
+ name: base64
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
32
46
  description: Riffer is a comprehensive Ruby framework designed to simplify the development
33
47
  of AI-powered applications and agents. It provides a complete toolkit for integrating
34
48
  artificial intelligence capabilities into your Ruby projects.
@@ -79,6 +93,7 @@ files:
79
93
  - docs/SERIALIZATION.md
80
94
  - docs/SKILLS.md
81
95
  - docs/STREAM_EVENTS.md
96
+ - docs/TESTING.md
82
97
  - docs/TOOLS.md
83
98
  - docs/TOOL_ADVANCED.md
84
99
  - docs/TRACING.md
@@ -110,6 +125,9 @@ files:
110
125
  - lib/riffer/evals/result.rb
111
126
  - lib/riffer/evals/run_result.rb
112
127
  - lib/riffer/evals/scenario_result.rb
128
+ - lib/riffer/files.rb
129
+ - lib/riffer/files/downloader.rb
130
+ - lib/riffer/files/resolver.rb
113
131
  - lib/riffer/guardrail.rb
114
132
  - lib/riffer/guardrails.rb
115
133
  - lib/riffer/guardrails/modification.rb
@@ -184,6 +202,9 @@ files:
184
202
  - lib/riffer/stream_events/tool_call_done.rb
185
203
  - lib/riffer/stream_events/web_search_done.rb
186
204
  - lib/riffer/stream_events/web_search_status.rb
205
+ - lib/riffer/testing.rb
206
+ - lib/riffer/testing/minitest.rb
207
+ - lib/riffer/testing/rspec.rb
187
208
  - lib/riffer/tool.rb
188
209
  - lib/riffer/tools.rb
189
210
  - lib/riffer/tools/response.rb
@@ -203,6 +224,7 @@ files:
203
224
  - sig/_private/aws-sdk-core/seahorse_request_context.rbs
204
225
  - sig/_private/aws-sdk-core/static_token_provider.rbs
205
226
  - sig/_private/mcp.rbs
227
+ - sig/_private/minitest.rbs
206
228
  - sig/_private/openai.rbs
207
229
  - sig/_private/opentelemetry.rbs
208
230
  - sig/_private/riffer/providers/amazon_bedrock.rbs
@@ -210,6 +232,8 @@ files:
210
232
  - sig/_private/riffer/providers/gemini.rbs
211
233
  - sig/_private/riffer/providers/open_ai.rbs
212
234
  - sig/_private/riffer/providers/open_router.rbs
235
+ - sig/_private/riffer/testing/minitest.rbs
236
+ - sig/_private/rspec.rbs
213
237
  - sig/_private/zeitwerk.rbs
214
238
  - sig/generated/riffer.rbs
215
239
  - sig/generated/riffer/agent.rbs
@@ -230,6 +254,9 @@ files:
230
254
  - sig/generated/riffer/evals/result.rbs
231
255
  - sig/generated/riffer/evals/run_result.rbs
232
256
  - sig/generated/riffer/evals/scenario_result.rbs
257
+ - sig/generated/riffer/files.rbs
258
+ - sig/generated/riffer/files/downloader.rbs
259
+ - sig/generated/riffer/files/resolver.rbs
233
260
  - sig/generated/riffer/guardrail.rbs
234
261
  - sig/generated/riffer/guardrails.rbs
235
262
  - sig/generated/riffer/guardrails/modification.rbs
@@ -304,6 +331,8 @@ files:
304
331
  - sig/generated/riffer/stream_events/tool_call_done.rbs
305
332
  - sig/generated/riffer/stream_events/web_search_done.rbs
306
333
  - sig/generated/riffer/stream_events/web_search_status.rbs
334
+ - sig/generated/riffer/testing.rbs
335
+ - sig/generated/riffer/testing/rspec.rbs
307
336
  - sig/generated/riffer/tool.rbs
308
337
  - sig/generated/riffer/tools.rbs
309
338
  - sig/generated/riffer/tools/response.rbs
@@ -335,6 +364,7 @@ files:
335
364
  - sig/manual/riffer/mcp/tool_factory.rbs
336
365
  - sig/manual/riffer/providers.rbs
337
366
  - sig/manual/riffer/providers/repository.rbs
367
+ - sig/manual/riffer/testing.rbs
338
368
  - sig/manual/riffer/tool.rbs
339
369
  - sig/manual/riffer/tracing.rbs
340
370
  - sig/manual/riffer/tracing/capture.rbs