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.
- checksums.yaml +4 -4
- data/.agents/rbs-inline.md +51 -0
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +7 -0
- data/Steepfile +2 -1
- data/lib/riffer/agent/context.rb +2 -0
- data/lib/riffer/agent/response.rb +2 -0
- data/lib/riffer/agent/session.rb +2 -0
- data/lib/riffer/agent.rb +2 -0
- data/lib/riffer/evals/evaluator.rb +5 -0
- data/lib/riffer/evals/judge.rb +5 -0
- data/lib/riffer/mcp/client.rb +2 -0
- data/lib/riffer/mcp/registration.rb +4 -0
- data/lib/riffer/mcp/registry.rb +3 -0
- data/lib/riffer/messages/file_part.rb +2 -0
- data/lib/riffer/providers/amazon_bedrock.rb +28 -21
- data/lib/riffer/providers/anthropic.rb +13 -9
- data/lib/riffer/providers/base.rb +2 -0
- data/lib/riffer/providers/gemini.rb +4 -0
- data/lib/riffer/providers/mock.rb +4 -0
- data/lib/riffer/providers/open_ai.rb +10 -7
- data/lib/riffer/providers/open_router.rb +25 -18
- data/lib/riffer/runner/fibers.rb +2 -0
- data/lib/riffer/runner/threaded.rb +2 -0
- data/lib/riffer/skills/config.rb +5 -0
- data/lib/riffer/skills/context.rb +3 -0
- data/lib/riffer/skills/filesystem_backend.rb +3 -0
- data/lib/riffer/tools/response.rb +2 -0
- data/lib/riffer/tools/runtime.rb +2 -0
- data/lib/riffer/tools/toolable.rb +7 -0
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +2 -0
- data/sig/_private/anthropic.rbs +16 -0
- data/sig/_private/openai.rbs +29 -0
- data/sig/_private/riffer/providers/amazon_bedrock.rbs +4 -0
- data/sig/_private/riffer/providers/anthropic.rbs +4 -0
- data/sig/_private/riffer/providers/open_ai.rbs +4 -0
- data/sig/_private/riffer/providers/open_router.rbs +4 -0
- data/sig/generated/riffer/agent/context.rbs +2 -0
- data/sig/generated/riffer/agent/response.rbs +2 -0
- data/sig/generated/riffer/agent/session.rbs +2 -0
- data/sig/generated/riffer/agent.rbs +2 -0
- data/sig/generated/riffer/evals/evaluator.rbs +8 -0
- data/sig/generated/riffer/evals/judge.rbs +8 -0
- data/sig/generated/riffer/mcp/client.rbs +2 -0
- data/sig/generated/riffer/mcp/registration.rbs +6 -0
- data/sig/generated/riffer/mcp/registry.rbs +4 -0
- data/sig/generated/riffer/messages/file_part.rbs +2 -0
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +20 -20
- data/sig/generated/riffer/providers/anthropic.rbs +10 -10
- data/sig/generated/riffer/providers/base.rbs +2 -0
- data/sig/generated/riffer/providers/gemini.rbs +6 -0
- data/sig/generated/riffer/providers/mock.rbs +6 -0
- data/sig/generated/riffer/providers/open_ai.rbs +8 -8
- data/sig/generated/riffer/providers/open_router.rbs +16 -16
- data/sig/generated/riffer/runner/fibers.rbs +2 -0
- data/sig/generated/riffer/runner/threaded.rbs +2 -0
- data/sig/generated/riffer/skills/config.rbs +8 -0
- data/sig/generated/riffer/skills/context.rbs +4 -0
- data/sig/generated/riffer/skills/filesystem_backend.rbs +4 -0
- data/sig/generated/riffer/tools/response.rbs +2 -0
- data/sig/generated/riffer/tools/runtime.rbs +2 -0
- data/sig/generated/riffer/tools/toolable.rbs +12 -0
- data/sig/generated/riffer.rbs +2 -0
- data/sig/manifest.yaml +3 -0
- data/sig/manual/riffer/agent/run.rbs +5 -0
- data/sig/manual/riffer/helpers/call_or_value.rbs +5 -0
- data/sig/manual/riffer/tools/toolable.rbs +6 -0
- metadata +16 -11
- data/sig/stubs/agent_ivars.rbs +0 -7
- data/sig/stubs/extend_self.rbs +0 -11
- data/sig/stubs/lib_ivars.rbs +0 -101
- data/sig/stubs/provider_ivars.rbs +0 -36
- data/sig/stubs/provider_sdk_methods.rbs +0 -50
- /data/sig/{stubs → _private}/async.rbs +0 -0
- /data/sig/{stubs → _private}/aws-sdk-core/seahorse_request_context.rbs +0 -0
- /data/sig/{stubs → _private}/aws-sdk-core/static_token_provider.rbs +0 -0
- /data/sig/{stubs/mcp_sdk.rbs → _private/mcp.rbs} +0 -0
- /data/sig/{stubs → _private}/zeitwerk.rbs +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 91f54f388c63e670d155ae2b8d84bdafe18edd2f29423d92b52c9bfb686332d3
|
|
4
|
+
data.tar.gz: 7b1bc3bb32951cadbadfe4ba388ca4dc805849a2724cbfae944daf1a59ea241c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5e573dd0b3ad056266a7c8a88b24754a510ceac1737f730cedf98e73dc18be17e2f9dc93ebe47d4c89e2d483c1c40c40a7fcbad586ba1ceac949b11af2011a8c
|
|
7
|
+
data.tar.gz: c1aa387e0298c3999da9da89a640a86048207dd54abe6a336781a9dd265acec49939729fcca7378872efa595131357601b5639b2437508225470fe473a9a5989
|
data/.agents/rbs-inline.md
CHANGED
|
@@ -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:
|
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
data/lib/riffer/agent/context.rb
CHANGED
|
@@ -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]
|
data/lib/riffer/agent/session.rb
CHANGED
data/lib/riffer/agent.rb
CHANGED
|
@@ -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
|
#
|
data/lib/riffer/evals/judge.rb
CHANGED
|
@@ -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"
|
data/lib/riffer/mcp/client.rb
CHANGED
|
@@ -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
|
|
data/lib/riffer/mcp/registry.rb
CHANGED
|
@@ -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
|
|
|
@@ -92,15 +92,16 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
#--
|
|
95
|
-
#: (Hash[Symbol, untyped]) ->
|
|
95
|
+
#: (Hash[Symbol, untyped]) -> untyped
|
|
96
96
|
def execute_generate(params)
|
|
97
97
|
@client.converse(**params)
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
#--
|
|
101
|
-
#: (
|
|
101
|
+
#: (untyped) -> Riffer::Providers::TokenUsage?
|
|
102
102
|
def extract_token_usage(response)
|
|
103
|
-
|
|
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
|
-
#: (
|
|
115
|
+
#: (untyped) -> String
|
|
115
116
|
def extract_content(response)
|
|
116
|
-
|
|
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
|
-
#: (
|
|
131
|
+
#: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
|
|
130
132
|
def extract_tool_calls(response)
|
|
131
|
-
|
|
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
|
-
#: (
|
|
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:
|
|
202
|
-
name: decode_tool_name(
|
|
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
|
-
#: (
|
|
212
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
209
213
|
def handle_content_block_delta_text_delta(event, state:, yielder:)
|
|
210
|
-
|
|
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
|
-
#: (
|
|
222
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
218
223
|
def handle_content_block_delta_tool_use(event, state:, yielder:)
|
|
219
|
-
|
|
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
|
-
#: (
|
|
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
|
-
#: (
|
|
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
|
-
#: (
|
|
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:
|
|
256
|
-
output_tokens:
|
|
257
|
-
cache_creation_tokens:
|
|
258
|
-
cache_read_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]) ->
|
|
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
|
-
#: (
|
|
86
|
+
#: (untyped) -> Riffer::Providers::TokenUsage?
|
|
87
87
|
def extract_token_usage(response)
|
|
88
|
-
|
|
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
|
-
#: (
|
|
100
|
+
#: (untyped) -> String
|
|
100
101
|
def extract_content(response)
|
|
101
|
-
|
|
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
|
-
#: (
|
|
116
|
+
#: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
|
|
115
117
|
def extract_tool_calls(response)
|
|
116
|
-
|
|
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:
|
|
291
|
+
#: (untyped, accumulated_message: untyped, yielder: Enumerator::Yielder) -> void
|
|
289
292
|
def handle_message_stop(_event, accumulated_message:, yielder:)
|
|
290
|
-
|
|
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]) ->
|
|
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
|
-
#: (
|
|
75
|
+
#: (untyped) -> Riffer::Providers::TokenUsage?
|
|
76
76
|
def extract_token_usage(response)
|
|
77
|
-
|
|
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
|
-
#: (
|
|
88
|
+
#: (untyped) -> String
|
|
88
89
|
def extract_content(response)
|
|
90
|
+
typed_response = response #: OpenAI::Models::Responses::Response
|
|
89
91
|
text_content = ""
|
|
90
92
|
|
|
91
|
-
|
|
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
|
-
#: (
|
|
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
|
-
|
|
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]) ->
|
|
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
|
-
#: (
|
|
73
|
+
#: (untyped) -> Riffer::Providers::TokenUsage?
|
|
74
74
|
def extract_token_usage(response)
|
|
75
|
-
|
|
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
|
-
#: (
|
|
86
|
+
#: (untyped) -> String
|
|
86
87
|
def extract_content(response)
|
|
87
|
-
response
|
|
88
|
+
typed_response = response #: OpenAI::Models::Chat::ChatCompletion
|
|
89
|
+
typed_response.choices.first&.message&.content || ""
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
#--
|
|
91
|
-
#: (
|
|
93
|
+
#: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
|
|
92
94
|
def extract_tool_calls(response)
|
|
93
|
-
|
|
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
|
-
#: (
|
|
151
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
149
152
|
def handle_stream_chunk(chunk, state:, yielder:)
|
|
150
|
-
|
|
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
|
|
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:
|
|
168
|
-
output_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
|
-
#: (
|
|
178
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
175
179
|
def handle_text_delta(delta, state:, yielder:)
|
|
176
|
-
|
|
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
|
-
#: (
|
|
202
|
+
#: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
|
|
198
203
|
def handle_tool_call_deltas(delta, state:, yielder:)
|
|
199
|
-
|
|
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
|
-
#: (
|
|
245
|
+
#: (untyped) -> bool
|
|
240
246
|
def finish_reason_is_tool_calls?(choice)
|
|
241
|
-
choice
|
|
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
|
#--
|
data/lib/riffer/runner/fibers.rb
CHANGED
|
@@ -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.
|
data/lib/riffer/skills/config.rb
CHANGED
|
@@ -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.
|