riffer 0.43.0 → 0.44.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/CHANGELOG.md +8 -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 +10 -0
  8. data/docs/OVERVIEW.md +1 -0
  9. data/docs/TESTING.md +129 -0
  10. data/docs/TOOLS.md +23 -2
  11. data/docs/providers/AMAZON_BEDROCK.md +17 -0
  12. data/docs/providers/GEMINI.md +2 -3
  13. data/docs-site/manifest.yml +3 -0
  14. data/lib/riffer/config.rb +94 -0
  15. data/lib/riffer/files/downloader.rb +78 -0
  16. data/lib/riffer/files/resolver.rb +97 -0
  17. data/lib/riffer/files.rb +5 -0
  18. data/lib/riffer/helpers/identifier.rb +11 -3
  19. data/lib/riffer/messages/file_part.rb +61 -9
  20. data/lib/riffer/providers/amazon_bedrock.rb +7 -7
  21. data/lib/riffer/providers/base.rb +8 -0
  22. data/lib/riffer/providers/gemini.rb +6 -5
  23. data/lib/riffer/providers/open_ai.rb +6 -0
  24. data/lib/riffer/providers/open_router.rb +6 -0
  25. data/lib/riffer/registrable.rb +105 -22
  26. data/lib/riffer/testing/minitest.rb +21 -0
  27. data/lib/riffer/testing/rspec.rb +11 -0
  28. data/lib/riffer/testing.rb +130 -0
  29. data/lib/riffer/version.rb +1 -1
  30. data/lib/riffer.rb +31 -0
  31. data/sig/_private/minitest.rbs +9 -0
  32. data/sig/_private/riffer/testing/minitest.rbs +6 -0
  33. data/sig/_private/rspec.rbs +7 -0
  34. data/sig/_private/zeitwerk.rbs +2 -0
  35. data/sig/generated/riffer/config.rbs +64 -0
  36. data/sig/generated/riffer/files/downloader.rbs +16 -0
  37. data/sig/generated/riffer/files/resolver.rbs +36 -0
  38. data/sig/generated/riffer/files.rbs +4 -0
  39. data/sig/generated/riffer/helpers/identifier.rbs +8 -0
  40. data/sig/generated/riffer/messages/file_part.rbs +38 -6
  41. data/sig/generated/riffer/providers/amazon_bedrock.rbs +4 -0
  42. data/sig/generated/riffer/providers/base.rbs +4 -0
  43. data/sig/generated/riffer/providers/gemini.rbs +4 -0
  44. data/sig/generated/riffer/providers/open_ai.rbs +4 -0
  45. data/sig/generated/riffer/providers/open_router.rbs +4 -0
  46. data/sig/generated/riffer/registrable.rbs +57 -9
  47. data/sig/generated/riffer/testing/rspec.rbs +2 -0
  48. data/sig/generated/riffer/testing.rbs +75 -0
  49. data/sig/generated/riffer.rbs +32 -0
  50. data/sig/manifest.yaml +1 -0
  51. data/sig/manual/riffer/testing.rbs +5 -0
  52. metadata +31 -1
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ # Builds throwaway agents and tools a test suite can resolve by identifier or
5
+ # constant name, and removes them again. Require <tt>riffer/testing/rspec</tt>
6
+ # or <tt>riffer/testing/minitest</tt> to get +stub_agent+/+stub_tool+ in every
7
+ # example plus per-test cleanup; otherwise include this module and call +reset!+
8
+ # from your own teardown, or call the methods on the module directly.
9
+ #
10
+ # Tracking is not synchronized — stub from a single-threaded test, before
11
+ # concurrent lookups begin.
12
+ module Riffer::Testing
13
+ extend self
14
+
15
+ # @rbs self.@registrations: Array[[Class, String?]]?
16
+
17
+ CONST_NAME_PATTERN = /\A[A-Z][A-Za-z0-9_]*\z/ #: Regexp
18
+ private_constant :CONST_NAME_PATTERN
19
+
20
+ # Builds an agent class, evaluates the optional body in it, and makes it
21
+ # resolvable until the next +reset!+. A +name+ assigns a top-level constant
22
+ # and derives the identifier; the body may set +identifier+ like any other
23
+ # agent config.
24
+ #
25
+ # agent = stub_agent("SupportAgent") { model "mock/gpt-5-mini" }
26
+ #
27
+ # Raises Riffer::ArgumentError when the constant is already defined or the
28
+ # stub ends up with no identifier, and Riffer::DuplicateIdentifierError when
29
+ # another agent already holds the identifier.
30
+ #
31
+ #--
32
+ #: (?(String | Symbol)?, ?base: singleton(Riffer::Agent)) ?{ () [self: singleton(Riffer::Agent)] -> void } -> singleton(Riffer::Agent)
33
+ def stub_agent(name = nil, base: Riffer::Agent, &body)
34
+ build_stub(name, base: base, &body) #: singleton(Riffer::Agent)
35
+ end
36
+
37
+ # Builds a tool class, evaluates the optional body in it, and makes it
38
+ # resolvable until the next +reset!+. A +name+ assigns a top-level constant
39
+ # and derives the identifier; the body may set +identifier+ like any other
40
+ # tool config.
41
+ #
42
+ # tool = stub_tool("KbSearch") { def call(context:, **) = text("stubbed") }
43
+ #
44
+ # Raises Riffer::ArgumentError when the constant is already defined or the
45
+ # stub ends up with no identifier, and Riffer::DuplicateIdentifierError when
46
+ # another tool already holds the identifier.
47
+ #
48
+ #--
49
+ #: (?(String | Symbol)?, ?base: singleton(Riffer::Tool)) ?{ () [self: singleton(Riffer::Tool)] -> void } -> singleton(Riffer::Tool)
50
+ def stub_tool(name = nil, base: Riffer::Tool, &body)
51
+ build_stub(name, base: base, &body) #: singleton(Riffer::Tool)
52
+ end
53
+
54
+ # Removes every stub built since the last reset — its registration and any
55
+ # constant it created — newest first, and forgets them. A no-op when nothing
56
+ # has been stubbed.
57
+ #--
58
+ #: () -> void
59
+ def reset!
60
+ tracked = Riffer::Testing.registrations
61
+ tracked.reverse_each do |stub, const_name|
62
+ registrable = stub.superclass #: untyped
63
+ registrable.unregister(stub)
64
+ remove_stub_const(const_name, stub) if const_name
65
+ end
66
+ tracked.clear
67
+ end
68
+
69
+ # The stub classes awaiting cleanup. Lives on the module rather than the
70
+ # caller so an including test case and a direct
71
+ # <tt>Riffer::Testing.stub_*</tt> call share one list.
72
+ #--
73
+ #: () -> Array[[Class, String?]]
74
+ def self.registrations # :nodoc:
75
+ @registrations ||= []
76
+ end
77
+
78
+ private
79
+
80
+ #--
81
+ #: ((String | Symbol)?, base: Class) ?{ () [self: untyped] -> void } -> Class
82
+ def build_stub(name, base:, &body)
83
+ const_name = validate_const_name(name)
84
+ stub = Class.new(base)
85
+ configurable = stub #: untyped
86
+ configurable.identifier(Riffer::Helpers::Identifier.derive(const_name)) if const_name
87
+ configurable.class_eval(&body) if body
88
+ if configurable.identifier.to_s.strip.empty?
89
+ raise Riffer::ArgumentError, "a stub needs a name, or an identifier set in its block"
90
+ end
91
+
92
+ # The class must be registered while still anonymous: naming it first makes
93
+ # it implicitly live, and +register+ rejects an identifier the registry
94
+ # already resolves — even to this same class.
95
+ registrable = base #: untyped
96
+ registrable.register(stub)
97
+ Object.const_set(const_name, stub) if const_name
98
+ Riffer::Testing.registrations << [stub, const_name]
99
+
100
+ stub
101
+ end
102
+
103
+ #--
104
+ #: ((String | Symbol)?) -> String?
105
+ def validate_const_name(name)
106
+ return nil if name.nil?
107
+
108
+ const_name = name.to_s
109
+ unless CONST_NAME_PATTERN.match?(const_name)
110
+ raise Riffer::ArgumentError, "#{const_name.inspect} is not a simple top-level constant name"
111
+ end
112
+
113
+ # True of a pending autoload too — the name is taken either way.
114
+ if Object.const_defined?(const_name, false)
115
+ raise Riffer::ArgumentError, "#{const_name} is already defined; use stub_const to replace a real class"
116
+ end
117
+
118
+ const_name
119
+ end
120
+
121
+ # A test may have removed or replaced the constant itself, so never clobber
122
+ # one that no longer points at the stub.
123
+ #--
124
+ #: (String, Class) -> void
125
+ def remove_stub_const(const_name, stub)
126
+ return unless Object.const_defined?(const_name, false) && Object.const_get(const_name, false).equal?(stub)
127
+
128
+ Object.send(:remove_const, const_name)
129
+ end
130
+ end
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module Riffer
5
- VERSION = "0.43.0" #: String
5
+ VERSION = "0.44.0" #: String
6
6
  end
