riffer 0.29.0 → 0.29.1

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/rbs-inline.md +51 -0
  3. data/.release-please-manifest.json +1 -1
  4. data/CHANGELOG.md +7 -0
  5. data/Steepfile +2 -1
  6. data/lib/riffer/agent/context.rb +2 -0
  7. data/lib/riffer/agent/response.rb +2 -0
  8. data/lib/riffer/agent/session.rb +2 -0
  9. data/lib/riffer/agent.rb +2 -0
  10. data/lib/riffer/evals/evaluator.rb +5 -0
  11. data/lib/riffer/evals/judge.rb +5 -0
  12. data/lib/riffer/mcp/client.rb +2 -0
  13. data/lib/riffer/mcp/registration.rb +4 -0
  14. data/lib/riffer/mcp/registry.rb +3 -0
  15. data/lib/riffer/messages/file_part.rb +2 -0
  16. data/lib/riffer/providers/amazon_bedrock.rb +28 -21
  17. data/lib/riffer/providers/anthropic.rb +13 -9
  18. data/lib/riffer/providers/base.rb +2 -0
  19. data/lib/riffer/providers/gemini.rb +4 -0
  20. data/lib/riffer/providers/mock.rb +4 -0
  21. data/lib/riffer/providers/open_ai.rb +10 -7
  22. data/lib/riffer/providers/open_router.rb +25 -18
  23. data/lib/riffer/runner/fibers.rb +2 -0
  24. data/lib/riffer/runner/threaded.rb +2 -0
  25. data/lib/riffer/skills/config.rb +5 -0
  26. data/lib/riffer/skills/context.rb +3 -0
  27. data/lib/riffer/skills/filesystem_backend.rb +3 -0
  28. data/lib/riffer/tools/response.rb +2 -0
  29. data/lib/riffer/tools/runtime.rb +2 -0
  30. data/lib/riffer/tools/toolable.rb +7 -0
  31. data/lib/riffer/version.rb +1 -1
  32. data/lib/riffer.rb +2 -0
  33. data/sig/_private/anthropic.rbs +16 -0
  34. data/sig/_private/openai.rbs +29 -0
  35. data/sig/_private/riffer/providers/amazon_bedrock.rbs +4 -0
  36. data/sig/_private/riffer/providers/anthropic.rbs +4 -0
  37. data/sig/_private/riffer/providers/open_ai.rbs +4 -0
  38. data/sig/_private/riffer/providers/open_router.rbs +4 -0
  39. data/sig/generated/riffer/agent/context.rbs +2 -0
  40. data/sig/generated/riffer/agent/response.rbs +2 -0
  41. data/sig/generated/riffer/agent/session.rbs +2 -0
  42. data/sig/generated/riffer/agent.rbs +2 -0
  43. data/sig/generated/riffer/evals/evaluator.rbs +8 -0
  44. data/sig/generated/riffer/evals/judge.rbs +8 -0
  45. data/sig/generated/riffer/mcp/client.rbs +2 -0
  46. data/sig/generated/riffer/mcp/registration.rbs +6 -0
  47. data/sig/generated/riffer/mcp/registry.rbs +4 -0
  48. data/sig/generated/riffer/messages/file_part.rbs +2 -0
  49. data/sig/generated/riffer/providers/amazon_bedrock.rbs +20 -20
  50. data/sig/generated/riffer/providers/anthropic.rbs +10 -10
  51. data/sig/generated/riffer/providers/base.rbs +2 -0
  52. data/sig/generated/riffer/providers/gemini.rbs +6 -0
  53. data/sig/generated/riffer/providers/mock.rbs +6 -0
  54. data/sig/generated/riffer/providers/open_ai.rbs +8 -8
  55. data/sig/generated/riffer/providers/open_router.rbs +16 -16
  56. data/sig/generated/riffer/runner/fibers.rbs +2 -0
  57. data/sig/generated/riffer/runner/threaded.rbs +2 -0
  58. data/sig/generated/riffer/skills/config.rbs +8 -0
  59. data/sig/generated/riffer/skills/context.rbs +4 -0
  60. data/sig/generated/riffer/skills/filesystem_backend.rbs +4 -0
  61. data/sig/generated/riffer/tools/response.rbs +2 -0
  62. data/sig/generated/riffer/tools/runtime.rbs +2 -0
  63. data/sig/generated/riffer/tools/toolable.rbs +12 -0
  64. data/sig/generated/riffer.rbs +2 -0
  65. data/sig/manifest.yaml +3 -0
  66. data/sig/manual/riffer/agent/run.rbs +5 -0
  67. data/sig/manual/riffer/helpers/call_or_value.rbs +5 -0
  68. data/sig/manual/riffer/tools/toolable.rbs +6 -0
  69. metadata +16 -11
  70. data/sig/stubs/agent_ivars.rbs +0 -7
  71. data/sig/stubs/extend_self.rbs +0 -11
  72. data/sig/stubs/lib_ivars.rbs +0 -101
  73. data/sig/stubs/provider_ivars.rbs +0 -36
  74. data/sig/stubs/provider_sdk_methods.rbs +0 -50
  75. /data/sig/{stubs → _private}/async.rbs +0 -0
  76. /data/sig/{stubs → _private}/aws-sdk-core/seahorse_request_context.rbs +0 -0
  77. /data/sig/{stubs → _private}/aws-sdk-core/static_token_provider.rbs +0 -0
  78. /data/sig/{stubs/mcp_sdk.rbs → _private/mcp.rbs} +0 -0
  79. /data/sig/{stubs → _private}/zeitwerk.rbs +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be73ae37c66f2f6ad16acdd7b52c271a782172878086821a9c95dbbe238541d3
