riffer 0.47.0 → 0.47.2
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/.claude/rules/rbs-inline.md +5 -1
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +16 -0
- data/Rakefile +1 -1
- data/Steepfile +1 -15
- data/docs/AGENTS.md +2 -2
- data/docs/STREAM_EVENTS.md +23 -0
- data/docs/providers/CUSTOM_PROVIDERS.md +2 -0
- data/lib/riffer/agent/config.rb +11 -11
- data/lib/riffer/agent/outcome.rb +2 -2
- data/lib/riffer/agent/response.rb +9 -9
- data/lib/riffer/agent/run.rb +0 -1
- data/lib/riffer/agent/session.rb +1 -1
- data/lib/riffer/agent/structured_output/result.rb +2 -2
- data/lib/riffer/agent/structured_output.rb +1 -1
- data/lib/riffer/agent.rb +11 -11
- data/lib/riffer/config.rb +30 -30
- data/lib/riffer/evals/judge.rb +2 -3
- data/lib/riffer/evals/result.rb +6 -6
- data/lib/riffer/evals/run_result.rb +1 -1
- data/lib/riffer/evals/scenario_result.rb +6 -6
- data/lib/riffer/guardrails/modification.rb +3 -3
- data/lib/riffer/guardrails/result.rb +3 -3
- data/lib/riffer/guardrails/runner.rb +4 -4
- data/lib/riffer/guardrails/tripwire.rb +4 -4
- data/lib/riffer/mcp/manifest.rb +5 -5
- data/lib/riffer/mcp/registration.rb +1 -1
- data/lib/riffer/mcp/search_tool.rb +1 -1
- data/lib/riffer/messages/assistant.rb +5 -5
- data/lib/riffer/messages/base.rb +2 -4
- data/lib/riffer/messages/file_part.rb +4 -7
- data/lib/riffer/messages/tool.rb +4 -4
- data/lib/riffer/messages/user.rb +1 -1
- data/lib/riffer/params/param.rb +29 -10
- data/lib/riffer/params.rb +3 -1
- data/lib/riffer/providers/amazon_bedrock.rb +29 -2
- data/lib/riffer/providers/anthropic.rb +7 -0
- data/lib/riffer/providers/base.rb +1 -2
- data/lib/riffer/providers/finish_reason.rb +2 -2
- data/lib/riffer/providers/mock.rb +1 -1
- data/lib/riffer/providers/open_ai.rb +8 -0
- data/lib/riffer/providers/repository.rb +0 -4
- data/lib/riffer/providers/token_usage.rb +5 -5
- data/lib/riffer/skills/adapter.rb +1 -1
- data/lib/riffer/skills/context.rb +4 -7
- data/lib/riffer/skills/filesystem_backend.rb +1 -2
- data/lib/riffer/skills/frontmatter.rb +4 -4
- data/lib/riffer/skills/xml_adapter.rb +1 -1
- data/lib/riffer/stream_events/base.rb +1 -1
- data/lib/riffer/stream_events/finish_reason_done.rb +2 -2
- data/lib/riffer/stream_events/guardrail_modification.rb +1 -1
- data/lib/riffer/stream_events/guardrail_tripwire.rb +1 -1
- data/lib/riffer/stream_events/interrupt.rb +2 -2
- data/lib/riffer/stream_events/reasoning_delta.rb +1 -1
- data/lib/riffer/stream_events/reasoning_done.rb +1 -1
- data/lib/riffer/stream_events/skill_activation.rb +1 -1
- data/lib/riffer/stream_events/text_delta.rb +1 -1
- data/lib/riffer/stream_events/text_done.rb +1 -1
- data/lib/riffer/stream_events/token_usage_done.rb +1 -1
- data/lib/riffer/stream_events/tool_call_delta.rb +3 -3
- data/lib/riffer/stream_events/tool_call_done.rb +4 -4
- data/lib/riffer/stream_events/web_search_done.rb +2 -2
- data/lib/riffer/stream_events/web_search_status.rb +3 -3
- data/lib/riffer/tools/response.rb +4 -4
- data/lib/riffer/tracing/stream_recorder.rb +6 -6
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +7 -0
- data/rbs_collection.lock.yaml +320 -0
- data/rbs_collection.yaml +12 -0
- data/sig/_private/anthropic.rbs +5 -4
- data/sig/_private/aws-sdk-core/event_error.rbs +15 -0
- data/sig/generated/riffer/params/param.rbs +10 -1
- data/sig/generated/riffer/params.rbs +2 -0
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +10 -1
- data/sig/generated/riffer/providers/repository.rbs +0 -2
- data/sig/generated/riffer.rbs +8 -0
- metadata +4 -3
- data/sig/_private/async.rbs +0 -28
- data/sig/_private/minitest.rbs +0 -9
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
# any blocks, execution stops and a tripwire is returned.
|
|
6
6
|
class Riffer::Guardrails::Runner
|
|
7
7
|
# The guardrail configs to execute.
|
|
8
|
-
attr_reader :guardrail_configs #: Array[Hash[Symbol, untyped]]
|
|
8
|
+
attr_reader :guardrail_configs #: Array[Hash[Symbol, untyped]] # @dynamic guardrail_configs
|
|
9
9
|
|
|
10
10
|
# The execution phase (:before or :after).
|
|
11
|
-
attr_reader :phase #: Symbol
|
|
11
|
+
attr_reader :phase #: Symbol # @dynamic phase
|
|
12
12
|
|
|
13
13
|
# The context passed to guardrails.
|
|
14
|
-
attr_reader :context #: untyped
|
|
14
|
+
attr_reader :context #: untyped # @dynamic context
|
|
15
15
|
|
|
16
16
|
# The normalized per-call tags, stamped as +riffer.tag.*+ on guardrail spans.
|
|
17
|
-
attr_reader :tags #: Hash[String, String]
|
|
17
|
+
attr_reader :tags #: Hash[String, String] # @dynamic tags
|
|
18
18
|
|
|
19
19
|
#--
|
|
20
20
|
#: (Array[Hash[Symbol, untyped]], phase: Symbol, ?context: untyped, ?tags: Hash[String, String]) -> void
|
|
@@ -6,16 +6,16 @@ class Riffer::Guardrails::Tripwire
|
|
|
6
6
|
PHASES = Riffer::Guardrails::PHASES #: Array[Symbol]
|
|
7
7
|
|
|
8
8
|
# The reason for blocking.
|
|
9
|
-
attr_reader :reason #: String
|
|
9
|
+
attr_reader :reason #: String # @dynamic reason
|
|
10
10
|
|
|
11
11
|
# The guardrail class that triggered the block.
|
|
12
|
-
attr_reader :guardrail #: singleton(Riffer::Guardrail)
|
|
12
|
+
attr_reader :guardrail #: singleton(Riffer::Guardrail) # @dynamic guardrail
|
|
13
13
|
|
|
14
14
|
# The phase when the block occurred (:before or :after).
|
|
15
|
-
attr_reader :phase #: Symbol
|
|
15
|
+
attr_reader :phase #: Symbol # @dynamic phase
|
|
16
16
|
|
|
17
17
|
# Optional metadata about the block.
|
|
18
|
-
attr_reader :metadata #: Hash[Symbol, untyped]?
|
|
18
|
+
attr_reader :metadata #: Hash[Symbol, untyped]? # @dynamic metadata
|
|
19
19
|
|
|
20
20
|
# Raises Riffer::ArgumentError if +phase+ is invalid.
|
|
21
21
|
#--
|
data/lib/riffer/mcp/manifest.rb
CHANGED
|
@@ -6,20 +6,20 @@ require "uri"
|
|
|
6
6
|
# Holds the configuration for a single MCP server.
|
|
7
7
|
class Riffer::Mcp::Manifest
|
|
8
8
|
# Identifier used as the registration key and generated-agent identifier.
|
|
9
|
-
attr_reader :name #: String
|
|
9
|
+
attr_reader :name #: String # @dynamic name
|
|
10
10
|
|
|
11
11
|
# Tags for matching +use_mcp+.
|
|
12
|
-
attr_reader :tags #: Array[Symbol]
|
|
12
|
+
attr_reader :tags #: Array[Symbol] # @dynamic tags
|
|
13
13
|
|
|
14
14
|
# HTTPS URL passed to the MCP transport.
|
|
15
|
-
attr_reader :endpoint #: String
|
|
15
|
+
attr_reader :endpoint #: String # @dynamic endpoint
|
|
16
16
|
|
|
17
17
|
# Headers (or a Proc) resolved once when building the discovery client.
|
|
18
|
-
attr_reader :discovery_headers #: (Hash[String, untyped] | ::Proc)?
|
|
18
|
+
attr_reader :discovery_headers #: (Hash[String, untyped] | ::Proc)? # @dynamic discovery_headers
|
|
19
19
|
|
|
20
20
|
# Optional hint (+:global+/+:tenant+/+:user+) for whether invocation
|
|
21
21
|
# credentials depend on tenant/user keys in +context+.
|
|
22
|
-
attr_reader :credentials_scope #: Symbol?
|
|
22
|
+
attr_reader :credentials_scope #: Symbol? # @dynamic credentials_scope
|
|
23
23
|
|
|
24
24
|
# Raises Riffer::ArgumentError unless +name+ is present and +endpoint+ is a
|
|
25
25
|
# valid HTTPS URL.
|
|
@@ -9,7 +9,7 @@ class Riffer::Mcp::Registration
|
|
|
9
9
|
# @rbs @mutex: Thread::Mutex
|
|
10
10
|
|
|
11
11
|
# The manifest that describes this server.
|
|
12
|
-
attr_reader :manifest #: Riffer::Mcp::Manifest
|
|
12
|
+
attr_reader :manifest #: Riffer::Mcp::Manifest # @dynamic manifest
|
|
13
13
|
|
|
14
14
|
# Generated Riffer::Mcp::Tool subclasses.
|
|
15
15
|
#
|
|
@@ -8,7 +8,7 @@ class Riffer::Mcp::SearchTool < Riffer::Tool
|
|
|
8
8
|
# Successful search response carrying the matched tool classes.
|
|
9
9
|
class Result < Riffer::Tools::Response
|
|
10
10
|
# Tool classes that matched the search query.
|
|
11
|
-
attr_reader :discovered_tools #: Array[singleton(Riffer::Tool)]
|
|
11
|
+
attr_reader :discovered_tools #: Array[singleton(Riffer::Tool)] # @dynamic discovered_tools
|
|
12
12
|
|
|
13
13
|
#--
|
|
14
14
|
#: (String, Array[singleton(Riffer::Tool)]) -> void
|
|
@@ -7,21 +7,21 @@ class Riffer::Messages::Assistant < Riffer::Messages::Base
|
|
|
7
7
|
ToolCall = Struct.new(:call_id, :name, :arguments)
|
|
8
8
|
|
|
9
9
|
# Array of tool calls requested by the assistant.
|
|
10
|
-
attr_reader :tool_calls #: Array[Riffer::Messages::Assistant::ToolCall]
|
|
10
|
+
attr_reader :tool_calls #: Array[Riffer::Messages::Assistant::ToolCall] # @dynamic tool_calls
|
|
11
11
|
|
|
12
12
|
# Token usage data for this response.
|
|
13
|
-
attr_reader :token_usage #: Riffer::Providers::TokenUsage?
|
|
13
|
+
attr_reader :token_usage #: Riffer::Providers::TokenUsage? # @dynamic token_usage
|
|
14
14
|
|
|
15
15
|
# Parsed structured output hash, or nil when not applicable.
|
|
16
|
-
attr_reader :structured_output #: Hash[Symbol, untyped]?
|
|
16
|
+
attr_reader :structured_output #: Hash[Symbol, untyped]? # @dynamic structured_output
|
|
17
17
|
|
|
18
18
|
# Normalized reason the provider finished this response, when reported (see
|
|
19
19
|
# <tt>Riffer::Providers::FinishReason::VALUES</tt>).
|
|
20
|
-
attr_reader :finish_reason #: Symbol?
|
|
20
|
+
attr_reader :finish_reason #: Symbol? # @dynamic finish_reason
|
|
21
21
|
|
|
22
22
|
# The provider's raw finish-reason value behind +finish_reason+, when one
|
|
23
23
|
# exists on the wire.
|
|
24
|
-
attr_reader :finish_reason_raw #: String?
|
|
24
|
+
attr_reader :finish_reason_raw #: String? # @dynamic finish_reason_raw
|
|
25
25
|
|
|
26
26
|
# Raises Riffer::ArgumentError when +finish_reason+ is outside the
|
|
27
27
|
# normalized vocabulary.
|
data/lib/riffer/messages/base.rb
CHANGED
|
@@ -12,8 +12,6 @@ class Riffer::Messages::Base
|
|
|
12
12
|
def self.from_hash(msg)
|
|
13
13
|
return msg if msg.is_a?(Riffer::Messages::Base)
|
|
14
14
|
|
|
15
|
-
raise Riffer::ArgumentError, "Message must be a Hash or Message object, got #{msg.class}" unless msg.is_a?(Hash)
|
|
16
|
-
|
|
17
15
|
raise Riffer::ArgumentError, "Message hash must include a 'role' key" if msg[:role].nil? || msg[:role].empty?
|
|
18
16
|
|
|
19
17
|
case msg[:role].to_sym
|
|
@@ -42,10 +40,10 @@ class Riffer::Messages::Base
|
|
|
42
40
|
end
|
|
43
41
|
|
|
44
42
|
# The message content.
|
|
45
|
-
attr_reader :content #: String
|
|
43
|
+
attr_reader :content #: String # @dynamic content
|
|
46
44
|
|
|
47
45
|
# The message id, or nil when +Riffer.config.message_id_strategy+ is +:none+.
|
|
48
|
-
attr_reader :id #: String?
|
|
46
|
+
attr_reader :id #: String? # @dynamic id
|
|
49
47
|
|
|
50
48
|
#--
|
|
51
49
|
#: (String, ?id: String?) -> void
|
|
@@ -29,13 +29,13 @@ class Riffer::Messages::FilePart
|
|
|
29
29
|
SHA256_PATTERN = /\A[0-9a-f]{64}\z/i #: Regexp
|
|
30
30
|
|
|
31
31
|
# The MIME type of the file.
|
|
32
|
-
attr_reader :media_type #: String
|
|
32
|
+
attr_reader :media_type #: String # @dynamic media_type
|
|
33
33
|
|
|
34
34
|
# The filename, if available.
|
|
35
|
-
attr_reader :filename #: String?
|
|
35
|
+
attr_reader :filename #: String? # @dynamic filename
|
|
36
36
|
|
|
37
37
|
# The expected SHA-256 of the file contents, if the caller supplied one.
|
|
38
|
-
attr_reader :sha256 #: String?
|
|
38
|
+
attr_reader :sha256 #: String? # @dynamic sha256
|
|
39
39
|
|
|
40
40
|
# Raises Riffer::ArgumentError unless +data+ or +url+ is given and
|
|
41
41
|
# +media_type+ is supported.
|
|
@@ -66,8 +66,7 @@ class Riffer::Messages::FilePart
|
|
|
66
66
|
def self.from_url(url, media_type: nil, filename: nil, sha256: nil)
|
|
67
67
|
unless media_type
|
|
68
68
|
ext = ::File.extname(URI.parse(url).path.to_s).downcase
|
|
69
|
-
media_type = MEDIA_TYPES
|
|
70
|
-
raise Riffer::ArgumentError, "Cannot detect media type from URL; provide media_type explicitly" unless media_type
|
|
69
|
+
media_type = MEDIA_TYPES.fetch(ext) { raise Riffer::ArgumentError, "Cannot detect media type from URL; provide media_type explicitly" }
|
|
71
70
|
end
|
|
72
71
|
|
|
73
72
|
new(url: url, media_type: media_type, filename: filename, sha256: sha256)
|
|
@@ -81,8 +80,6 @@ class Riffer::Messages::FilePart
|
|
|
81
80
|
def self.from_hash(file)
|
|
82
81
|
return file if file.is_a?(Riffer::Messages::FilePart)
|
|
83
82
|
|
|
84
|
-
raise Riffer::ArgumentError, "File must be a Hash or FilePart object, got #{file.class}" unless file.is_a?(Hash)
|
|
85
|
-
|
|
86
83
|
url = file[:url]
|
|
87
84
|
data = file[:data]
|
|
88
85
|
media_type = file[:media_type]
|
data/lib/riffer/messages/tool.rb
CHANGED
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
# Represents a tool execution result in a conversation.
|
|
5
5
|
class Riffer::Messages::Tool < Riffer::Messages::Base
|
|
6
6
|
# The ID of the tool call this result responds to.
|
|
7
|
-
attr_reader :tool_call_id #: String
|
|
7
|
+
attr_reader :tool_call_id #: String # @dynamic tool_call_id
|
|
8
8
|
|
|
9
9
|
# The name of the tool that was called.
|
|
10
|
-
attr_reader :name #: String
|
|
10
|
+
attr_reader :name #: String # @dynamic name
|
|
11
11
|
|
|
12
12
|
# The error message if the tool execution failed.
|
|
13
|
-
attr_reader :error #: String?
|
|
13
|
+
attr_reader :error #: String? # @dynamic error
|
|
14
14
|
|
|
15
15
|
# The type of error (:unknown_tool, :validation_error, :execution_error,
|
|
16
16
|
# :timeout_error, :unhandled_error).
|
|
17
|
-
attr_reader :error_type #: Symbol?
|
|
17
|
+
attr_reader :error_type #: Symbol? # @dynamic error_type
|
|
18
18
|
|
|
19
19
|
#--
|
|
20
20
|
#: (String, tool_call_id: String, name: String, ?id: String?, ?error: String?, ?error_type: Symbol?) -> void
|
data/lib/riffer/messages/user.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Represents a user message in a conversation.
|
|
5
5
|
class Riffer::Messages::User < Riffer::Messages::Base
|
|
6
6
|
# File attachments for this message.
|
|
7
|
-
attr_reader :files #: Array[Riffer::Messages::FilePart]
|
|
7
|
+
attr_reader :files #: Array[Riffer::Messages::FilePart] # @dynamic files
|
|
8
8
|
|
|
9
9
|
#--
|
|
10
10
|
#: (String, ?id: String?, ?files: Array[Riffer::Messages::FilePart]) -> void
|
data/lib/riffer/params/param.rb
CHANGED
|
@@ -31,28 +31,28 @@ class Riffer::Params::Param
|
|
|
31
31
|
}.freeze #: Hash[String, Module]
|
|
32
32
|
|
|
33
33
|
# The parameter name.
|
|
34
|
-
attr_reader :name #: Symbol
|
|
34
|
+
attr_reader :name #: Symbol # @dynamic name
|
|
35
35
|
|
|
36
36
|
# The Ruby type.
|
|
37
|
-
attr_reader :type #: Module
|
|
37
|
+
attr_reader :type #: Module # @dynamic type
|
|
38
38
|
|
|
39
39
|
# Whether the parameter is required.
|
|
40
|
-
attr_reader :required #: bool
|
|
40
|
+
attr_reader :required #: bool # @dynamic required
|
|
41
41
|
|
|
42
42
|
# The parameter description, if any.
|
|
43
|
-
attr_reader :description #: String?
|
|
43
|
+
attr_reader :description #: String? # @dynamic description
|
|
44
44
|
|
|
45
45
|
# Allowed values, if constrained.
|
|
46
|
-
attr_reader :enum #: Array[untyped]?
|
|
46
|
+
attr_reader :enum #: Array[untyped]? # @dynamic enum
|
|
47
47
|
|
|
48
48
|
# The default value, if any.
|
|
49
|
-
attr_reader :default #: untyped
|
|
49
|
+
attr_reader :default #: untyped # @dynamic default
|
|
50
50
|
|
|
51
51
|
# Element type for a typed array (+of:+).
|
|
52
|
-
attr_reader :item_type #: Module?
|
|
52
|
+
attr_reader :item_type #: Module? # @dynamic item_type
|
|
53
53
|
|
|
54
54
|
# Nested Params for object / array-of-object types.
|
|
55
|
-
attr_reader :nested_params #: Riffer::Params?
|
|
55
|
+
attr_reader :nested_params #: Riffer::Params? # @dynamic nested_params
|
|
56
56
|
|
|
57
57
|
# Reconstructs a Param from a single JSON Schema property. Raises
|
|
58
58
|
# Riffer::ArgumentError on a type outside the Params-expressible subset.
|
|
@@ -140,10 +140,14 @@ class Riffer::Params::Param
|
|
|
140
140
|
# params are made nullable (<tt>["type", "null"]</tt>) so strict providers
|
|
141
141
|
# distinguish absent from present; optional params with an +enum+ use +anyOf+
|
|
142
142
|
# instead, since providers like Anthropic reject
|
|
143
|
-
# <tt>{"type": ["string", "null"], "enum": [...]}</tt>.
|
|
143
|
+
# <tt>{"type": ["string", "null"], "enum": [...]}</tt>. Raises
|
|
144
|
+
# Riffer::ArgumentError when +strict+ and a Hash param has no block or an
|
|
145
|
+
# Array param has neither a block nor <tt>of:</tt>, since strict providers
|
|
146
|
+
# reject objects without +properties+ and arrays without +items+.
|
|
144
147
|
#--
|
|
145
148
|
#: (?strict: bool) -> Hash[Symbol, untyped]
|
|
146
149
|
def to_json_schema(strict: false)
|
|
150
|
+
validate_strict_shape! if strict
|
|
147
151
|
nullable = strict && !required
|
|
148
152
|
|
|
149
153
|
if nullable && enum
|
|
@@ -168,9 +172,24 @@ class Riffer::Params::Param
|
|
|
168
172
|
elsif self.type == Array && item_type
|
|
169
173
|
schema[:items] = { type: TYPE_MAPPINGS[item_type] }
|
|
170
174
|
elsif self.type == Hash && nested_params
|
|
171
|
-
schema
|
|
175
|
+
# The nested schema carries its own type: "object", which would clobber a nullable union.
|
|
176
|
+
schema.merge!(nested_params.to_json_schema(strict: strict).except(:type))
|
|
172
177
|
end
|
|
173
178
|
|
|
174
179
|
schema
|
|
175
180
|
end
|
|
181
|
+
|
|
182
|
+
private
|
|
183
|
+
|
|
184
|
+
#--
|
|
185
|
+
#: () -> void
|
|
186
|
+
def validate_strict_shape!
|
|
187
|
+
if type == Hash && nested_params.nil?
|
|
188
|
+
raise Riffer::ArgumentError,
|
|
189
|
+
"#{name}: a Hash param requires a block defining its properties under strict schemas"
|
|
190
|
+
elsif type == Array && nested_params.nil? && item_type.nil?
|
|
191
|
+
raise Riffer::ArgumentError,
|
|
192
|
+
"#{name}: an Array param requires a block or of: defining its items under strict schemas"
|
|
193
|
+
end
|
|
194
|
+
end
|
|
176
195
|
end
|
data/lib/riffer/params.rb
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
#
|
|
12
12
|
class Riffer::Params
|
|
13
13
|
# The defined parameters.
|
|
14
|
-
attr_reader :parameters #: Array[Riffer::Params::Param]
|
|
14
|
+
attr_reader :parameters #: Array[Riffer::Params::Param] # @dynamic parameters
|
|
15
15
|
|
|
16
16
|
#--
|
|
17
17
|
#: () -> void
|
|
@@ -128,6 +128,8 @@ class Riffer::Params
|
|
|
128
128
|
# Converts all parameters to JSON Schema format. When +strict+ is true, every
|
|
129
129
|
# property is listed in +required+ and optional ones are made nullable
|
|
130
130
|
# instead, satisfying providers that enforce strict structured output schemas.
|
|
131
|
+
# Raises Riffer::ArgumentError when +strict+ and a Hash param (at any depth)
|
|
132
|
+
# has no block or an Array param has neither a block nor <tt>of:</tt>.
|
|
131
133
|
#--
|
|
132
134
|
#: (?strict: bool) -> Hash[Symbol, untyped]
|
|
133
135
|
def to_json_schema(strict: false)
|
|
@@ -154,7 +154,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
154
154
|
#: (untyped) -> Hash[Symbol, untyped]
|
|
155
155
|
def build_cache_point(cache_control)
|
|
156
156
|
point = { type: "default" } #: Hash[Symbol, untyped]
|
|
157
|
-
ttl = cache_control.is_a?(Hash)
|
|
157
|
+
ttl = cache_control[:ttl] if cache_control.is_a?(Hash)
|
|
158
158
|
point[:ttl] = ttl if ttl
|
|
159
159
|
point
|
|
160
160
|
end
|
|
@@ -252,6 +252,8 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
252
252
|
tool_call: nil,
|
|
253
253
|
} #: Hash[Symbol, untyped]
|
|
254
254
|
|
|
255
|
+
stream_completed = false
|
|
256
|
+
|
|
255
257
|
client.converse_stream(**params) do |stream|
|
|
256
258
|
stream.on_event do |event|
|
|
257
259
|
case event
|
|
@@ -264,20 +266,45 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
|
|
|
264
266
|
handle_content_block_stop_text_delta(event, state: current_state, yielder: yielder) if current_state[:text]
|
|
265
267
|
handle_content_block_stop_tool_use(event, state: current_state, yielder: yielder) if current_state[:tool_call]
|
|
266
268
|
when Aws::BedrockRuntime::Types::MessageStopEvent
|
|
269
|
+
stream_completed = true
|
|
267
270
|
yield_finish_reason(yielder, build_finish_reason(event.stop_reason))
|
|
268
271
|
when Aws::BedrockRuntime::Types::ConverseStreamMetadataEvent
|
|
269
272
|
handle_metadata_usage(event, state: current_state, yielder: yielder) if event.usage
|
|
273
|
+
when Aws::Errors::EventError
|
|
274
|
+
# The SDK turns an event-stream +:message-type: error+ frame into an
|
|
275
|
+
# EventError instance and hands it to this block as an event; it is
|
|
276
|
+
# never raised. Re-raise it here so the failure surfaces instead of
|
|
277
|
+
# truncating the stream.
|
|
278
|
+
raise_stream_event_error!(event)
|
|
270
279
|
else
|
|
271
280
|
raise_if_stream_exception!(event)
|
|
272
281
|
end
|
|
273
282
|
end
|
|
274
283
|
end
|
|
284
|
+
|
|
285
|
+
return if stream_completed
|
|
286
|
+
|
|
287
|
+
raise Riffer::IncompleteStreamError, "Bedrock ConverseStream ended without a messageStop event"
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Re-raises an +Aws::Errors::EventError+ event with a message built from its
|
|
291
|
+
# error code and message. The SDK's own +#message+ is just the class name,
|
|
292
|
+
# so without this the failure would be unreadable.
|
|
293
|
+
#--
|
|
294
|
+
#: (untyped) -> void
|
|
295
|
+
def raise_stream_event_error!(event)
|
|
296
|
+
details = [event.error_code, event.error_message].compact.join(": ")
|
|
297
|
+
details = "Bedrock ConverseStream error event" if details.empty?
|
|
298
|
+
|
|
299
|
+
raise event.exception(details)
|
|
275
300
|
end
|
|
276
301
|
|
|
277
302
|
# Re-raises a Bedrock stream-exception event as the matching
|
|
278
303
|
# +Aws::BedrockRuntime::Errors+ class. ConverseStream delivers API errors on
|
|
279
304
|
# the same channel as content, so without this a mid-stream failure would
|
|
280
|
-
# silently end the stream with no content.
|
|
305
|
+
# silently end the stream with no content. Non-exception events that we do
|
|
306
|
+
# not consume (including the SDK's +:unknown_event+ struct) are ignored for
|
|
307
|
+
# forward compatibility.
|
|
281
308
|
#--
|
|
282
309
|
#: (untyped) -> void
|
|
283
310
|
def raise_if_stream_exception!(event)
|
|
@@ -211,6 +211,8 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
211
211
|
web_search_query: nil,
|
|
212
212
|
} #: Hash[Symbol, untyped]
|
|
213
213
|
|
|
214
|
+
stream_completed = false
|
|
215
|
+
|
|
214
216
|
# Workaround for anthropics/anthropic-sdk-ruby#182: force identity
|
|
215
217
|
# encoding so Net::HTTP/Zlib doesn't buffer SSE chunks until EOF.
|
|
216
218
|
stream = client.messages.stream(
|
|
@@ -247,6 +249,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
247
249
|
handle_content_block_stop_web_search_result(event, state: current_state, yielder: yielder)
|
|
248
250
|
end
|
|
249
251
|
when ::Anthropic::Helpers::Streaming::MessageStopEvent
|
|
252
|
+
stream_completed = true
|
|
250
253
|
handle_message_stop(event, accumulated_message: stream.accumulated_message, yielder: yielder)
|
|
251
254
|
end
|
|
252
255
|
end
|
|
@@ -256,6 +259,10 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
|
|
|
256
259
|
# socket leaks until GC. close is idempotent and a no-op after EOF.
|
|
257
260
|
stream.close
|
|
258
261
|
end
|
|
262
|
+
|
|
263
|
+
return if stream_completed
|
|
264
|
+
|
|
265
|
+
raise Riffer::IncompleteStreamError, "Anthropic stream ended without a message_stop event"
|
|
259
266
|
end
|
|
260
267
|
|
|
261
268
|
#--
|
|
@@ -30,10 +30,9 @@ class Riffer::Providers::Base
|
|
|
30
30
|
#--
|
|
31
31
|
#: () -> String
|
|
32
32
|
def self.semconv_provider_name
|
|
33
|
-
class_name = name
|
|
34
33
|
# Anonymous classes stay uncached: a class assigned to a constant
|
|
35
34
|
# later must pick up its real name, not a frozen "unknown".
|
|
36
|
-
return "unknown"
|
|
35
|
+
class_name = name or return "unknown"
|
|
37
36
|
|
|
38
37
|
@semconv_provider_name ||= Riffer::Helpers::Identifier.derive(class_name.split("::").last)
|
|
39
38
|
end
|
|
@@ -8,10 +8,10 @@ class Riffer::Providers::FinishReason
|
|
|
8
8
|
VALUES = %i[stop length tool_calls content_filter context_window malformed_output error other].freeze #: Array[Symbol]
|
|
9
9
|
|
|
10
10
|
# The normalized reason.
|
|
11
|
-
attr_reader :reason #: Symbol
|
|
11
|
+
attr_reader :reason #: Symbol # @dynamic reason
|
|
12
12
|
|
|
13
13
|
# The provider's raw finish-reason value, when one exists on the wire.
|
|
14
|
-
attr_reader :raw #: String?
|
|
14
|
+
attr_reader :raw #: String? # @dynamic raw
|
|
15
15
|
|
|
16
16
|
# Raises Riffer::ArgumentError when +reason+ is outside VALUES.
|
|
17
17
|
#--
|
|
@@ -25,7 +25,7 @@ class Riffer::Providers::Mock < Riffer::Providers::Base
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# Array of recorded method calls for assertions.
|
|
28
|
-
attr_reader :calls #: Array[Hash[Symbol, untyped]]
|
|
28
|
+
attr_reader :calls #: Array[Hash[Symbol, untyped]] # @dynamic calls
|
|
29
29
|
|
|
30
30
|
# +responses:+ pre-configures canned responses (same shape as
|
|
31
31
|
# +#stub_response+) for standalone use; agent tests queue responses on
|
|
@@ -218,6 +218,8 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
218
218
|
tool_info: {},
|
|
219
219
|
} #: Hash[Symbol, untyped]
|
|
220
220
|
|
|
221
|
+
stream_completed = false
|
|
222
|
+
|
|
221
223
|
stream = client.responses.stream(params)
|
|
222
224
|
begin
|
|
223
225
|
stream.each do |event|
|
|
@@ -247,6 +249,7 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
247
249
|
when :"response.output_item.done"
|
|
248
250
|
handle_output_item_done_web_search(event, yielder: yielder) if event.item&.type == :web_search_call
|
|
249
251
|
when :"response.completed", :"response.incomplete", :"response.failed"
|
|
252
|
+
stream_completed = true
|
|
250
253
|
handle_response_finished(event, state: current_state, yielder: yielder)
|
|
251
254
|
end
|
|
252
255
|
end
|
|
@@ -256,6 +259,11 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
|
|
|
256
259
|
# socket leaks until GC. close is idempotent and a no-op after EOF.
|
|
257
260
|
stream.close
|
|
258
261
|
end
|
|
262
|
+
|
|
263
|
+
return if stream_completed
|
|
264
|
+
|
|
265
|
+
raise Riffer::IncompleteStreamError,
|
|
266
|
+
"OpenAI Responses stream ended without a response.completed, response.incomplete, or response.failed event"
|
|
259
267
|
end
|
|
260
268
|
|
|
261
269
|
#--
|
|
@@ -27,13 +27,9 @@ module Riffer::Providers::Repository
|
|
|
27
27
|
#
|
|
28
28
|
# Riffer::Providers::Repository.register(:jane) { MyApp::JaneProvider }
|
|
29
29
|
#
|
|
30
|
-
# Raises Riffer::ArgumentError when called without a block.
|
|
31
|
-
#
|
|
32
30
|
#--
|
|
33
31
|
#: ((String | Symbol)) { () -> singleton(Riffer::Providers::Base) } -> void
|
|
34
32
|
def register(identifier, &factory)
|
|
35
|
-
raise Riffer::ArgumentError, "register requires a block returning a provider class" unless factory
|
|
36
|
-
|
|
37
33
|
@registrations[identifier.to_sym] = factory
|
|
38
34
|
@key_for = nil
|
|
39
35
|
end
|
|
@@ -5,19 +5,19 @@
|
|
|
5
5
|
# meaning for every provider.
|
|
6
6
|
class Riffer::Providers::TokenUsage
|
|
7
7
|
# Number of tokens entering the context window, including cache reads and writes.
|
|
8
|
-
attr_reader :input_tokens #: Integer
|
|
8
|
+
attr_reader :input_tokens #: Integer # @dynamic input_tokens
|
|
9
9
|
|
|
10
10
|
# Number of tokens generated by the model, including reasoning/thinking tokens.
|
|
11
|
-
attr_reader :output_tokens #: Integer
|
|
11
|
+
attr_reader :output_tokens #: Integer # @dynamic output_tokens
|
|
12
12
|
|
|
13
13
|
# Subset of +input_tokens+ written to the provider's prompt cache, when the provider reports it.
|
|
14
|
-
attr_reader :cache_write_tokens #: Integer?
|
|
14
|
+
attr_reader :cache_write_tokens #: Integer? # @dynamic cache_write_tokens
|
|
15
15
|
|
|
16
16
|
# Subset of +input_tokens+ read from the provider's prompt cache, when the provider reports it.
|
|
17
|
-
attr_reader :cache_read_tokens #: Integer?
|
|
17
|
+
attr_reader :cache_read_tokens #: Integer? # @dynamic cache_read_tokens
|
|
18
18
|
|
|
19
19
|
# Cost of the call, set when the model is priced. For observability, not billing.
|
|
20
|
-
attr_reader :cost #: Float?
|
|
20
|
+
attr_reader :cost #: Float? # @dynamic cost
|
|
21
21
|
|
|
22
22
|
#--
|
|
23
23
|
#: (input_tokens: Integer, output_tokens: Integer, ?cache_write_tokens: Integer?, ?cache_read_tokens: Integer?, ?cost: Float?) -> void
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# for the rendered output.
|
|
8
8
|
class Riffer::Skills::Adapter
|
|
9
9
|
# The activation tool class for this adapter.
|
|
10
|
-
attr_reader :skill_activate_tool #: singleton(Riffer::Tool)
|
|
10
|
+
attr_reader :skill_activate_tool #: singleton(Riffer::Tool) # @dynamic skill_activate_tool
|
|
11
11
|
|
|
12
12
|
#--
|
|
13
13
|
#: (skill_activate_tool: singleton(Riffer::Tool)) -> void
|
|
@@ -11,13 +11,13 @@ class Riffer::Skills::Context
|
|
|
11
11
|
# @rbs @preactivated: Array[String]
|
|
12
12
|
|
|
13
13
|
# Skill catalog indexed by name.
|
|
14
|
-
attr_reader :skills #: Hash[String, Riffer::Skills::Frontmatter]
|
|
14
|
+
attr_reader :skills #: Hash[String, Riffer::Skills::Frontmatter] # @dynamic skills
|
|
15
15
|
|
|
16
16
|
# The skill adapter used for this context.
|
|
17
|
-
attr_reader :adapter #: Riffer::Skills::Adapter
|
|
17
|
+
attr_reader :adapter #: Riffer::Skills::Adapter # @dynamic adapter
|
|
18
18
|
|
|
19
19
|
# Optional callback invoked when a skill is first activated.
|
|
20
|
-
attr_accessor :on_activate #: (^(String) -> void)?
|
|
20
|
+
attr_accessor :on_activate #: (^(String) -> void)? # @dynamic on_activate, on_activate=
|
|
21
21
|
|
|
22
22
|
#--
|
|
23
23
|
#: (backend: Riffer::Skills::Backend, skills: Hash[String, Riffer::Skills::Frontmatter], adapter: Riffer::Skills::Adapter) -> void
|
|
@@ -106,10 +106,7 @@ class Riffer::Skills::Context
|
|
|
106
106
|
#--
|
|
107
107
|
#: (String) -> bool
|
|
108
108
|
def model_invocable?(name)
|
|
109
|
-
|
|
110
|
-
return false unless skill
|
|
111
|
-
|
|
112
|
-
!skill.disable_model_invocation
|
|
109
|
+
skills.key?(name) && !skills.fetch(name).disable_model_invocation
|
|
113
110
|
end
|
|
114
111
|
|
|
115
112
|
# Returns whether any skill is available for the model to activate.
|
|
@@ -53,8 +53,7 @@ class Riffer::Skills::FilesystemBackend < Riffer::Skills::Backend
|
|
|
53
53
|
def read_skill(name)
|
|
54
54
|
list_skills unless @skills_cache
|
|
55
55
|
cache = @skills_cache #: Hash[String, String]
|
|
56
|
-
dir = cache
|
|
57
|
-
raise Riffer::ArgumentError, "Skill not found: '#{name}'" unless dir
|
|
56
|
+
dir = cache.fetch(name) { raise Riffer::ArgumentError, "Skill not found: '#{name}'" }
|
|
58
57
|
|
|
59
58
|
_, body = Riffer::Skills::Frontmatter.parse(File.read(File.join(dir, SKILL_FILENAME)))
|
|
60
59
|
body
|
|
@@ -13,19 +13,19 @@ class Riffer::Skills::Frontmatter
|
|
|
13
13
|
MAX_DESCRIPTION_LENGTH = 1024 #: Integer
|
|
14
14
|
|
|
15
15
|
# The skill name (1-64 chars, lowercase alphanumeric and hyphens).
|
|
16
|
-
attr_reader :name #: String
|
|
16
|
+
attr_reader :name #: String # @dynamic name
|
|
17
17
|
|
|
18
18
|
# The skill description (1-1024 chars).
|
|
19
|
-
attr_reader :description #: String
|
|
19
|
+
attr_reader :description #: String # @dynamic description
|
|
20
20
|
|
|
21
21
|
# Whether the skill opts out of model-driven activation. Hidden from the
|
|
22
22
|
# catalog and rejected at model activation; still reachable via programmatic
|
|
23
23
|
# activation.
|
|
24
|
-
attr_reader :disable_model_invocation #: bool
|
|
24
|
+
attr_reader :disable_model_invocation #: bool # @dynamic disable_model_invocation
|
|
25
25
|
|
|
26
26
|
# Metadata from the spec's +metadata+ field plus any unrecognized top-level
|
|
27
27
|
# keys.
|
|
28
|
-
attr_reader :metadata #: Hash[Symbol, untyped]
|
|
28
|
+
attr_reader :metadata #: Hash[Symbol, untyped] # @dynamic metadata
|
|
29
29
|
|
|
30
30
|
# Parses a raw SKILL.md string into a +[Frontmatter, body]+ pair — public so
|
|
31
31
|
# custom backends needn't reimplement parsing. Raises Riffer::ArgumentError
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Base class for all streaming events. Subclasses must implement +to_h+.
|
|
5
5
|
class Riffer::StreamEvents::Base
|
|
6
6
|
# The message role (typically :assistant).
|
|
7
|
-
attr_reader :role #: Symbol
|
|
7
|
+
attr_reader :role #: Symbol # @dynamic role
|
|
8
8
|
|
|
9
9
|
#--
|
|
10
10
|
#: (?role: Symbol) -> void
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
# stream; no ordering guarantee relative to TokenUsageDone.
|
|
6
6
|
class Riffer::StreamEvents::FinishReasonDone < Riffer::StreamEvents::Base
|
|
7
7
|
# The normalized finish reason (see <tt>Riffer::Providers::FinishReason::VALUES</tt>).
|
|
8
|
-
attr_reader :finish_reason #: Symbol
|
|
8
|
+
attr_reader :finish_reason #: Symbol # @dynamic finish_reason
|
|
9
9
|
|
|
10
10
|
# The provider's raw finish-reason value, when one exists on the wire.
|
|
11
|
-
attr_reader :raw_finish_reason #: String?
|
|
11
|
+
attr_reader :raw_finish_reason #: String? # @dynamic raw_finish_reason
|
|
12
12
|
|
|
13
13
|
# Raises Riffer::ArgumentError when +finish_reason+ is outside the
|
|
14
14
|
# normalized vocabulary.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Emitted when a guardrail transforms data during streaming.
|
|
5
5
|
class Riffer::StreamEvents::GuardrailModification < Riffer::StreamEvents::Base
|
|
6
6
|
# The modification record.
|
|
7
|
-
attr_reader :modification #: Riffer::Guardrails::Modification
|
|
7
|
+
attr_reader :modification #: Riffer::Guardrails::Modification # @dynamic modification
|
|
8
8
|
|
|
9
9
|
#--
|
|
10
10
|
#: (Riffer::Guardrails::Modification, ?role: Symbol) -> void
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Emitted when a guardrail blocks execution during streaming.
|
|
5
5
|
class Riffer::StreamEvents::GuardrailTripwire < Riffer::StreamEvents::Base
|
|
6
6
|
# The tripwire containing block details.
|
|
7
|
-
attr_reader :tripwire #: Riffer::Guardrails::Tripwire
|
|
7
|
+
attr_reader :tripwire #: Riffer::Guardrails::Tripwire # @dynamic tripwire
|
|
8
8
|
|
|
9
9
|
#--
|
|
10
10
|
#: (Riffer::Guardrails::Tripwire, ?role: Symbol) -> void
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
# +:riffer_interrupt+.
|
|
6
6
|
class Riffer::StreamEvents::Interrupt < Riffer::StreamEvents::Base
|
|
7
7
|
# The reason provided with the interrupt, if any.
|
|
8
|
-
attr_reader :reason #: (String | Symbol)?
|
|
8
|
+
attr_reader :reason #: (String | Symbol)? # @dynamic reason
|
|
9
9
|
|
|
10
10
|
# Call ids of tool_use blocks riffer filled with placeholder results when the
|
|
11
11
|
# interrupt fired (only when history healing is on).
|
|
12
|
-
attr_reader :healed_tool_call_ids #: Array[String]
|
|
12
|
+
attr_reader :healed_tool_call_ids #: Array[String] # @dynamic healed_tool_call_ids
|
|
13
13
|
|
|
14
14
|
#--
|
|
15
15
|
#: (?reason: (String | Symbol)?, ?healed_tool_call_ids: Array[String]) -> void
|