data/lib/riffer.rb CHANGED
@@ -10,6 +10,13 @@ loader.inflector.inflect(
10
10
  "azure_open_ai" => "AzureOpenAI",
11
11
  "open_router" => "OpenRouter",
12
12
  )
13
+ # Test-framework wiring a consumer requires by hand; neither file defines the
14
+ # constant its path implies, and both reference framework constants riffer
15
+ # never loads itself.
16
+ loader.ignore(
17
+ "#{__dir__}/riffer/testing/rspec.rb",
18
+ "#{__dir__}/riffer/testing/minitest.rb",
19
+ )
13
20
  loader.setup
14
21
 
15
22
  module Riffer
@@ -34,6 +41,30 @@ module Riffer
34
41
  # Raised when a tool encounters an expected execution error.
35
42
  class ToolExecutionError < Error; end
36
43
 
44
+ # Base error for a file attachment that can't be resolved for the provider.
45
+ class FileError < Error; end
46
+
47
+ # Raised when the provider can't accept the file at all.
48
+ class FileUnsupportedError < FileError; end
49
+
50
+ # Raised when a download is required but Riffer.config.files.allow_downloads is false.
51
+ class FileDownloadsDisabledError < FileError; end
52
+
53
+ # Raised when a message carries more files than Riffer.config.files.max_per_message allows.
54
+ class TooManyFilesError < FileError; end
55
+
56
+ # Raised when there's an issue downloading a file
57
+ class FileDownloadError < FileError; end
58
+
59
+ # Raised when a file size exceeds Riffer.config.files.max_bytes
60
+ class FileTooLargeError < FileError; end
61
+
62
+ # Raised when a downloaded or inline file's sha256 doesn't match.
63
+ class FileChecksumMismatchError < FileError; end
64
+
65
+ # Raised when a FilePart's data isn't valid base64
66
+ class FileEncodingError < FileError; end
67
+
37
68
  # Raised when two registered subclasses share the same identifier.