4
- data.tar.gz: 29ea65859395f2e6daaa993d9e8c60cf1908781f1acf79b642588439f4e41781
3
+ metadata.gz: 91f54f388c63e670d155ae2b8d84bdafe18edd2f29423d92b52c9bfb686332d3
4
+ data.tar.gz: 7b1bc3bb32951cadbadfe4ba388ca4dc805849a2724cbfae944daf1a59ea241c
5
5
  SHA512:
6
- metadata.gz: cce21dcb1840e39b96032cf6061dcce6030e35c21c1ce8f3937138405067cd39644f4a55fd4fddfcc9623549441dccb0d76b5443e7fa890ab84ce73f6af2736d
7
- data.tar.gz: 3c0e8a0fbcd42c3aceee9b4d8837188e260d444c2e22df72f1976bdfc7b05bf2669f2494d3121ca066d142baa8629bdd01c51acd294869702b5357d83943a4a6
6
+ metadata.gz: 5e573dd0b3ad056266a7c8a88b24754a510ceac1737f730cedf98e73dc18be17e2f9dc93ebe47d4c89e2d483c1c40c40a7fcbad586ba1ceac949b11af2011a8c
7
+ data.tar.gz: c1aa387e0298c3999da9da89a640a86048207dd54abe6a336781a9dd265acec49939729fcca7378872efa595131357601b5639b2437508225470fe473a9a5989
@@ -98,6 +98,23 @@ VERSION = "1.0.0" #: String
98
98
  DEFAULTS = {}.freeze #: Hash[Symbol, untyped]
99
99
  ```
100
100
 
101
+ ### Instance variables
102
+
103
+ The `#:` shorthand on an assignment is a Steep _assertion_ — it types the expression but does
104
+ **not declare the ivar**. To declare an ivar's type, use a `# @rbs` comment inside the class
105
+ body. `# @rbs` is used **only** for ivar declarations in this codebase; everything else uses
106
+ `#:`.
107
+
108
+ ```ruby
109
+ class Riffer::Agent::Session
110
+ # @rbs @callbacks: Array[^(Riffer::Messages::Base) -> void] # instance ivar
111
+ end
112
+
113
+ module Riffer
114
+ # @rbs self.@config: Riffer::Config? # class/module-level ivar
115
+ end
116
+ ```
117
+
101
118
  ## Common Type Patterns
102
119
 
103
120
  | Pattern | Meaning |
@@ -112,6 +129,40 @@ DEFAULTS = {}.freeze #: Hash[Symbol, untyped]
112
129
  | `untyped` | Any type |
113
130
  | `void` | No meaningful return |
114
131
 
