riffer 0.49.0 → 0.50.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.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +13 -0
- data/docs/STREAM_EVENTS.md +3 -3
- data/docs/providers/ANTHROPIC.md +10 -0
- data/docs/providers/AZURE_OPENAI.md +2 -0
- data/docs/providers/OPENAI.md +22 -0
- data/lib/riffer/providers/amazon_bedrock.rb +2 -9
- data/lib/riffer/providers/anthropic.rb +58 -43
- data/lib/riffer/providers/azure_open_ai.rb +7 -0
- data/lib/riffer/providers/open_ai.rb +87 -29
- data/lib/riffer/version.rb +1 -1
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +0 -4
- data/sig/generated/riffer/providers/anthropic.rbs +23 -12
- data/sig/generated/riffer/providers/azure_open_ai.rbs +5 -0
- data/sig/generated/riffer/providers/open_ai.rbs +34 -10
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1700c8028d7de9b0e109fc259f904c8a8ff1e92964e23c90cd00574cd43a0bc2
|
|
4
|
+
data.tar.gz: 1245b96f6a1490aa5cedd3d5e60ff062a8d70cc7330bd7ec01d0f027e05d65fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dc40857b3085259180f014442cd8d2ec4c6f6f85d2c577cacbff6332176eb39e948de9e20b22732730e52ea4151016fcf23c3387de181f8b8ff27537df8cc21f
|
|
7
|
+
data.tar.gz: '0152801a535973a01216aac4ff1a5b07554b2e8bde282a9dd3fc2c09408b0ddb7890953e7d60c21102d0889896190adf88d6822ef0ca0ff4a6564b688f7b4dd2'
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ 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.50.0](https://github.com/janeapp/riffer/compare/riffer/v0.49.0...riffer/v0.50.0) (2026-09-28)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **anthropic:** capture and replay thinking blocks ([#463](https://github.com/janeapp/riffer/issues/463)) ([4a25851](https://github.com/janeapp/riffer/commit/4a25851141cb9b859778e38e58d701cbfe72939f))
|
|
14
|
+
* **open_ai:** capture and replay reasoning items ([#464](https://github.com/janeapp/riffer/issues/464)) ([03d54a9](https://github.com/janeapp/riffer/commit/03d54a90547797a8c56321527fd73ae1d3b9becc))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* **providers:** keep every text block in assistant content ([#468](https://github.com/janeapp/riffer/issues/468)) ([db14e7a](https://github.com/janeapp/riffer/commit/db14e7ac35bd764c1efa7eb2c7bea95c8050a984))
|
|
20
|
+
|
|
8
21
|
## [0.49.0](https://github.com/janeapp/riffer/compare/riffer/v0.48.0...riffer/v0.49.0) (2026-09-27)
|
|
9
22
|
|
|
10
23
|
|
data/docs/STREAM_EVENTS.md
CHANGED
|
@@ -53,7 +53,7 @@ event.content # => "Hello, how can I help you?"
|
|
|
53
53
|
event.to_h # => {role: :assistant, content: "Hello, how can I help you?"}
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
Fires once per model response, after its last `TextDelta`, and contains the response's full text: every text block the provider returned, concatenated in order, so it always equals the joined `TextDelta` contents. A response that carries no text (for example, only tool calls) emits no `TextDone`.
|
|
57
57
|
|
|
58
58
|
### ToolCallDelta
|
|
59
59
|
|
|
@@ -303,8 +303,8 @@ end
|
|
|
303
303
|
|
|
304
304
|
When an agent uses tools during streaming, the flow is:
|
|
305
305
|
|
|
306
|
-
1.
|
|
307
|
-
2. If
|
|
306
|
+
1. `TextDelta`, `ToolCallDelta` and `ToolCallDone` events stream in, in whatever order the model produces them. Text can come before, between or after tool calls.
|
|
307
|
+
2. If the response contained any text, one `TextDone` follows with the full text. A response that only calls tools has no `TextDone`.
|
|
308
308
|
3. Agent executes tools internally
|
|
309
309
|
4. Agent sends results back to LLM
|
|
310
310
|
5. More text events stream in
|
data/docs/providers/ANTHROPIC.md
CHANGED
|
@@ -187,6 +187,16 @@ agent.stream("Solve this complex math problem").each do |event|
|
|
|
187
187
|
end
|
|
188
188
|
```
|
|
189
189
|
|
|
190
|
+
The reasoning is kept on the assistant message as [reasoning parts](../MESSAGES.md#reasoning), whether you call `generate` or `stream`. Read it with `response.reasoning`, or as plain text with `reasoning_text` on the message. Each `thinking` block becomes a `:text` part carrying its `signature`, and each `redacted_thinking` block becomes an `:encrypted` part whose `data` is the block's opaque payload. `reasoning_text` leaves the redacted parts out, since they have no readable text. Every part is tagged `format: "anthropic-messages-v1"` (`Riffer::Providers::Anthropic::REASONING_FORMAT`).
|
|
191
|
+
|
|
192
|
+
### Reasoning Replay
|
|
193
|
+
|
|
194
|
+
Riffer sends the reasoning back to Anthropic on every later turn, as `thinking` and `redacted_thinking` blocks ahead of the message's text and `tool_use` blocks, in their original order and unchanged. For tool calls, sending it back is required: when thinking is enabled, Claude needs its earlier thinking blocks back to carry on after a tool result. You don't have to do anything; it happens as long as the assistant messages stay in the history.
|
|
195
|
+
|
|
196
|
+
If you persist sessions, keep the `reasoning` key when you store messages (see [Messages — Reasoning](../MESSAGES.md#reasoning)). If you drop it, later turns lose the model's earlier reasoning, and a tool-calling turn with thinking enabled may be rejected.
|
|
197
|
+
|
|
198
|
+
Only reasoning that this provider produced is sent back to Anthropic. A conversation that switches providers midway still works: reasoning from other providers (including OpenRouter's `anthropic-claude-v1` parts) is kept on the messages but left out of Anthropic requests.
|
|
199
|
+
|
|
190
200
|
## Web Search
|
|
191
201
|
|
|
192
202
|
Web search allows Claude to search the web for up-to-date information. When enabled, the provider injects the `web_search_20250305` server tool into the request.
|
|
@@ -87,6 +87,8 @@ Enables extended thinking (for supported models):
|
|
|
87
87
|
model_options reasoning: 'medium' # 'low', 'medium', or 'high'
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
+
Reasoning is captured and replayed on later turns exactly as with the [OpenAI provider](OPENAI.md#reasoning-replay), except that parts are tagged `format: "azure-openai-v1"`. Azure only accepts reasoning produced by the same resource, so parts from the OpenAI provider are never sent to Azure, and Azure's are never sent to OpenAI.
|
|
91
|
+
|
|
90
92
|
### structured_output
|
|
91
93
|
|
|
92
94
|
Structured JSON output works identically to the OpenAI provider.
|
data/docs/providers/OPENAI.md
CHANGED
|
@@ -82,6 +82,28 @@ model_options reasoning: 'medium' # 'low', 'medium', or 'high'
|
|
|
82
82
|
|
|
83
83
|
When reasoning is enabled, you'll receive `ReasoningDelta` and `ReasoningDone` events during streaming.
|
|
84
84
|
|
|
85
|
+
#### Reasoning Replay
|
|
86
|
+
|
|
87
|
+
Each `reasoning` item in a Responses API output becomes [reasoning parts](../MESSAGES.md#reasoning) on the assistant message, on both `generate_text` and `stream_text`, all tagged `format: "openai-v1"` and carrying the item's `id`:
|
|
88
|
+
|
|
89
|
+
| Reasoning item field | `ReasoningPart` |
|
|
90
|
+
| -------------------- | --------------------------------------- |
|
|
91
|
+
| each `summary` entry | a `:summary` part with its `text` |
|
|
92
|
+
| each `content` entry | a `:text` part with its `text` |
|
|
93
|
+
| `encrypted_content` | one `:encrypted` part with it as `data` |
|
|
94
|
+
|
|
95
|
+
The `:encrypted` part is always present, even when the response carried no `encrypted_content`, so the item's `id` is kept. When streaming, the parts are yielded as `ReasoningDone` events once the item completes; summary text still arrives as `ReasoningDelta` events before that.
|
|
96
|
+
|
|
97
|
+
On the next request, parts sharing an `id` go back as one `reasoning` item, ahead of the assistant's text and function calls, in their original order and unchanged. This lets reasoning models carry their reasoning across turns and tool-call loops. You don't have to do anything as long as the assistant messages stay in the history; if you persist sessions, keep the `reasoning` key when you store them.
|
|
98
|
+
|
|
99
|
+
With `store` left on (the API default), OpenAI can resolve a replayed item from its `id` alone. With `model_options store: false`, the item can only be replayed from its encrypted payload, so request it explicitly:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
model_options reasoning: 'medium', store: false, include: ['reasoning.encrypted_content']
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Only parts tagged `openai-v1` are sent. Reasoning from other providers stays on the messages but is left out, including OpenRouter's `openai-responses-v1` parts, which are encrypted under OpenRouter's organization rather than yours.
|
|
106
|
+
|
|
85
107
|
### web_search
|
|
86
108
|
|
|
87
109
|
Enable server-side web search using OpenAI's `web_search_preview` tool. Pass `true` to use defaults or a hash to merge with the tool definition:
|
|
@@ -298,11 +298,11 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
298
298
|
handle_content_block_delta_tool_use(event, state: current_state, yielder: yielder) if event.delta&.tool_use
|
|
299
299
|
handle_reasoning_delta(event, state: current_state, yielder: yielder) if event.delta&.reasoning_content
|
|
300
300
|
when Aws::BedrockRuntime::Types::ContentBlockStopEvent
|
|
301
|
-
handle_content_block_stop_text_delta(event, state: current_state, yielder: yielder) if current_state[:text]
|
|
302
301
|
handle_content_block_stop_tool_use(event, state: current_state, yielder: yielder) if current_state[:tool_call]
|
|
303
302
|
handle_reasoning_stop(event, state: current_state, yielder: yielder) if current_state[:reasoning]
|
|
304
303
|
when Aws::BedrockRuntime::Types::MessageStopEvent
|
|
305
304
|
stream_completed = true
|
|
305
|
+
yielder << Riffer::StreamEvents::TextDone.new(current_state[:text]) if current_state[:text]
|
|
306
306
|
yield_finish_reason(yielder, build_finish_reason(event.stop_reason))
|
|
307
307
|
when Aws::BedrockRuntime::Types::ConverseStreamMetadataEvent
|
|
308
308
|
handle_metadata_usage(event, state: current_state, yielder: yielder) if event.usage
|
|
@@ -362,7 +362,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
362
362
|
typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockDeltaEvent
|
|
363
363
|
delta_text = typed_event.delta.text
|
|
364
364
|
# << avoids += copying the whole buffer per delta (O(n^2)); safe because
|
|
365
|
-
# nothing reads the
|
|
365
|
+
# nothing reads the buffer before message stop hands it to TextDone.
|
|
366
366
|
state[:text] ||= +""
|
|
367
367
|
state[:text] << delta_text
|
|
368
368
|
yielder << Riffer::StreamEvents::TextDelta.new(delta_text)
|
|
@@ -406,13 +406,6 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
406
406
|
state[:reasoning] = nil
|
|
407
407
|
end
|
|
408
408
|
|
|
409
|
-
#--
|
|
410
|
-
#: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
411
|
-
def handle_content_block_stop_text_delta(_event, state:, yielder:)
|
|
412
|
-
yielder << Riffer::StreamEvents::TextDone.new(state[:text])
|
|
413
|
-
state[:text] = nil
|
|
414
|
-
end
|
|
415
|
-
|
|
416
409
|
#--
|
|
417
410
|
#: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
418
411
|
def handle_content_block_stop_tool_use(_event, state:, yielder:)
|
|
@@ -16,6 +16,11 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
16
16
|
"pause_turn" => :other,
|
|
17
17
|
}.freeze #: Hash[String, Symbol]
|
|
18
18
|
|
|
19
|
+
# Only parts carrying this tag are replayed, so reasoning captured by another
|
|
20
|
+
# adapter, whose signatures the Messages API may not accept, never reaches the
|
|
21
|
+
# request.
|
|
22
|
+
REASONING_FORMAT = "anthropic-messages-v1" #: String
|
|
23
|
+
|
|
19
24
|
#--
|
|
20
25
|
#: (?String?) -> singleton(Riffer::Skills::Adapter)
|
|
21
26
|
def self.skills_adapter(_model = nil)
|
|
@@ -153,16 +158,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
153
158
|
#: (untyped) -> String
|
|
154
159
|
def extract_content(response)
|
|
155
160
|
message = response #: Anthropic::Models::Message
|
|
156
|
-
|
|
157
|
-
return "" if content_blocks.nil? || content_blocks.empty?
|
|
158
|
-
|
|
159
|
-
text_content = ""
|
|
160
|
-
|
|
161
|
-
content_blocks.each do |block|
|
|
162
|
-
text_content = block.text if block.is_a?(::Anthropic::Models::TextBlock)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
text_content
|
|
161
|
+
(message.content || []).filter_map { |block| block.text if block.is_a?(::Anthropic::Models::TextBlock) }.join
|
|
166
162
|
end
|
|
167
163
|
|
|
168
164
|
#--
|
|
@@ -187,12 +183,34 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
187
183
|
tool_calls
|
|
188
184
|
end
|
|
189
185
|
|
|
186
|
+
#--
|
|
187
|
+
#: (untyped) -> Array[Riffer::Messages::Assistant::ReasoningPart]
|
|
188
|
+
def extract_reasoning(response)
|
|
189
|
+
message = response #: Anthropic::Models::Message
|
|
190
|
+
(message.content || []).filter_map { |block| build_reasoning_part(block) }
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
#--
|
|
194
|
+
#: (untyped) -> Riffer::Messages::Assistant::ReasoningPart?
|
|
195
|
+
def build_reasoning_part(block)
|
|
196
|
+
case block
|
|
197
|
+
when ::Anthropic::Models::ThinkingBlock
|
|
198
|
+
Riffer::Messages::Assistant::ReasoningPart.new(
|
|
199
|
+
type: :text,
|
|
200
|
+
text: block.thinking,
|
|
201
|
+
signature: block.signature,
|
|
202
|
+
format: REASONING_FORMAT,
|
|
203
|
+
)
|
|
204
|
+
when ::Anthropic::Models::RedactedThinkingBlock
|
|
205
|
+
Riffer::Messages::Assistant::ReasoningPart.new(type: :encrypted, data: block.data, format: REASONING_FORMAT)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
190
209
|
#--
|
|
191
210
|
#: (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
|
|
192
211
|
def execute_stream(params, yielder)
|
|
193
212
|
current_state = {
|
|
194
213
|
text: nil,
|
|
195
|
-
reasoning: nil,
|
|
196
214
|
tool_call: nil,
|
|
197
215
|
web_search_index: nil,
|
|
198
216
|
web_search_json: nil,
|
|
@@ -218,19 +236,15 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
218
236
|
when ::Anthropic::Helpers::Streaming::TextEvent
|
|
219
237
|
handle_text_event(event, state: current_state, yielder: yielder)
|
|
220
238
|
when ::Anthropic::Helpers::Streaming::ThinkingEvent
|
|
221
|
-
|
|
239
|
+
yielder << Riffer::StreamEvents::ReasoningDelta.new(event.thinking)
|
|
222
240
|
when ::Anthropic::Helpers::Streaming::InputJsonEvent
|
|
223
241
|
handle_input_json_event(event, state: current_state, yielder: yielder)
|
|
224
242
|
when ::Anthropic::Helpers::Streaming::ContentBlockStopEvent
|
|
225
243
|
case event.content_block
|
|
226
|
-
when ::Anthropic::Models::TextBlock
|
|
227
|
-
handle_content_block_stop_text(event, state: current_state, yielder: yielder) if current_state[:text]
|
|
228
244
|
when ::Anthropic::Models::ToolUseBlock
|
|
229
245
|
handle_content_block_stop_tool_use(event, state: current_state, yielder: yielder)
|
|
230
|
-
when ::Anthropic::Models::ThinkingBlock
|
|
231
|
-
|
|
232
|
-
handle_content_block_stop_thinking(event, state: current_state, yielder: yielder)
|
|
233
|
-
end
|
|
246
|
+
when ::Anthropic::Models::ThinkingBlock, ::Anthropic::Models::RedactedThinkingBlock
|
|
247
|
+
handle_content_block_stop_reasoning(event, yielder: yielder)
|
|
234
248
|
when ::Anthropic::Models::ServerToolUseBlock
|
|
235
249
|
handle_content_block_stop_server_tool_use(event, state: current_state, yielder: yielder)
|
|
236
250
|
when ::Anthropic::Models::WebSearchToolResultBlock
|
|
@@ -238,7 +252,9 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
238
252
|
end
|
|
239
253
|
when ::Anthropic::Helpers::Streaming::MessageStopEvent
|
|
240
254
|
stream_completed = true
|
|
241
|
-
handle_message_stop(
|
|
255
|
+
handle_message_stop(
|
|
256
|
+
event, state: current_state, accumulated_message: stream.accumulated_message, yielder: yielder,
|
|
257
|
+
)
|
|
242
258
|
end
|
|
243
259
|
end
|
|
244
260
|
ensure
|
|
@@ -275,21 +291,13 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
275
291
|
#--
|
|
276
292
|
#: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
277
293
|
def handle_text_event(event, state:, yielder:)
|
|
278
|
-
# Mutating append avoids O(n^2) copying
|
|
279
|
-
#
|
|
294
|
+
# Mutating append avoids O(n^2) copying across the response; the buffer
|
|
295
|
+
# reaches TextDone only at message stop, so no reader sees it mid-append.
|
|
280
296
|
state[:text] ||= +""
|
|
281
297
|
state[:text] << event.text
|
|
282
298
|
yielder << Riffer::StreamEvents::TextDelta.new(event.text)
|
|
283
299
|
end
|
|
284
300
|
|
|
285
|
-
#--
|
|
286
|
-
#: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
287
|
-
def handle_thinking_event(event, state:, yielder:)
|
|
288
|
-
state[:reasoning] ||= +""
|
|
289
|
-
state[:reasoning] << event.thinking
|
|
290
|
-
yielder << Riffer::StreamEvents::ReasoningDelta.new(event.thinking)
|
|
291
|
-
end
|
|
292
|
-
|
|
293
301
|
#--
|
|
294
302
|
#: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
295
303
|
def handle_input_json_event(event, state:, yielder:)
|
|
@@ -320,18 +328,13 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
320
328
|
state[:tool_call] = nil
|
|
321
329
|
end
|
|
322
330
|
|
|
331
|
+
# The SDK's accumulated block already carries the full thinking text and the
|
|
332
|
+
# signature from its trailing signature_delta.
|
|
323
333
|
#--
|
|
324
|
-
#: (untyped,
|
|
325
|
-
def
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
#--
|
|
331
|
-
#: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
332
|
-
def handle_content_block_stop_text(_event, state:, yielder:)
|
|
333
|
-
yielder << Riffer::StreamEvents::TextDone.new(state[:text])
|
|
334
|
-
state[:text] = nil
|
|
334
|
+
#: (untyped, yielder: Riffer::Providers::_EventSink) -> void
|
|
335
|
+
def handle_content_block_stop_reasoning(event, yielder:)
|
|
336
|
+
part = build_reasoning_part(event.content_block) #: Riffer::Messages::Assistant::ReasoningPart
|
|
337
|
+
yielder << Riffer::StreamEvents::ReasoningDone.new(part)
|
|
335
338
|
end
|
|
336
339
|
|
|
337
340
|
#--
|
|
@@ -361,8 +364,10 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
361
364
|
end
|
|
362
365
|
|
|
363
366
|
#--
|
|
364
|
-
#: (untyped, accumulated_message: untyped, yielder: Riffer::Providers::_EventSink) -> void
|
|
365
|
-
def handle_message_stop(_event, accumulated_message:, yielder:)
|
|
367
|
+
#: (untyped, state: Hash[Symbol, untyped], accumulated_message: untyped, yielder: Riffer::Providers::_EventSink) -> void
|
|
368
|
+
def handle_message_stop(_event, state:, accumulated_message:, yielder:)
|
|
369
|
+
yielder << Riffer::StreamEvents::TextDone.new(state[:text]) if state[:text]
|
|
370
|
+
|
|
366
371
|
message = accumulated_message #: Anthropic::Models::Message?
|
|
367
372
|
yield_finish_reason(yielder, build_finish_reason(message&.stop_reason))
|
|
368
373
|
|
|
@@ -413,7 +418,9 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
413
418
|
#--
|
|
414
419
|
#: (Riffer::Messages::Assistant) -> Hash[Symbol, untyped]
|
|
415
420
|
def convert_assistant_to_anthropic_format(message)
|
|
416
|
-
content =
|
|
421
|
+
content = message.reasoning.filter_map do |part|
|
|
422
|
+
convert_reasoning_part_to_anthropic_format(part) if part.format == REASONING_FORMAT
|
|
423
|
+
end
|
|
417
424
|
content << { type: "text", text: message.content } if message.content && !message.content.empty?
|
|
418
425
|
|
|
419
426
|
message.tool_calls.each do |tc|
|
|
@@ -428,6 +435,14 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
428
435
|
{ role: "assistant", content: content }
|
|
429
436
|
end
|
|
430
437
|
|
|
438
|
+
#--
|
|
439
|
+
#: (Riffer::Messages::Assistant::ReasoningPart) -> Hash[Symbol, untyped]
|
|
440
|
+
def convert_reasoning_part_to_anthropic_format(part)
|
|
441
|
+
return { type: "redacted_thinking", data: part.data } if part.type == :encrypted
|
|
442
|
+
|
|
443
|
+
{ type: "thinking", thinking: part.text, signature: part.signature }
|
|
444
|
+
end
|
|
445
|
+
|
|
431
446
|
#--
|
|
432
447
|
#: (Riffer::Messages::User::FilePart) -> Hash[Symbol, untyped]
|
|
433
448
|
def convert_file_part_to_anthropic_format(file)
|
|
@@ -26,4 +26,11 @@ class Riffer::Providers::AzureOpenAI < Riffer::Providers::OpenAI
|
|
|
26
26
|
# OpenAI credential elsewhere. A nil raises in the SDK instead.
|
|
27
27
|
::OpenAI::Client.new(api_key: api_key, base_url: base_url)
|
|
28
28
|
end
|
|
29
|
+
|
|
30
|
+
# Azure resolves and decrypts reasoning items only within the resource that produced them.
|
|
31
|
+
#--
|
|
32
|
+
#: () -> String
|
|
33
|
+
def reasoning_format
|
|
34
|
+
"azure-openai-v1"
|
|
35
|
+
end
|
|
29
36
|
end
|
|
@@ -173,16 +173,12 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
173
173
|
#: (untyped) -> String
|
|
174
174
|
def extract_content(response)
|
|
175
175
|
typed_response = response #: OpenAI::Models::Responses::Response
|
|
176
|
-
text_content = ""
|
|
177
176
|
|
|
178
|
-
typed_response.output.
|
|
179
|
-
next unless item.is_a?(::OpenAI::Models::Responses::ResponseOutputMessage)
|
|
180
|
-
|
|
181
|
-
text_block = item.content.find { |c| c.is_a?(::OpenAI::Models::Responses::ResponseOutputText) }
|
|
182
|
-
text_content = text_block.text if text_block.is_a?(::OpenAI::Models::Responses::ResponseOutputText)
|
|
183
|
-
end
|
|
177
|
+
typed_response.output.flat_map do |item|
|
|
178
|
+
next [] unless item.is_a?(::OpenAI::Models::Responses::ResponseOutputMessage)
|
|
184
179
|
|
|
185
|
-
|
|
180
|
+
item.content.filter_map { |c| c.text if c.is_a?(::OpenAI::Models::Responses::ResponseOutputText) }
|
|
181
|
+
end.join
|
|
186
182
|
end
|
|
187
183
|
|
|
188
184
|
#--
|
|
@@ -204,10 +200,47 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
204
200
|
tool_calls
|
|
205
201
|
end
|
|
206
202
|
|
|
203
|
+
#--
|
|
204
|
+
#: (untyped) -> Array[Riffer::Messages::Assistant::ReasoningPart]
|
|
205
|
+
def extract_reasoning(response)
|
|
206
|
+
typed_response = response #: OpenAI::Models::Responses::Response
|
|
207
|
+
typed_response.output.
|
|
208
|
+
grep(::OpenAI::Models::Responses::ResponseReasoningItem).
|
|
209
|
+
flat_map { |item| build_reasoning_parts(item) }
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# The trailing +:encrypted+ part is always present, even without a payload, so the item's id
|
|
213
|
+
# survives for replay: with +store+ on, OpenAI resolves the item from the id alone.
|
|
214
|
+
#--
|
|
215
|
+
#: (untyped) -> Array[Riffer::Messages::Assistant::ReasoningPart]
|
|
216
|
+
def build_reasoning_parts(item)
|
|
217
|
+
typed_item = item #: OpenAI::Models::Responses::ResponseReasoningItem
|
|
218
|
+
id = typed_item.id
|
|
219
|
+
summaries = typed_item.summary.map { |summary| build_reasoning_part(type: :summary, id: id, text: summary.text) }
|
|
220
|
+
texts = (typed_item.content || []).map { |content| build_reasoning_part(type: :text, id: id, text: content.text) }
|
|
221
|
+
|
|
222
|
+
[*summaries, *texts, build_reasoning_part(type: :encrypted, id: id, data: typed_item.encrypted_content)]
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
#--
|
|
226
|
+
#: (type: Symbol, id: String, ?text: String?, ?data: String?) -> Riffer::Messages::Assistant::ReasoningPart
|
|
227
|
+
def build_reasoning_part(type:, id:, text: nil, data: nil)
|
|
228
|
+
Riffer::Messages::Assistant::ReasoningPart.new(type: type, text: text, data: data, id: id, format: reasoning_format)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Distinct from OpenRouter's +openai-responses-v1+: reasoning relayed through OpenRouter is
|
|
232
|
+
# encrypted and stored under OpenRouter's organization, so neither side can replay the other's.
|
|
233
|
+
#--
|
|
234
|
+
#: () -> String
|
|
235
|
+
def reasoning_format
|
|
236
|
+
"openai-v1"
|
|
237
|
+
end
|
|
238
|
+
|
|
207
239
|
#--
|
|
208
240
|
#: (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
|
|
209
241
|
def execute_stream(params, yielder)
|
|
210
242
|
current_state = {
|
|
243
|
+
text: nil,
|
|
211
244
|
tool_info: {},
|
|
212
245
|
} #: Hash[Symbol, untyped]
|
|
213
246
|
|
|
@@ -223,12 +256,8 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
223
256
|
end
|
|
224
257
|
when :"response.output_text.delta"
|
|
225
258
|
handle_output_text_delta(event, state: current_state, yielder: yielder)
|
|
226
|
-
when :"response.output_text.done"
|
|
227
|
-
handle_output_text_done(event, state: current_state, yielder: yielder)
|
|
228
259
|
when :"response.reasoning_summary_text.delta"
|
|
229
260
|
handle_reasoning_summary_text_delta(event, state: current_state, yielder: yielder)
|
|
230
|
-
when :"response.reasoning_summary_text.done"
|
|
231
|
-
handle_reasoning_summary_text_done(event, state: current_state, yielder: yielder)
|
|
232
261
|
when :"response.function_call_arguments.delta"
|
|
233
262
|
handle_function_call_arguments_delta(event, state: current_state, yielder: yielder)
|
|
234
263
|
when :"response.function_call_arguments.done"
|
|
@@ -240,7 +269,10 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
240
269
|
when :"response.web_search_call.completed"
|
|
241
270
|
handle_web_search_status(event, status: "completed", yielder: yielder)
|
|
242
271
|
when :"response.output_item.done"
|
|
243
|
-
|
|
272
|
+
case event.item&.type
|
|
273
|
+
when :web_search_call then handle_output_item_done_web_search(event, yielder: yielder)
|
|
274
|
+
when :reasoning then handle_output_item_done_reasoning(event, yielder: yielder)
|
|
275
|
+
end
|
|
244
276
|
when :"response.completed", :"response.incomplete", :"response.failed"
|
|
245
277
|
stream_completed = true
|
|
246
278
|
handle_response_finished(event, state: current_state, yielder: yielder)
|
|
@@ -270,27 +302,19 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
270
302
|
#--
|
|
271
303
|
#: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
272
304
|
def handle_output_text_delta(event, state:, yielder:)
|
|
305
|
+
# << avoids += copying the whole buffer per delta (O(n^2)); safe because
|
|
306
|
+
# nothing reads the buffer before the response finishes and TextDone takes it.
|
|
307
|
+
state[:text] ||= +""
|
|
308
|
+
state[:text] << event.delta
|
|
273
309
|
yielder << Riffer::StreamEvents::TextDelta.new(event.delta)
|
|
274
310
|
end
|
|
275
311
|
|
|
276
|
-
#--
|
|
277
|
-
#: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
278
|
-
def handle_output_text_done(event, state:, yielder:)
|
|
279
|
-
yielder << Riffer::StreamEvents::TextDone.new(event.text)
|
|
280
|
-
end
|
|
281
|
-
|
|
282
312
|
#--
|
|
283
313
|
#: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
284
314
|
def handle_reasoning_summary_text_delta(event, state:, yielder:)
|
|
285
315
|
yielder << Riffer::StreamEvents::ReasoningDelta.new(event.delta)
|
|
286
316
|
end
|
|
287
317
|
|
|
288
|
-
#--
|
|
289
|
-
#: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
290
|
-
def handle_reasoning_summary_text_done(event, state:, yielder:)
|
|
291
|
-
yield_reasoning_done(yielder, event.text)
|
|
292
|
-
end
|
|
293
|
-
|
|
294
318
|
#--
|
|
295
319
|
#: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
296
320
|
def handle_function_call_arguments_delta(event, state:, yielder:)
|
|
@@ -317,6 +341,8 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
317
341
|
#--
|
|
318
342
|
#: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
319
343
|
def handle_response_finished(event, state:, yielder:)
|
|
344
|
+
yielder << Riffer::StreamEvents::TextDone.new(state[:text]) if state[:text]
|
|
345
|
+
|
|
320
346
|
response = event.response
|
|
321
347
|
return unless response
|
|
322
348
|
|
|
@@ -348,6 +374,12 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
348
374
|
end
|
|
349
375
|
end
|
|
350
376
|
|
|
377
|
+
#--
|
|
378
|
+
#: (untyped, yielder: Riffer::Providers::_EventSink) -> void
|
|
379
|
+
def handle_output_item_done_reasoning(event, yielder:)
|
|
380
|
+
build_reasoning_parts(event.item).each { |part| yielder << Riffer::StreamEvents::ReasoningDone.new(part) }
|
|
381
|
+
end
|
|
382
|
+
|
|
351
383
|
#--
|
|
352
384
|
#: (Array[Riffer::Messages::Base]) -> Array[Hash[Symbol, untyped]]
|
|
353
385
|
def convert_messages_to_openai_format(messages)
|
|
@@ -378,12 +410,13 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
378
410
|
end
|
|
379
411
|
|
|
380
412
|
#--
|
|
381
|
-
#: (Riffer::Messages::Assistant) ->
|
|
413
|
+
#: (Riffer::Messages::Assistant) -> Array[Hash[Symbol, untyped]]
|
|
382
414
|
def convert_assistant_to_openai_format(message)
|
|
415
|
+
items = convert_reasoning_to_openai_format(message.reasoning)
|
|
416
|
+
|
|
383
417
|
if message.tool_calls.empty?
|
|
384
|
-
{ role: "assistant", content: message.content }
|
|
418
|
+
items << { role: "assistant", content: message.content }
|
|
385
419
|
else
|
|
386
|
-
items = [] #: Array[Hash[Symbol, untyped]]
|
|
387
420
|
if message.content && !message.content.empty?
|
|
388
421
|
items << { type: "message", role: "assistant",
|
|
389
422
|
content: message.content, }
|
|
@@ -396,8 +429,33 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
396
429
|
arguments: tc.arguments,
|
|
397
430
|
}
|
|
398
431
|
end
|
|
399
|
-
items
|
|
400
432
|
end
|
|
433
|
+
|
|
434
|
+
items
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
#--
|
|
438
|
+
#: (Array[Riffer::Messages::Assistant::ReasoningPart]) -> Array[Hash[Symbol, untyped]]
|
|
439
|
+
def convert_reasoning_to_openai_format(reasoning)
|
|
440
|
+
reasoning.
|
|
441
|
+
select { |part| part.format == reasoning_format }.
|
|
442
|
+
chunk_while { |a, b| a.id == b.id }.
|
|
443
|
+
map { |parts| convert_reasoning_item_to_openai_format(parts) }
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
#--
|
|
447
|
+
#: (Array[Riffer::Messages::Assistant::ReasoningPart]) -> Hash[Symbol, untyped]
|
|
448
|
+
def convert_reasoning_item_to_openai_format(parts)
|
|
449
|
+
summary = parts.filter_map { |part| { type: "summary_text", text: part.text } if part.type == :summary }
|
|
450
|
+
content = parts.filter_map { |part| { type: "reasoning_text", text: part.text } if part.type == :text }
|
|
451
|
+
|
|
452
|
+
{
|
|
453
|
+
type: "reasoning",
|
|
454
|
+
id: parts.first&.id,
|
|
455
|
+
summary: summary,
|
|
456
|
+
content: content.empty? ? nil : content,
|
|
457
|
+
encrypted_content: parts.find { |part| part.type == :encrypted }&.data,
|
|
458
|
+
}.compact
|
|
401
459
|
end
|
|
402
460
|
|
|
403
461
|
#--
|
data/lib/riffer/version.rb
CHANGED
|
@@ -120,10 +120,6 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
120
120
|
# : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
121
121
|
def handle_reasoning_stop: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
122
122
|
|
|
123
|
-
# --
|
|
124
|
-
# : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
125
|
-
def handle_content_block_stop_text_delta: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
126
|
-
|
|
127
123
|
# --
|
|
128
124
|
# : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
129
125
|
def handle_content_block_stop_tool_use: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
@@ -5,6 +5,11 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
5
5
|
|
|
6
6
|
FINISH_REASONS: Hash[String, Symbol]
|
|
7
7
|
|
|
8
|
+
# Only parts carrying this tag are replayed, so reasoning captured by another
|
|
9
|
+
# adapter, whose signatures the Messages API may not accept, never reaches the
|
|
10
|
+
# request.
|
|
11
|
+
REASONING_FORMAT: String
|
|
12
|
+
|
|
8
13
|
# --
|
|
9
14
|
# : (?String?) -> singleton(Riffer::Skills::Adapter)
|
|
10
15
|
def self.skills_adapter: (?String?) -> singleton(Riffer::Skills::Adapter)
|
|
@@ -59,6 +64,14 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
59
64
|
# : (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
|
|
60
65
|
def extract_tool_calls: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
|
|
61
66
|
|
|
67
|
+
# --
|
|
68
|
+
# : (untyped) -> Array[Riffer::Messages::Assistant::ReasoningPart]
|
|
69
|
+
def extract_reasoning: (untyped) -> Array[Riffer::Messages::Assistant::ReasoningPart]
|
|
70
|
+
|
|
71
|
+
# --
|
|
72
|
+
# : (untyped) -> Riffer::Messages::Assistant::ReasoningPart?
|
|
73
|
+
def build_reasoning_part: (untyped) -> Riffer::Messages::Assistant::ReasoningPart?
|
|
74
|
+
|
|
62
75
|
# --
|
|
63
76
|
# : (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
|
|
64
77
|
def execute_stream: (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
|
|
@@ -75,10 +88,6 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
75
88
|
# : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
76
89
|
def handle_text_event: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
77
90
|
|
|
78
|
-
# --
|
|
79
|
-
# : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
80
|
-
def handle_thinking_event: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
81
|
-
|
|
82
91
|
# --
|
|
83
92
|
# : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
84
93
|
def handle_input_json_event: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
@@ -87,13 +96,11 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
87
96
|
# : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
88
97
|
def handle_content_block_stop_tool_use: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
89
98
|
|
|
99
|
+
# The SDK's accumulated block already carries the full thinking text and the
|
|
100
|
+
# signature from its trailing signature_delta.
|
|
90
101
|
# --
|
|
91
|
-
# : (untyped,
|
|
92
|
-
def
|
|
93
|
-
|
|
94
|
-
# --
|
|
95
|
-
# : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
96
|
-
def handle_content_block_stop_text: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
102
|
+
# : (untyped, yielder: Riffer::Providers::_EventSink) -> void
|
|
103
|
+
def handle_content_block_stop_reasoning: (untyped, yielder: Riffer::Providers::_EventSink) -> void
|
|
97
104
|
|
|
98
105
|
# --
|
|
99
106
|
# : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
@@ -104,8 +111,8 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
104
111
|
def handle_content_block_stop_web_search_result: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
105
112
|
|
|
106
113
|
# --
|
|
107
|
-
# : (untyped, accumulated_message: untyped, yielder: Riffer::Providers::_EventSink) -> void
|
|
108
|
-
def handle_message_stop: (untyped, accumulated_message: untyped, yielder: Riffer::Providers::_EventSink) -> void
|
|
114
|
+
# : (untyped, state: Hash[Symbol, untyped], accumulated_message: untyped, yielder: Riffer::Providers::_EventSink) -> void
|
|
115
|
+
def handle_message_stop: (untyped, state: Hash[Symbol, untyped], accumulated_message: untyped, yielder: Riffer::Providers::_EventSink) -> void
|
|
109
116
|
|
|
110
117
|
# --
|
|
111
118
|
# : (Array[Riffer::Messages::Base]) -> Hash[Symbol, untyped]
|
|
@@ -115,6 +122,10 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
115
122
|
# : (Riffer::Messages::Assistant) -> Hash[Symbol, untyped]
|
|
116
123
|
def convert_assistant_to_anthropic_format: (Riffer::Messages::Assistant) -> Hash[Symbol, untyped]
|
|
117
124
|
|
|
125
|
+
# --
|
|
126
|
+
# : (Riffer::Messages::Assistant::ReasoningPart) -> Hash[Symbol, untyped]
|
|
127
|
+
def convert_reasoning_part_to_anthropic_format: (Riffer::Messages::Assistant::ReasoningPart) -> Hash[Symbol, untyped]
|
|
128
|
+
|
|
118
129
|
# --
|
|
119
130
|
# : (Riffer::Messages::User::FilePart) -> Hash[Symbol, untyped]
|
|
120
131
|
def convert_file_part_to_anthropic_format: (Riffer::Messages::User::FilePart) -> Hash[Symbol, untyped]
|
|
@@ -14,4 +14,9 @@ class Riffer::Providers::AzureOpenAI < Riffer::Providers::OpenAI
|
|
|
14
14
|
# --
|
|
15
15
|
# : () -> untyped
|
|
16
16
|
def build_client: () -> untyped
|
|
17
|
+
|
|
18
|
+
# Azure resolves and decrypts reasoning items only within the resource that produced them.
|
|
19
|
+
# --
|
|
20
|
+
# : () -> String
|
|
21
|
+
def reasoning_format: () -> String
|
|
17
22
|
end
|
|
@@ -65,6 +65,26 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
65
65
|
# : (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
|
|
66
66
|
def extract_tool_calls: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
|
|
67
67
|
|
|
68
|
+
# --
|
|
69
|
+
# : (untyped) -> Array[Riffer::Messages::Assistant::ReasoningPart]
|
|
70
|
+
def extract_reasoning: (untyped) -> Array[Riffer::Messages::Assistant::ReasoningPart]
|
|
71
|
+
|
|
72
|
+
# The trailing +:encrypted+ part is always present, even without a payload, so the item's id
|
|
73
|
+
# survives for replay: with +store+ on, OpenAI resolves the item from the id alone.
|
|
74
|
+
# --
|
|
75
|
+
# : (untyped) -> Array[Riffer::Messages::Assistant::ReasoningPart]
|
|
76
|
+
def build_reasoning_parts: (untyped) -> Array[Riffer::Messages::Assistant::ReasoningPart]
|
|
77
|
+
|
|
78
|
+
# --
|
|
79
|
+
# : (type: Symbol, id: String, ?text: String?, ?data: String?) -> Riffer::Messages::Assistant::ReasoningPart
|
|
80
|
+
def build_reasoning_part: (type: Symbol, id: String, ?text: String?, ?data: String?) -> Riffer::Messages::Assistant::ReasoningPart
|
|
81
|
+
|
|
82
|
+
# Distinct from OpenRouter's +openai-responses-v1+: reasoning relayed through OpenRouter is
|
|
83
|
+
# encrypted and stored under OpenRouter's organization, so neither side can replay the other's.
|
|
84
|
+
# --
|
|
85
|
+
# : () -> String
|
|
86
|
+
def reasoning_format: () -> String
|
|
87
|
+
|
|
68
88
|
# --
|
|
69
89
|
# : (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
|
|
70
90
|
def execute_stream: (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void
|
|
@@ -77,18 +97,10 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
77
97
|
# : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
78
98
|
def handle_output_text_delta: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
79
99
|
|
|
80
|
-
# --
|
|
81
|
-
# : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
82
|
-
def handle_output_text_done: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
83
|
-
|
|
84
100
|
# --
|
|
85
101
|
# : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
86
102
|
def handle_reasoning_summary_text_delta: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
87
103
|
|
|
88
|
-
# --
|
|
89
|
-
# : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
90
|
-
def handle_reasoning_summary_text_done: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
91
|
-
|
|
92
104
|
# --
|
|
93
105
|
# : (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
94
106
|
def handle_function_call_arguments_delta: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
|
|
@@ -109,13 +121,25 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
109
121
|
# : (untyped, yielder: Riffer::Providers::_EventSink) -> void
|
|
110
122
|
def handle_output_item_done_web_search: (untyped, yielder: Riffer::Providers::_EventSink) -> void
|
|
111
123
|
|
|
124
|
+
# --
|
|
125
|
+
# : (untyped, yielder: Riffer::Providers::_EventSink) -> void
|
|
126
|
+
def handle_output_item_done_reasoning: (untyped, yielder: Riffer::Providers::_EventSink) -> void
|
|
127
|
+
|
|
112
128
|
# --
|
|
113
129
|
# : (Array[Riffer::Messages::Base]) -> Array[Hash[Symbol, untyped]]
|
|
114
130
|
def convert_messages_to_openai_format: (Array[Riffer::Messages::Base]) -> Array[Hash[Symbol, untyped]]
|
|
115
131
|
|
|
116
132
|
# --
|
|
117
|
-
# : (Riffer::Messages::Assistant) ->
|
|
118
|
-
def convert_assistant_to_openai_format: (Riffer::Messages::Assistant) ->
|
|
133
|
+
# : (Riffer::Messages::Assistant) -> Array[Hash[Symbol, untyped]]
|
|
134
|
+
def convert_assistant_to_openai_format: (Riffer::Messages::Assistant) -> Array[Hash[Symbol, untyped]]
|
|
135
|
+
|
|
136
|
+
# --
|
|
137
|
+
# : (Array[Riffer::Messages::Assistant::ReasoningPart]) -> Array[Hash[Symbol, untyped]]
|
|
138
|
+
def convert_reasoning_to_openai_format: (Array[Riffer::Messages::Assistant::ReasoningPart]) -> Array[Hash[Symbol, untyped]]
|
|
139
|
+
|
|
140
|
+
# --
|
|
141
|
+
# : (Array[Riffer::Messages::Assistant::ReasoningPart]) -> Hash[Symbol, untyped]
|
|
142
|
+
def convert_reasoning_item_to_openai_format: (Array[Riffer::Messages::Assistant::ReasoningPart]) -> Hash[Symbol, untyped]
|
|
119
143
|
|
|
120
144
|
# --
|
|
121
145
|
# : (Riffer::Messages::User::FilePart) -> Hash[Symbol, untyped]
|