38
69
  class DuplicateIdentifierError < Error; end
39
70
 
@@ -0,0 +1,9 @@
1
+ # Minimal signatures for `minitest`, a development-only framework riffer never
2
+ # depends on. Only the surface `lib/riffer/testing/minitest.rb` touches is
3
+ # declared; the file is required by hand from a consumer's test_helper, so this
4
+ # stub keeps Steep aware of the constants without a `library` dependency.
5
+ module Minitest
6
+ class Test
7
+ def after_teardown: () -> void
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ # Hand-written because the signature names `Minitest`, an optional
2
+ # development-only framework that must never appear in a shipped signature —
3
+ # `lib/riffer/testing/minitest.rb` therefore leaves rbs-inline disabled.
4
+ module Riffer::Testing::MinitestCleanup : Minitest::Test
5
+ def after_teardown: () -> void
6
+ end
@@ -0,0 +1,7 @@
1
+ # Minimal signatures for `rspec`, a development-only framework riffer never
2
+ # depends on. Only the surface `lib/riffer/testing/rspec.rb` touches is
3
+ # declared; the file is required by hand from a consumer's spec_helper, so this
4
+ # stub keeps Steep aware of the constants without a `library` dependency.
5
+ module RSpec
6
+ def self.configure: () { (untyped) -> void } -> void
7
+ end
@@ -5,6 +5,8 @@ module Zeitwerk
5
5
  class Loader