132
+ ## Optional-dependency types (consumer-safe signatures)
133
+
134
+ `sig/generated/` ships with the gem and is loaded by downstream projects (`rbs collection` / `rbs -r riffer`). rbs-inline copies a method's `#:` signature **verbatim** into the shipped sig, so **never name an optional-dependency type in a `#:` signature** — `OpenAI::*`, `Anthropic::*`, `Aws::*`, `MCP::*`, `Async::*`, `Zeitwerk::*`, etc. A consumer who installs riffer without that gem would hit `Cannot find type`, because those providers are pluggable and the gems ship no usable RBS of their own.
135
+
136
+ **Workaround — assert the type inside the method body instead.** An inline assertion (`local = arg #: OpenAI::Models::…`) is a Steep-only hint that rbs-inline does **not** emit into the signature. Leave the SDK param/return `untyped` in the `#:` line, keep every riffer/stdlib param and return typed, and recover the SDK type with a body assertion:
137
+
138
+ ```ruby
139
+ #: (untyped) -> String
140
+ def extract_content(response)
141
+ message = response #: Anthropic::Models::Message
142
+ message.content&.first&.text || "" # fully type-checked against the SDK type
143
+ end
144
+
145
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
146
+ def handle_stream_chunk(chunk, state:, yielder:)
147
+ typed = chunk #: OpenAI::Models::Chat::ChatCompletionChunk
148
+ # ...
149
+ end
150
+
151
+ # When the return value IS the SDK object, type the return `untyped` (no body assertion needed):
152
+ #: (Hash[Symbol, untyped]) -> untyped
153
+ def execute_generate(params)
154
+ @client.messages.create(**params)
155
+ end
156
+ ```
157
+
158
+ `test/shipped_signatures_test.rb` enforces this — it fails if any optional-dependency type appears in `sig/generated/` or `sig/manual/`.
159
+
160
+ ### Where stubs and stdlib deps live
161
+
162
+ - `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` declares the SDK-typed `@client` ivar).
163
+ - `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 "Instance variables"). SDK-typed ivars can't ship, so they go in `_private/riffer/providers/` (`@client`).
164
+ - `sig/manifest.yaml` — declares the **stdlib** RBS the shipped sigs reference (`uri`, `net-http`) so `rbs -r riffer` resolves them.
165
+
115
166
  ## Workflow
116
167
 
117
168
  After changing type annotations:
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.29.0"
2
+ ".": "0.29.1"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@ 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.29.1](https://github.com/janeapp/riffer/compare/riffer/v0.29.0...riffer/v0.29.1) (2026-06-01)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **rbs:** ship consumer-safe RBS signatures ([#286](https://github.com/janeapp/riffer/issues/286)) ([ac8ce6c](https://github.com/janeapp/riffer/commit/ac8ce6c40ab665456cee6cd9275649024492f4a0))
14
+
8
15
  ## [0.29.0](https://github.com/janeapp/riffer/compare/riffer/v0.28.0...riffer/v0.29.0) (2026-05-29)
9
16
 
10
17
 
data/Steepfile CHANGED
@@ -2,7 +2,8 @@ D = Steep::Diagnostic
2
2
 
3
3
  target :lib do
4
4
  signature "sig/generated"
5
- signature "sig/stubs"
5
+ signature "sig/manual"
6
+ signature "sig/_private"
6
7
 
7
8
  check "lib"
8
9
 
@@ -19,6 +19,8 @@
19
19
  # context.token_usage # => nil
20
20
  #
21
21
  class Riffer::Agent::Context
22
+ # @rbs @data: Hash[Symbol, untyped]
23
+
22
24
  # Keys reserved for framework use. Passing any of these to the
23
25
  # constructor raises +Riffer::ArgumentError+.
24
26
  RESERVED_KEYS = [:skills, :token_usage].freeze #: Array[Symbol]
@@ -13,6 +13,8 @@
13
13
  # puts response.content
14
14
  # end
15
15
  class Riffer::Agent::Response
16
+ # @rbs @interrupted: bool
17
+
16
18
  # The response content.
17
19
  attr_reader :content #: String
18
20
 
@@ -18,6 +18,8 @@
18
18
  class Riffer::Agent::Session
19
19
  include Enumerable #[Riffer::Messages::Base]
20
20
 
21
+ # @rbs @callbacks: Array[^(Riffer::Messages::Base) -> void]
22
+
21
23
  # The message history.
22
24
  attr_reader :messages #: Array[Riffer::Messages::Base]
23
25
 
data/lib/riffer/agent.rb CHANGED
@@ -19,6 +19,8 @@ require "json"
19
19
  # agent.generate('Hello!')
20
20
  #
21
21
  class Riffer::Agent
22
+ # @rbs self.@config: Riffer::Agent::Config?
23
+
22
24
  include Riffer::Messages::Converter
23
25
  extend Riffer::Helpers::ClassNameConverter
24
26
 
@@ -16,6 +16,11 @@
16
16
  # end
17
17
  #
18
18
  class Riffer::Evals::Evaluator
19
+ # @rbs self.@instructions: String?
20
+ # @rbs self.@higher_is_better: bool?
21
+ # @rbs self.@judge_model: String?
22
+ # @rbs @judge: Riffer::Evals::Judge?
23
+
19
24
  class << self
20
25
  # Gets or sets the evaluation instructions (criteria and scoring rubric).
21
26
  #
@@ -19,6 +19,11 @@ require "json"
19
19
  # result[:reason] # => "The response is relevant..."
20
20
  #
21
21
  class Riffer::Evals::Judge
22
+ # @rbs @provider_options: Hash[Symbol, untyped]
23
+ # @rbs @provider_instance: Riffer::Providers::Base?
24
+ # @rbs @provider_name: String?
25
+ # @rbs @model_name: String?
26
+
22
27
  # Internal tool for structured evaluation output.
23
28
  class EvaluationTool < Riffer::Tool
24
29
  identifier "evaluation"
@@ -16,6 +16,8 @@
16
16
  class Riffer::Mcp::Client
17
17
  include Riffer::Helpers::Dependencies
18
18
 
19
+ # @rbs @client: untyped
20
+
19
21
  #--
20
22
  #: (endpoint: String, ?headers: (Hash[String, String] | Proc), ?client: untyped?) -> void
21
23
  def initialize(endpoint:, headers: {}, client: nil)
@@ -7,6 +7,10 @@
7
7
  # +tools/list+ call, then generates tool classes.
8
8
  #
9
9
  class Riffer::Mcp::Registration
10
+ # @rbs @cancelled: bool
11
+ # @rbs @tools: Array[singleton(Riffer::Tool)]
12
+ # @rbs @mutex: Thread::Mutex
13
+
10
14
  # The manifest that describes this server.
11
15
  attr_reader :manifest #: Riffer::Mcp::Manifest
12
16
 
@@ -6,6 +6,9 @@
6
6
  # Keyed by manifest name. All public methods are mutex-guarded.
7
7
  #
8
8
  module Riffer::Mcp::Registry
9
+ # @rbs self.@mutex: Thread::Mutex
10
+ # @rbs self.@store: Hash[String, Riffer::Mcp::Registration]
11
+
9
12
  @mutex = Mutex.new
10
13
  @store = {} #: Hash[String, Riffer::Mcp::Registration]
11
14
 
@@ -15,6 +15,8 @@ require "uri"
15
15
  # file.document? # => true
16
16
  #
17
17
  class Riffer::Messages::FilePart
18
+ # @rbs @url_string: String?
19
+
18
20
  MEDIA_TYPES = {
19
21
  ".jpg" => "image/jpeg",
20
22
  ".jpeg" => "image/jpeg",
@@ -92,15 +92,16 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
92
92
  end
93
93
 
94
94
  #--
95
- #: (Hash[Symbol, untyped]) -> Aws::BedrockRuntime::Client::_ConverseResponseSuccess
95
+ #: (Hash[Symbol, untyped]) -> untyped
96
96
  def execute_generate(params)
97
97
  @client.converse(**params)
98
98
  end
99
99
 
100
100
  #--
101
- #: (Aws::BedrockRuntime::Client::_ConverseResponseSuccess) -> Riffer::Providers::TokenUsage?
101
+ #: (untyped) -> Riffer::Providers::TokenUsage?
102
102
  def extract_token_usage(response)
103
- usage = response.usage
103
+ typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
104
+ usage = typed_response.usage
104
105
 
105
106
  Riffer::Providers::TokenUsage.new(
106
107
  input_tokens: usage.input_tokens,
@@ -111,9 +112,10 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
111
112
  end
112
113
 
113
114
  #--
114
- #: (Aws::BedrockRuntime::Client::_ConverseResponseSuccess) -> String
115
+ #: (untyped) -> String
115
116
  def extract_content(response)
116
- content_blocks = response.output&.message&.content
117
+ typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
118
+ content_blocks = typed_response.output&.message&.content
117
119
  return "" if content_blocks.nil? || content_blocks.empty?
118
120
 
119
121
  text_content = ""
@@ -126,9 +128,10 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
126
128
  end
127
129
 
128
130
  #--
129
- #: (Aws::BedrockRuntime::Client::_ConverseResponseSuccess) -> Array[Riffer::Messages::Assistant::ToolCall]
131
+ #: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
130
132
  def extract_tool_calls(response)
131
- content_blocks = response.output&.message&.content
133
+ typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
134
+ content_blocks = typed_response.output&.message&.content
132
135
  return [] if content_blocks.nil? || content_blocks.empty?
133
136
 
134
137
  tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
@@ -195,28 +198,31 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
195
198
  end
196
199
 
197
200
  #--
198
- #: (Aws::BedrockRuntime::Types::ContentBlockStartEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
201
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
199
202
  def handle_content_block_start_tool_use(event, state:, yielder:)
203
+ typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockStartEvent
200
204
  state[:tool_call] = {
201
- id: event.start.tool_use.tool_use_id,
202
- name: decode_tool_name(event.start.tool_use.name, tools: @current_tools),
205
+ id: typed_event.start.tool_use.tool_use_id,
206
+ name: decode_tool_name(typed_event.start.tool_use.name, tools: @current_tools),
203
207
  arguments: ""
204
208
  }
205
209
  end
206
210
 
207
211
  #--
208
- #: (Aws::BedrockRuntime::Types::ContentBlockDeltaEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
212
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
209
213
  def handle_content_block_delta_text_delta(event, state:, yielder:)
210
- delta_text = event.delta.text
214
+ typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockDeltaEvent
215
+ delta_text = typed_event.delta.text
211
216
  state[:text] ||= ""
212
217
  state[:text] += delta_text
213
218
  yielder << Riffer::StreamEvents::TextDelta.new(delta_text)
214
219
  end
215
220
 
216
221
  #--
217
- #: (Aws::BedrockRuntime::Types::ContentBlockDeltaEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
222
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
218
223
  def handle_content_block_delta_tool_use(event, state:, yielder:)
219
- input_delta = event.delta.tool_use.input
224
+ typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockDeltaEvent
225
+ input_delta = typed_event.delta.tool_use.input
220
226
 
221
227
  state[:tool_call][:arguments] += input_delta
222
228
 
@@ -228,14 +234,14 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
228
234
  end
229
235
 
230
236
  #--
231
- #: (Aws::BedrockRuntime::Types::ContentBlockStopEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
237
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
232
238
  def handle_content_block_stop_text_delta(_event, state:, yielder:)
233
239
  yielder << Riffer::StreamEvents::TextDone.new(state[:text])
234
240
  state[:text] = nil
235
241
  end
236
242
 
237
243
  #--
238
- #: (Aws::BedrockRuntime::Types::ContentBlockStopEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
244
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
239
245
  def handle_content_block_stop_tool_use(_event, state:, yielder:)
240
246
  tool_call = state[:tool_call]
241
247
  yielder << Riffer::StreamEvents::ToolCallDone.new(
@@ -248,14 +254,15 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
248
254
  end
249
255
 
250
256
  #--
251
- #: (Aws::BedrockRuntime::Types::ConverseStreamMetadataEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
257
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
252
258
  def handle_metadata_usage(event, state:, yielder:)
259
+ typed_event = event #: Aws::BedrockRuntime::Types::ConverseStreamMetadataEvent
253
260
  yielder << Riffer::StreamEvents::TokenUsageDone.new(
254
261
  token_usage: Riffer::Providers::TokenUsage.new(
255
- input_tokens: event.usage.input_tokens,
256
- output_tokens: event.usage.output_tokens,
257
- cache_creation_tokens: event.usage.cache_write_input_tokens,
258
- cache_read_tokens: event.usage.cache_read_input_tokens
262
+ input_tokens: typed_event.usage.input_tokens,
263
+ output_tokens: typed_event.usage.output_tokens,
264
+ cache_creation_tokens: typed_event.usage.cache_write_input_tokens,
265
+ cache_read_tokens: typed_event.usage.cache_read_input_tokens
259
266
  )
260
267
  )
261
268
  end
@@ -77,15 +77,16 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
77
77
  end
78
78
 
79
79
  #--
80
- #: (Hash[Symbol, untyped]) -> Anthropic::Models::Message
80
+ #: (Hash[Symbol, untyped]) -> untyped
81
81
  def execute_generate(params)
82
82
  @client.messages.create(**params)
83
83
  end
84
84
 
85
85
  #--
86
- #: (Anthropic::Models::Message) -> Riffer::Providers::TokenUsage?
86
+ #: (untyped) -> Riffer::Providers::TokenUsage?
87
87
  def extract_token_usage(response)
88
- usage = response.usage
88
+ message = response #: Anthropic::Models::Message
89
+ usage = message.usage
89
90
 
90
91
  Riffer::Providers::TokenUsage.new(
91
92
  input_tokens: usage.input_tokens,
@@ -96,9 +97,10 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
96
97
  end
97
98
 
98
99
  #--
99
- #: (Anthropic::Models::Message) -> String
100
+ #: (untyped) -> String
100
101
  def extract_content(response)
101
- content_blocks = response.content
102
+ message = response #: Anthropic::Models::Message
103
+ content_blocks = message.content
102
104
  return "" if content_blocks.nil? || content_blocks.empty?
103
105
 
104
106
  text_content = ""
@@ -111,9 +113,10 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
111
113
  end
112
114
 
113
115
  #--
114
- #: (Anthropic::Models::Message) -> Array[Riffer::Messages::Assistant::ToolCall]
116
+ #: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
115
117
  def extract_tool_calls(response)
116
- content_blocks = response.content
118
+ message = response #: Anthropic::Models::Message
119
+ content_blocks = message.content
117
120
  return [] if content_blocks.nil? || content_blocks.empty?
118
121
 
119
122
  tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
@@ -285,9 +288,10 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
285
288
  end
286
289
 
287
290
  #--
288
- #: (untyped, accumulated_message: Anthropic::Models::Message?, yielder: Enumerator::Yielder) -> void
291
+ #: (untyped, accumulated_message: untyped, yielder: Enumerator::Yielder) -> void
289
292
  def handle_message_stop(_event, accumulated_message:, yielder:)
290
- usage = accumulated_message&.usage
293
+ message = accumulated_message #: Anthropic::Models::Message?
294
+ usage = message&.usage
291
295
  return unless usage
292
296
 
293
297
  yielder << Riffer::StreamEvents::TokenUsageDone.new(
@@ -17,6 +17,8 @@ require "json"
17
17
  # [extract_content] extract text content from the SDK response
18
18
  # [extract_tool_calls] extract tool calls from the SDK response
19
19
  class Riffer::Providers::Base
20
+ # @rbs @current_tools: Array[singleton(Riffer::Tool)]
21
+
20
22
  include Riffer::Helpers::Dependencies
21
23
  include Riffer::Messages::Converter
22
24
 
@@ -8,6 +8,10 @@ require "uri"
8
8
 
9
9
  # Google Gemini provider for Gemini models via the Gemini REST API.
10
10
  class Riffer::Providers::Gemini < Riffer::Providers::Base
11
+ # @rbs @api_key: String?
12
+ # @rbs @open_timeout: Integer
13
+ # @rbs @read_timeout: Integer
14
+
11
15
  BASE_URI = URI("https://generativelanguage.googleapis.com") #: URI::Generic
12
16
  VALID_MODEL_PATTERN = /\A[a-zA-Z0-9._-]+\z/ #: Regexp
13
17
  DEFAULT_OPEN_TIMEOUT = 10 #: Integer
@@ -5,6 +5,10 @@
5
5
  #
6
6
  # No external gems required.
7
7
  class Riffer::Providers::Mock < Riffer::Providers::Base
8
+ # @rbs @responses: Array[Hash[Symbol, untyped]]
9
+ # @rbs @current_index: Integer
10
+ # @rbs @stubbed_responses: Array[Hash[Symbol, untyped]]
11
+
8
12
  # Returns the preferred skill adapter for the given mock model.
9
13
  #
10
14
  # Mock is used to stand in for any real provider in tests, so the model
@@ -66,15 +66,16 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
66
66
  end
67
67
 
68
68
  #--
69
- #: (Hash[Symbol, untyped]) -> OpenAI::Models::Responses::Response
69
+ #: (Hash[Symbol, untyped]) -> untyped
70
70
  def execute_generate(params)
71
71
  @client.responses.create(params)
72
72
  end
73
73
 
74
74
  #--
75
- #: (OpenAI::Models::Responses::Response) -> Riffer::Providers::TokenUsage?
75
+ #: (untyped) -> Riffer::Providers::TokenUsage?
76
76
  def extract_token_usage(response)
77
- usage = response.usage
77
+ typed_response = response #: OpenAI::Models::Responses::Response
78
+ usage = typed_response.usage
78
79
  return nil unless usage
79
80
 
80
81
  Riffer::Providers::TokenUsage.new(
@@ -84,11 +85,12 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
84
85
  end
85
86
 
86
87
  #--
87
- #: (OpenAI::Models::Responses::Response) -> String
88
+ #: (untyped) -> String
88
89
  def extract_content(response)
90
+ typed_response = response #: OpenAI::Models::Responses::Response
89
91
  text_content = ""
90
92
 
91
- response.output.each do |item|
93
+ typed_response.output.each do |item|
92
94
  next unless item.is_a?(::OpenAI::Models::Responses::ResponseOutputMessage)
93
95
 
94
96
  text_block = item.content.find { |c| c.is_a?(::OpenAI::Models::Responses::ResponseOutputText) }
@@ -99,11 +101,12 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
99
101
  end
100
102
 
101
103
  #--
102
- #: (OpenAI::Models::Responses::Response) -> Array[Riffer::Messages::Assistant::ToolCall]
104
+ #: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
103
105
  def extract_tool_calls(response)
106
+ typed_response = response #: OpenAI::Models::Responses::Response
104
107
  tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
105
108
 
106
- response.output.each do |item|
109
+ typed_response.output.each do |item|
107
110
  next unless item.is_a?(::OpenAI::Models::Responses::ResponseFunctionToolCall)
108
111
 
109
112
  tool_calls << Riffer::Messages::Assistant::ToolCall.new(
@@ -64,15 +64,16 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
64
64
  end
65
65
 
66
66
  #--
67
- #: (Hash[Symbol, untyped]) -> OpenAI::Models::Chat::ChatCompletion
67
+ #: (Hash[Symbol, untyped]) -> untyped
68
68
  def execute_generate(params)
69
69
  @client.chat.completions.create(**params)
70
70
  end
71
71
 
72
72
  #--
73
- #: (OpenAI::Models::Chat::ChatCompletion) -> Riffer::Providers::TokenUsage?
73
+ #: (untyped) -> Riffer::Providers::TokenUsage?
74
74
  def extract_token_usage(response)
75
- usage = response.usage
75
+ typed_response = response #: OpenAI::Models::Chat::ChatCompletion
76
+ usage = typed_response.usage
76
77
  return nil unless usage
77
78
 
78
79
  Riffer::Providers::TokenUsage.new(
@@ -82,15 +83,17 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
82
83
  end
83
84
 
84
85
  #--
85
- #: (OpenAI::Models::Chat::ChatCompletion) -> String
86
+ #: (untyped) -> String
86
87
  def extract_content(response)
87
- response.choices.first&.message&.content || ""
88
+ typed_response = response #: OpenAI::Models::Chat::ChatCompletion
89
+ typed_response.choices.first&.message&.content || ""
88
90
  end
89
91
 
90
92
  #--
91
- #: (OpenAI::Models::Chat::ChatCompletion) -> Array[Riffer::Messages::Assistant::ToolCall]
93
+ #: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
92
94
  def extract_tool_calls(response)
93
- message = response.choices.first&.message
95
+ typed_response = response #: OpenAI::Models::Chat::ChatCompletion
96
+ message = typed_response.choices.first&.message
94
97
  return [] unless message
95
98
 
96
99
  tool_calls = message.tool_calls
@@ -145,9 +148,10 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
145
148
  end
146
149
 
147
150
  #--
148
- #: (OpenAI::Models::Chat::ChatCompletionChunk, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
151
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
149
152
  def handle_stream_chunk(chunk, state:, yielder:)
150
- choice = chunk.choices&.first
153
+ typed_chunk = chunk #: OpenAI::Models::Chat::ChatCompletionChunk
154
+ choice = typed_chunk.choices&.first
151
155
  delta = choice&.delta
152
156
 
153
157
  if delta
@@ -160,20 +164,21 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
160
164
  emit_tool_call_done_events(state: state, yielder: yielder)
161
165
  end
162
166
 
163
- return unless chunk.usage
167
+ return unless typed_chunk.usage
164
168
 
165
169
  yielder << Riffer::StreamEvents::TokenUsageDone.new(
166
170
  token_usage: Riffer::Providers::TokenUsage.new(
167
- input_tokens: chunk.usage.prompt_tokens,
168
- output_tokens: chunk.usage.completion_tokens
171
+ input_tokens: typed_chunk.usage.prompt_tokens,
172
+ output_tokens: typed_chunk.usage.completion_tokens
169
173
  )
170
174
  )
171
175
  end
172
176
 
173
177
  #--
174
- #: (OpenAI::Models::Chat::ChatCompletionChunk::Choice::Delta, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
178
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
175
179
  def handle_text_delta(delta, state:, yielder:)
176
- content = delta.content
180
+ typed_delta = delta #: OpenAI::Models::Chat::ChatCompletionChunk::Choice::Delta
181
+ content = typed_delta.content
177
182
  return if content.nil? || content.empty?
178
183
 
179
184
  state[:text] << content
@@ -194,9 +199,10 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
194
199
  end
195
200
 
196
201
  #--
197
- #: (OpenAI::Models::Chat::ChatCompletionChunk::Choice::Delta, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
202
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
198
203
  def handle_tool_call_deltas(delta, state:, yielder:)
199
- tool_calls = delta.tool_calls
204
+ typed_delta = delta #: OpenAI::Models::Chat::ChatCompletionChunk::Choice::Delta
205
+ tool_calls = typed_delta.tool_calls
200
206
  return if tool_calls.nil? || tool_calls.empty?
201
207
 
202
208
  tool_calls.each do |tc|
@@ -236,9 +242,10 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
236
242
  end
237
243
 
238
244
  #--
239
- #: (OpenAI::Models::Chat::ChatCompletionChunk::Choice) -> bool
245
+ #: (untyped) -> bool
240
246
  def finish_reason_is_tool_calls?(choice)
241
- choice.finish_reason.to_s == "tool_calls"
247
+ typed_choice = choice #: OpenAI::Models::Chat::ChatCompletionChunk::Choice
248
+ typed_choice.finish_reason.to_s == "tool_calls"
242
249
  end
243
250
 
244
251
  #--
@@ -14,6 +14,8 @@
14
14
  # runner.map(items) { |item| expensive_operation(item) }
15
15
  #
16
16
  class Riffer::Runner::Fibers < Riffer::Runner
17
+ # @rbs @max_concurrency: Integer?
18
+
17
19
  include Riffer::Helpers::Dependencies
18
20
 
19
21
  # [max_concurrency] maximum number of fibers to run simultaneously.
@@ -14,6 +14,8 @@
14
14
  # runner.map(items) { |item| expensive_operation(item) }
15
15
  #
16
16
  class Riffer::Runner::Threaded < Riffer::Runner
17
+ # @rbs @max_concurrency: Integer
18
+
17
19
  DEFAULT_MAX_CONCURRENCY = 5 #: Integer
18
20
 
19
21
  # [max_concurrency] maximum number of threads to run simultaneously.
@@ -11,6 +11,11 @@
11
11
  # activate ["code-review"]
12
12
  # end
13
13
  class Riffer::Skills::Config
14
+ # @rbs @backend: (Riffer::Skills::Backend | Proc)?
15
+ # @rbs @adapter: singleton(Riffer::Skills::Adapter)?
16
+ # @rbs @activate: (Array[String] | Proc)?
17
+ # @rbs @activate_tool: singleton(Riffer::Tool)?
18
+
14
19
  # Creates a new Config with all options unset.
15
20
  #
16
21
  #--
@@ -11,6 +11,9 @@
11
11
  #
12
12
  # See Riffer::Skills::Backend, Riffer::Skills::Frontmatter.
13
13
  class Riffer::Skills::Context
14
+ # @rbs @backend: Riffer::Skills::Backend
15
+ # @rbs @activated: Hash[String, String]
16
+
14
17
  # Skill catalog indexed by name.
15
18
  attr_reader :skills #: Hash[String, Riffer::Skills::Frontmatter]
16
19
 
@@ -11,6 +11,9 @@
11
11
  # backend.read_skill("code-review") # => "Full skill instructions..."
12
12
  #
13
13
  class Riffer::Skills::FilesystemBackend < Riffer::Skills::Backend
14
+ # @rbs @paths: Array[String]
15
+ # @rbs @skills_cache: Hash[String, String]?
16
+
14
17
  # Creates a new FilesystemBackend.
15
18
  #
16
19
  # [paths] one or more directory paths to scan for skills.
@@ -18,6 +18,8 @@ require "json"
18
18
  # end
19
19
  #
20
20
  class Riffer::Tools::Response
21
+ # @rbs @success: bool
22
+
21
23
  VALID_FORMATS = %i[text json].freeze #: Array[Symbol]
22
24
 
23
25
  attr_reader :content #: String