6
6
  def self.for_gem: (*untyped) -> Zeitwerk::Loader
7
7
 
8
+ def ignore: (*String) -> void
9
+
8
10
  def inflector: () -> untyped
9
11
 
10
12
  def setup: () -> void
@@ -147,6 +147,68 @@ class Riffer::Config
147
147
  def backend=: (untyped) -> void
148
148
  end
149
149
 
150
+ # File-attachment-download policy for +Riffer::Messages::FilePart+ URL sources
151
+ class Files
152
+ # Allow file attachments to be downloaded to send to providers.
153
+ attr_reader allow_downloads: bool
154
+
155
+ # Maximum file size to download before failing.
156
+ attr_reader max_bytes: Integer
157
+
158
+ # Maximum amount of time to spend downloading a file before failing.
159
+ attr_reader timeout: Integer
160
+
161
+ # Maximum number of files to include in an individual message.
162
+ attr_reader max_per_message: Integer?
163
+
164
+ # Execution pattern for downloading files.
165
+ attr_reader runner: Riffer::Runner
166
+
167
+ # The object used to fetch a URL source's bytes
168
+ attr_reader downloader: untyped
169
+
170
+ # --
171
+ # : () -> void
172
+ def initialize: () -> void
173
+
174
+ # Sets the allow_downloads flag, coercing boolean-ish values so an env-var
175
+ # +"false"+ (truthy in Ruby) doesn't silently enable downloads. Raises
176
+ # Riffer::ArgumentError on an unrecognized value.
177
+ # --
178
+ # : (untyped) -> void
179
+ def allow_downloads=: (untyped) -> void
180
+
181
+ # Sets max_bytes, provided value is a positive integer.
182
+ # Raises Riffer::ArgumentError if value is not an Integer or less than or equal to 0.
183
+ # --
184
+ # : (untyped) -> void
185
+ def max_bytes=: (untyped) -> void
186
+
187
+ # Sets timeout, provided value is a positive integer.
188
+ # Raises Riffer::ArgumentError if value is not an Integer or is less than or equal to 0.
189
+ # --
190
+ # : (untyped) -> void
191
+ def timeout=: (untyped) -> void
192
+
193
+ # Sets max_per_message, provided value is either nil or a positive integer.
194
+ # Raises Riffer::ArgumentError if value is not an Integer or nil, or is less than or equal to 0.
195
+ # --
196
+ # : (untyped) -> void
197
+ def max_per_message=: (untyped) -> void
198
+
199
+ # Sets the runner used to process file downloads, provided value is a Riffer::Runner.
200
+ # Raises Riffer::ArgumentError if value is not a Riffer::Runner.
201
+ # --
202
+ # : (untyped) -> void
203
+ def runner=: (untyped) -> void
204
+
205
+ # Sets the object used to download bytes from a URL.
206
+ # Raises Riffer::ArgumentError if value does not respond to +#call+.
207
+ # --
208
+ # : (untyped) -> void
209
+ def downloader=: (untyped) -> void
210
+ end
211
+
150
212
  # Consumer-configured token pricing, keyed by +provider/model+ id. Riffer
151
213
  # ships no price table, so an unconfigured model carries no cost.
152
214
  class Pricing
@@ -256,6 +318,8 @@ class Riffer::Config
256
318
  # Tracing-related global configuration.
257
319
  attr_reader tracing: Riffer::Config::Tracing
258
320
 
321
+ attr_reader files: Riffer::Config::Files
322
+
259
323
  # Consumer-configured per-model token pricing.
260
324
  attr_reader pricing: Riffer::Config::Pricing
261
325
 
@@ -0,0 +1,16 @@
1
+ # Generated from lib/riffer/files/downloader.rb with RBS::Inline
2
+
3
+ class Riffer::Files::Downloader
4
+ MAX_REDIRECTS: Integer
5
+
6
+ # : (String, max_bytes: Integer, timeout: Integer) -> String
7
+ def call: (String, max_bytes: Integer, timeout: Integer) -> String
8
+
9
+ private
10
+
11
+ # : (String, max_bytes: Integer, timeout: Integer, redirects_remaining: Integer) -> String
12
+ def fetch: (String, max_bytes: Integer, timeout: Integer, redirects_remaining: Integer) -> String
13
+
14
+ # : (Net::HTTPResponse, max_bytes: Integer) -> String
15
+ def read_capped: (Net::HTTPResponse, max_bytes: Integer) -> String
16
+ end
@@ -0,0 +1,36 @@
1
+ # Generated from lib/riffer/files/resolver.rb with RBS::Inline
2
+
3
+ class Riffer::Files::Resolver
4
+ @provider: Riffer::Providers::Base
5
+
6
+ @config: Riffer::Config::Files
7
+
8
+ # : (provider: Riffer::Providers::Base) -> void
9
+ def initialize: (provider: Riffer::Providers::Base) -> void
10
+
11
+ # Resolves every file in every User message in place - downloading,
12
+ # verifying, and caching as the provider's capability and each file's
13
+ # sha256 require. Raises Riffer::FileError on any file that can't be
14
+ # resolved
15
+ # : (Array[Riffer::Messages::Base]) -> void
16
+ def resolve!: (Array[Riffer::Messages::Base]) -> void
17
+
18
+ private
19
+
20
+ # : (Riffer::Messages::User) -> void
21
+ def check_file_count!: (Riffer::Messages::User) -> void
22
+
23
+ # : (Riffer::Messages::FilePart) -> void
24
+ def resolve_file!: (Riffer::Messages::FilePart) -> void
25
+
26
+ # : (Riffer::Messages::FilePart) -> void
27
+ def verify_inline!: (Riffer::Messages::FilePart) -> void
28
+
29
+ # +cache:+ is false for a :url-delivery provider verifying a sha256 — the
30
+ # request still sends the URL, never the downloaded bytes, so caching them
31
+ # would hold memory nothing reads and let later turns skip re-verifying.
32
+ # : (Riffer::Messages::FilePart, cache: (false | Symbol)) -> void
33
+ def download!: (Riffer::Messages::FilePart, cache: false | Symbol) -> void
34
+
35
+ def verify_bytes!: (untyped bytes, untyped sha256) -> untyped
36
+ end
@@ -0,0 +1,4 @@
1
+ # Generated from lib/riffer/files.rb with RBS::Inline
2
+
3
+ module Riffer::Files
4
+ end
@@ -16,4 +16,12 @@ module Riffer::Helpers::Identifier
16
16
  # --
17
17
  # : (Module) -> String
18
18
  def for: (Module) -> String
19
+
20
+ # Returns the class-path name of a class or module, or +nil+ when anonymous.
21
+ # Tool classes shadow Module#name with the identifier DSL, so the real name
22
+ # must come from Module's own implementation.
23
+ #
24
+ # --
25
+ # : (Module) -> String?
26
+ def real_name: (Module) -> String?
19
27
  end
@@ -3,29 +3,40 @@
3
3
  # Represents a file attachment (image or document) — from a URL (+from_url+) or
4
4
  # raw base64 data (+new+).
5
5
  class Riffer::Messages::FilePart
6
+ @data_bytes: String?
7
+
8
+ @downloaded_data: String?
9
+
10
+ @data: String?
11
+
6
12
  @url_string: String?
7
13
 
8
14
  MEDIA_TYPES: Hash[String, String]
9
15
 
10
16
  SUPPORTED_MEDIA_TYPES: Array[String]
11
17
 
18
+ SHA256_PATTERN: Regexp
19
+
12
20
  # The MIME type of the file.
13
21
  attr_reader media_type: String
14
22
 
15
23
  # The filename, if available.
16
24
  attr_reader filename: String?
17
25
 
26
+ # The expected SHA-256 of the file contents, if the caller supplied one.
27
+ attr_reader sha256: String?
28
+
18
29
  # Raises Riffer::ArgumentError unless +data+ or +url+ is given and
19
30
  # +media_type+ is supported.
20
31
  # --
21
- # : (media_type: String, ?data: String?, ?filename: String?, ?url: String?) -> void
22
- def initialize: (media_type: String, ?data: String?, ?filename: String?, ?url: String?) -> void
32
+ # : (media_type: String, ?data: String?, ?filename: String?, ?url: String?, ?sha256: String?) -> void
33
+ def initialize: (media_type: String, ?data: String?, ?filename: String?, ?url: String?, ?sha256: String?) -> void
23
34
 
24
35
  # Creates a FilePart from a URL, detecting +media_type+ from the path
25
36
  # extension when omitted. Raises Riffer::ArgumentError if it can't be detected.
26
37
  # --
27
- # : (String, ?media_type: String?) -> Riffer::Messages::FilePart
28
- def self.from_url: (String, ?media_type: String?) -> Riffer::Messages::FilePart
38
+ # : (String, ?media_type: String?, ?filename: String?, ?sha256: String?) -> Riffer::Messages::FilePart
39
+ def self.from_url: (String, ?media_type: String?, ?filename: String?, ?sha256: String?) -> Riffer::Messages::FilePart
29
40
 
30
41
  # Builds a FilePart from a +{url:, media_type:}+ or +{data:, media_type:}+ hash,
31
42
  # or returns +file+ unchanged when it is already a FilePart. Raises
@@ -34,8 +45,29 @@ class Riffer::Messages::FilePart
34
45
  # : ((Hash[Symbol, untyped] | Riffer::Messages::FilePart)) -> Riffer::Messages::FilePart
35
46
  def self.from_hash: (Hash[Symbol, untyped] | Riffer::Messages::FilePart) -> Riffer::Messages::FilePart
36
47
 
37
- # Returns the base64-encoded data, or nil for URL-only sources.
38
- attr_reader data: String?
48
+ # The base64-encoded contents - caller-supplied, or filled in by the file
49
+ # resolver after a download. Nil for a URL source riffer hasn't fetched.
50
+ # --
51
+ # : () -> String?
52
+ def data: () -> String?
53
+
54
+ # : () -> String?
55
+ def data_bytes: () -> String?
56
+
57
+ # : (String) -> void
58
+ def cache_data_bytes: (String) -> void
59
+
60
+ # Whether data was supplied directly, as opposed to filled in later by the
61
+ # file resolver after a download
62
+ # : () -> bool
63
+ def inline_data?: () -> bool
64
+
65
+ # Caches bytes fetched for a URL source. Deliberately absent from +to_h+:
66
+ # the agent loop re-sends history on every turn, so the cache saves refreshing
67
+ # the same file, while persisted history stays free of megabytes of base64
68
+ # --
69
+ # : (String) -> void
70
+ def cache_downloaded_data: (String) -> void
39
71
 
40
72
  # Returns the URL if the source was a URL, nil otherwise.
41
73
  #
@@ -24,6 +24,10 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
24
24
  # : () -> void
25
25
  def initialize: () -> void
26
26
 
27
+ # --
28
+ # : (Riffer::Messages::FilePart) -> Symbol
29
+ def file_delivery: (Riffer::Messages::FilePart) -> Symbol
30
+
27
31
  private
28
32
 
29
33
  # --
@@ -41,6 +41,10 @@ class Riffer::Providers::Base
41
41
  # : (?prompt: String?, ?system: String?, ?messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?model: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, **untyped) -> Enumerator[Riffer::StreamEvents::Base, void]
42
42
  def stream_text: (?prompt: String?, ?system: String?, ?messages: Array[Hash[Symbol, untyped] | Riffer::Messages::Base]?, ?model: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, **untyped) -> Enumerator[Riffer::StreamEvents::Base, void]
43
43
 
44
+ # --
45
+ # : (Riffer::Messages::FilePart) -> Symbol
46
+ def file_delivery: (Riffer::Messages::FilePart) -> Symbol
47
+
44
48
  private
45
49
 
46
50
  # : (String) -> true
@@ -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
  # --
@@ -13,6 +13,10 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
13
13
  # : () -> void
14
14
  def initialize: () -> void
15
15
 
16
+ # --
17
+ # : (Riffer::Messages::FilePart) -> Symbol
18
+ def file_delivery: (Riffer::Messages::FilePart) -> Symbol
19
+
16
20
  private
17
21
 
18
22
  # --
@@ -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
  # --
@@ -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
+