riffer 0.48.0 → 0.49.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/.claude/rules/comments.md +2 -4
- data/.claude/rules/rbs-inline.md +2 -12
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +19 -0
- data/docs/AGENTS.md +3 -5
- data/docs/AGENT_LIFECYCLE.md +13 -16
- data/docs/CONFIGURATION.md +22 -33
- data/docs/TOOL_ADVANCED.md +1 -3
- data/docs/TRACING.md +1 -1
- data/docs/providers/AMAZON_BEDROCK.md +31 -0
- data/docs/providers/OPENROUTER.md +18 -1
- data/docs-site/build.rb +0 -6
- data/docs-site/check.rb +1 -5
- data/lib/riffer/agent/config.rb +4 -45
- data/lib/riffer/agent/context.rb +2 -26
- data/lib/riffer/agent/outcome.rb +0 -20
- data/lib/riffer/agent/response.rb +0 -38
- data/lib/riffer/agent/run.rb +11 -43
- data/lib/riffer/agent/serializer.rb +10 -39
- data/lib/riffer/agent/session/repair.rb +3 -15
- data/lib/riffer/agent/session.rb +25 -38
- data/lib/riffer/agent/structured_output/result.rb +0 -8
- data/lib/riffer/agent/structured_output.rb +0 -7
- data/lib/riffer/agent.rb +10 -130
- data/lib/riffer/config/amazon_bedrock.rb +30 -0
- data/lib/riffer/config/anthropic.rb +21 -0
- data/lib/riffer/config/azure_open_ai.rb +30 -0
- data/lib/riffer/config/evals.rb +18 -0
- data/lib/riffer/config/files.rb +61 -0
- data/lib/riffer/config/gemini.rb +21 -0
- data/lib/riffer/config/mcp.rb +31 -0
- data/lib/riffer/config/open_ai.rb +30 -0
- data/lib/riffer/config/open_router.rb +21 -0
- data/lib/riffer/config/pricing/rates.rb +36 -0
- data/lib/riffer/config/pricing.rb +64 -0
- data/lib/riffer/config/skills.rb +38 -0
- data/lib/riffer/config/tracing.rb +43 -0
- data/lib/riffer/config.rb +3 -362
- data/lib/riffer/evals/evaluator.rb +1 -29
- data/lib/riffer/evals/evaluator_runner.rb +0 -15
- data/lib/riffer/evals/judge.rb +0 -8
- data/lib/riffer/evals/result.rb +1 -11
- data/lib/riffer/evals/run_result.rb +0 -12
- data/lib/riffer/evals/scenario_result.rb +0 -19
- data/lib/riffer/files/downloader.rb +2 -3
- data/lib/riffer/files/resolver.rb +2 -7
- data/lib/riffer/guardrail.rb +2 -22
- data/lib/riffer/guardrails/modification.rb +0 -8
- data/lib/riffer/guardrails/result.rb +0 -17
- data/lib/riffer/guardrails/runner.rb +2 -11
- data/lib/riffer/guardrails/tripwire.rb +0 -8
- data/lib/riffer/guardrails.rb +0 -2
- data/lib/riffer/helpers/boolean.rb +0 -4
- data/lib/riffer/helpers/call_or_value.rb +0 -3
- data/lib/riffer/helpers/deep_dup.rb +3 -8
- data/lib/riffer/helpers/dependencies.rb +0 -4
- data/lib/riffer/helpers/identifier.rb +3 -12
- data/lib/riffer/helpers/validate.rb +42 -0
- data/lib/riffer/mcp/authenticated_tool.rb +4 -12
- data/lib/riffer/mcp/client.rb +0 -7
- data/lib/riffer/mcp/manifest.rb +3 -7
- data/lib/riffer/mcp/registration.rb +0 -10
- data/lib/riffer/mcp/registry.rb +0 -9
- data/lib/riffer/mcp/search_tool.rb +0 -4
- data/lib/riffer/mcp/tool.rb +1 -4
- data/lib/riffer/mcp/tool_factory.rb +3 -6
- data/lib/riffer/mcp.rb +2 -23
- data/lib/riffer/messages/assistant/reasoning_part.rb +4 -21
- data/lib/riffer/messages/assistant/tool_call.rb +1 -9
- data/lib/riffer/messages/assistant.rb +1 -21
- data/lib/riffer/messages/base.rb +0 -12
- data/lib/riffer/messages/system.rb +0 -3
- data/lib/riffer/messages/tool.rb +0 -15
- data/lib/riffer/messages/user/file_part.rb +2 -30
- data/lib/riffer/messages/user.rb +0 -4
- data/lib/riffer/params/boolean.rb +1 -5
- data/lib/riffer/params/param.rb +3 -31
- data/lib/riffer/params.rb +8 -41
- data/lib/riffer/providers/amazon_bedrock.rb +101 -42
- data/lib/riffer/providers/anthropic.rb +9 -24
- data/lib/riffer/providers/azure_open_ai.rb +3 -8
- data/lib/riffer/providers/base.rb +7 -28
- data/lib/riffer/providers/finish_reason.rb +0 -6
- data/lib/riffer/providers/gemini/client.rb +4 -17
- data/lib/riffer/providers/gemini.rb +4 -10
- data/lib/riffer/providers/mock.rb +1 -26
- data/lib/riffer/providers/open_ai.rb +7 -17
- data/lib/riffer/providers/open_router.rb +105 -30
- data/lib/riffer/providers/repository.rb +2 -14
- data/lib/riffer/providers/token_usage.rb +5 -14
- data/lib/riffer/registrable.rb +11 -45
- data/lib/riffer/runner/fibers.rb +1 -6
- data/lib/riffer/runner/sequential.rb +0 -1
- data/lib/riffer/runner/threaded.rb +0 -3
- data/lib/riffer/runner.rb +0 -3
- data/lib/riffer/skills/activate_tool.rb +0 -3
- data/lib/riffer/skills/adapter.rb +0 -8
- data/lib/riffer/skills/backend.rb +2 -7
- data/lib/riffer/skills/config.rb +4 -20
- data/lib/riffer/skills/context.rb +0 -29
- data/lib/riffer/skills/filesystem_backend.rb +0 -7
- data/lib/riffer/skills/frontmatter.rb +2 -16
- data/lib/riffer/skills/markdown_adapter.rb +3 -6
- data/lib/riffer/skills/xml_adapter.rb +0 -3
- data/lib/riffer/stream_events/base.rb +0 -3
- data/lib/riffer/stream_events/finish_reason_done.rb +1 -6
- data/lib/riffer/stream_events/guardrail_modification.rb +0 -10
- data/lib/riffer/stream_events/guardrail_tripwire.rb +0 -10
- data/lib/riffer/stream_events/interrupt.rb +2 -12
- data/lib/riffer/stream_events/reasoning_delta.rb +0 -3
- data/lib/riffer/stream_events/reasoning_done.rb +1 -5
- data/lib/riffer/stream_events/skill_activation.rb +0 -3
- data/lib/riffer/stream_events/text_delta.rb +0 -2
- data/lib/riffer/stream_events/text_done.rb +0 -2
- data/lib/riffer/stream_events/token_usage_done.rb +0 -2
- data/lib/riffer/stream_events/tool_call_delta.rb +1 -5
- data/lib/riffer/stream_events/tool_call_done.rb +0 -5
- data/lib/riffer/stream_events/web_search_done.rb +0 -3
- data/lib/riffer/stream_events/web_search_status.rb +1 -5
- data/lib/riffer/testing/minitest.rb +4 -5
- data/lib/riffer/testing.rb +5 -38
- data/lib/riffer/tool.rb +2 -28
- data/lib/riffer/tools/response.rb +3 -32
- data/lib/riffer/tools/runtime/fibers.rb +0 -6
- data/lib/riffer/tools/runtime/inline.rb +0 -1
- data/lib/riffer/tools/runtime/threaded.rb +0 -6
- data/lib/riffer/tools/runtime.rb +5 -26
- data/lib/riffer/tools/toolable.rb +0 -37
- data/lib/riffer/tracing/capture.rb +3 -5
- data/lib/riffer/tracing/no_op.rb +0 -7
- data/lib/riffer/tracing/otel.rb +7 -16
- data/lib/riffer/tracing/stream_recorder.rb +0 -7
- data/lib/riffer/tracing.rb +4 -27
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +2 -30
- data/sig/generated/riffer/agent/config.rbs +12 -48
- data/sig/generated/riffer/agent/context.rbs +2 -26
- data/sig/generated/riffer/agent/outcome.rbs +0 -20
- data/sig/generated/riffer/agent/response.rbs +1 -29
- data/sig/generated/riffer/agent/run.rbs +1 -27
- data/sig/generated/riffer/agent/serializer.rbs +2 -27
- data/sig/generated/riffer/agent/session/repair.rbs +2 -10
- data/sig/generated/riffer/agent/session.rbs +10 -36
- data/sig/generated/riffer/agent/structured_output/result.rbs +0 -7
- data/sig/generated/riffer/agent/structured_output.rbs +0 -7
- data/sig/generated/riffer/agent.rbs +5 -121
- data/sig/generated/riffer/config/amazon_bedrock.rbs +21 -0
- data/sig/generated/riffer/config/anthropic.rbs +15 -0
- data/sig/generated/riffer/config/azure_open_ai.rbs +21 -0
- data/sig/generated/riffer/config/evals.rbs +13 -0
- data/sig/generated/riffer/config/files.rbs +43 -0
- data/sig/generated/riffer/config/gemini.rbs +15 -0
- data/sig/generated/riffer/config/mcp.rbs +19 -0
- data/sig/generated/riffer/config/open_ai.rbs +21 -0
- data/sig/generated/riffer/config/open_router.rbs +15 -0
- data/sig/generated/riffer/config/pricing/rates.rbs +19 -0
- data/sig/generated/riffer/config/pricing.rbs +31 -0
- data/sig/generated/riffer/config/skills.rbs +19 -0
- data/sig/generated/riffer/config/tracing.rbs +25 -0
- data/sig/generated/riffer/config.rbs +2 -307
- data/sig/generated/riffer/evals/evaluator.rbs +0 -28
- data/sig/generated/riffer/evals/evaluator_runner.rbs +0 -14
- data/sig/generated/riffer/evals/judge.rbs +0 -7
- data/sig/generated/riffer/evals/result.rbs +1 -11
- data/sig/generated/riffer/evals/run_result.rbs +0 -12
- data/sig/generated/riffer/evals/scenario_result.rbs +0 -14
- data/sig/generated/riffer/files/resolver.rbs +0 -7
- data/sig/generated/riffer/guardrail.rbs +2 -22
- data/sig/generated/riffer/guardrails/modification.rbs +0 -6
- data/sig/generated/riffer/guardrails/result.rbs +0 -15
- data/sig/generated/riffer/guardrails/runner.rbs +0 -11
- data/sig/generated/riffer/guardrails/tripwire.rbs +0 -8
- data/sig/generated/riffer/guardrails.rbs +0 -2
- data/sig/generated/riffer/helpers/boolean.rbs +0 -4
- data/sig/generated/riffer/helpers/call_or_value.rbs +0 -3
- data/sig/generated/riffer/helpers/deep_dup.rbs +0 -8
- data/sig/generated/riffer/helpers/dependencies.rbs +0 -4
- data/sig/generated/riffer/helpers/identifier.rbs +0 -12
- data/sig/generated/riffer/helpers/validate.rbs +21 -0
- data/sig/generated/riffer/mcp/authenticated_tool.rbs +0 -5
- data/sig/generated/riffer/mcp/client.rbs +0 -7
- data/sig/generated/riffer/mcp/manifest.rbs +3 -7
- data/sig/generated/riffer/mcp/registration.rbs +0 -10
- data/sig/generated/riffer/mcp/registry.rbs +0 -9
- data/sig/generated/riffer/mcp/search_tool.rbs +0 -4
- data/sig/generated/riffer/mcp/tool.rbs +0 -4
- data/sig/generated/riffer/mcp/tool_factory.rbs +0 -6
- data/sig/generated/riffer/mcp.rbs +2 -22
- data/sig/generated/riffer/messages/assistant/reasoning_part.rbs +4 -17
- data/sig/generated/riffer/messages/assistant/tool_call.rbs +1 -9
- data/sig/generated/riffer/messages/assistant.rbs +10 -30
- data/sig/generated/riffer/messages/base.rbs +0 -12
- data/sig/generated/riffer/messages/system.rbs +0 -3
- data/sig/generated/riffer/messages/tool.rbs +0 -12
- data/sig/generated/riffer/messages/user/file_part.rbs +0 -30
- data/sig/generated/riffer/messages/user.rbs +0 -4
- data/sig/generated/riffer/params/boolean.rbs +1 -4
- data/sig/generated/riffer/params/param.rbs +0 -31
- data/sig/generated/riffer/params.rbs +4 -40
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +29 -25
- data/sig/generated/riffer/providers/anthropic.rbs +0 -10
- data/sig/generated/riffer/providers/azure_open_ai.rbs +0 -8
- data/sig/generated/riffer/providers/base.rbs +0 -28
- data/sig/generated/riffer/providers/finish_reason.rbs +0 -6
- data/sig/generated/riffer/providers/gemini/client.rbs +4 -17
- data/sig/generated/riffer/providers/gemini.rbs +0 -6
- data/sig/generated/riffer/providers/mock.rbs +0 -26
- data/sig/generated/riffer/providers/open_ai.rbs +2 -9
- data/sig/generated/riffer/providers/open_router.rbs +33 -14
- data/sig/generated/riffer/providers/repository.rbs +2 -14
- data/sig/generated/riffer/providers/token_usage.rbs +5 -14
- data/sig/generated/riffer/registrable.rbs +0 -45
- data/sig/generated/riffer/runner/fibers.rbs +0 -6
- data/sig/generated/riffer/runner/sequential.rbs +0 -1
- data/sig/generated/riffer/runner/threaded.rbs +0 -3
- data/sig/generated/riffer/runner.rbs +0 -3
- data/sig/generated/riffer/skills/activate_tool.rbs +0 -3
- data/sig/generated/riffer/skills/adapter.rbs +0 -8
- data/sig/generated/riffer/skills/backend.rbs +2 -7
- data/sig/generated/riffer/skills/config.rbs +0 -20
- data/sig/generated/riffer/skills/context.rbs +0 -29
- data/sig/generated/riffer/skills/filesystem_backend.rbs +0 -7
- data/sig/generated/riffer/skills/frontmatter.rbs +2 -16
- data/sig/generated/riffer/skills/markdown_adapter.rbs +0 -6
- data/sig/generated/riffer/skills/xml_adapter.rbs +0 -3
- data/sig/generated/riffer/stream_events/base.rbs +0 -3
- data/sig/generated/riffer/stream_events/finish_reason_done.rbs +1 -6
- data/sig/generated/riffer/stream_events/guardrail_modification.rbs +0 -10
- data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +0 -10
- data/sig/generated/riffer/stream_events/interrupt.rbs +2 -10
- data/sig/generated/riffer/stream_events/reasoning_delta.rbs +0 -3
- data/sig/generated/riffer/stream_events/reasoning_done.rbs +1 -5
- data/sig/generated/riffer/stream_events/skill_activation.rbs +0 -3
- data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
- data/sig/generated/riffer/stream_events/text_done.rbs +0 -2
- data/sig/generated/riffer/stream_events/token_usage_done.rbs +0 -2
- data/sig/generated/riffer/stream_events/tool_call_delta.rbs +1 -5
- data/sig/generated/riffer/stream_events/tool_call_done.rbs +0 -5
- data/sig/generated/riffer/stream_events/web_search_done.rbs +0 -3
- data/sig/generated/riffer/stream_events/web_search_status.rbs +1 -5
- data/sig/generated/riffer/testing.rbs +0 -38
- data/sig/generated/riffer/tool.rbs +0 -27
- data/sig/generated/riffer/tools/response.rbs +3 -29
- data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -5
- data/sig/generated/riffer/tools/runtime/inline.rbs +0 -1
- data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -5
- data/sig/generated/riffer/tools/runtime.rbs +2 -24
- data/sig/generated/riffer/tools/toolable.rbs +0 -37
- data/sig/generated/riffer/tracing/capture.rbs +2 -5
- data/sig/generated/riffer/tracing/no_op.rbs +0 -7
- data/sig/generated/riffer/tracing/otel.rbs +7 -16
- data/sig/generated/riffer/tracing/stream_recorder.rbs +0 -2
- data/sig/generated/riffer/tracing.rbs +4 -23
- data/sig/generated/riffer.rbs +2 -29
- data/sig/manual/riffer/helpers/validate.rbs +5 -0
- metadata +30 -1
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Searches available MCP tools by name or description.
|
|
5
4
|
class Riffer::Mcp::SearchTool < Riffer::Tool
|
|
6
5
|
IDENTIFIER = "mcp_search"
|
|
7
6
|
|
|
8
|
-
# Successful search response carrying the matched tool classes.
|
|
9
7
|
class Result < Riffer::Tools::Response
|
|
10
|
-
# Tool classes that matched the search query.
|
|
11
8
|
attr_reader :discovered_tools #: Array[singleton(Riffer::Tool)] # @dynamic discovered_tools
|
|
12
9
|
|
|
13
10
|
#--
|
|
@@ -25,7 +22,6 @@ class Riffer::Mcp::SearchTool < Riffer::Tool
|
|
|
25
22
|
required :query, String, description: "Non-empty substring to filter tools by name or description."
|
|
26
23
|
end
|
|
27
24
|
|
|
28
|
-
# Searches progressive MCP tools and returns a +Result+ with +discovered_tools+.
|
|
29
25
|
#--
|
|
30
26
|
#: (context: Riffer::Agent::Context?, query: String) -> Riffer::Tools::Response
|
|
31
27
|
def call(context:, query:)
|
data/lib/riffer/mcp/tool.rb
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Base class for MCP-generated tools.
|
|
5
4
|
class Riffer::Mcp::Tool < Riffer::Tool
|
|
6
5
|
# @rbs self.@mcp_server_tool_name: String?
|
|
7
6
|
# @rbs self.@input_schema: Hash[Symbol, untyped]?
|
|
8
7
|
|
|
9
|
-
# Returns the unprefixed tool name used for +tools/call+ on the MCP server.
|
|
10
8
|
#--
|
|
11
9
|
#: () -> String
|
|
12
10
|
def self.mcp_server_tool_name
|
|
13
11
|
@mcp_server_tool_name || raise(NotImplementedError, "#{self} must set @mcp_server_tool_name")
|
|
14
12
|
end
|
|
15
13
|
|
|
16
|
-
# Returns the server-published input schema, falling back to the params DSL.
|
|
17
|
-
# MCP schemas are server-defined, so +strict+ is not applied to them.
|
|
18
14
|
#--
|
|
19
15
|
#: (?strict: bool) -> Hash[Symbol, untyped]
|
|
20
16
|
def self.parameters_schema(strict: false)
|
|
17
|
+
# MCP schemas are server-defined, so +strict+ is not applied to them.
|
|
21
18
|
@input_schema || super
|
|
22
19
|
end
|
|
23
20
|
end
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Generates anonymous Riffer::Mcp::Tool subclasses from MCP tool definitions.
|
|
5
|
-
# Generated tools delegate +#call+ to the MCP client and skip Riffer's param
|
|
6
|
-
# validation — the MCP server validates inputs.
|
|
7
4
|
module Riffer::Mcp::ToolFactory
|
|
8
5
|
extend self
|
|
9
6
|
|
|
10
|
-
# Builds one Riffer::Mcp::Tool subclass per tool definition, prefixing names
|
|
11
|
-
# with the manifest name to avoid cross-server collisions (e.g.
|
|
12
|
-
# +jira__search+); the server-side name stays on +.mcp_server_tool_name+.
|
|
13
7
|
#--
|
|
14
8
|
#: (String, Riffer::Mcp::Client, Array[Hash[Symbol, untyped]]) -> Array[singleton(Riffer::Mcp::Tool)]
|
|
15
9
|
def build(manifest_name, client, tool_defs)
|
|
@@ -25,6 +19,8 @@ module Riffer::Mcp::ToolFactory
|
|
|
25
19
|
|
|
26
20
|
#: (String, Riffer::Mcp::Client, Hash[Symbol, untyped]) -> singleton(Riffer::Mcp::Tool)
|
|
27
21
|
def build_tool_class(manifest_name, client, descriptor)
|
|
22
|
+
# Prefixed to avoid cross-server collisions; the server-side name stays on
|
|
23
|
+
# mcp_server_tool_name.
|
|
28
24
|
prefixed = "#{sanitize_name_component(manifest_name)}__#{sanitize_name_component(descriptor[:name])}"
|
|
29
25
|
|
|
30
26
|
# steep does not model Class.new's class_eval semantics — the block body
|
|
@@ -37,6 +33,7 @@ module Riffer::Mcp::ToolFactory
|
|
|
37
33
|
@description = descriptor[:description]
|
|
38
34
|
@input_schema = descriptor[:input_schema]
|
|
39
35
|
|
|
36
|
+
# No Riffer param validation — the MCP server validates inputs.
|
|
40
37
|
define_method(:call) do |context:, **kwargs|
|
|
41
38
|
text(client.tools_call(self.class.mcp_server_tool_name, kwargs))
|
|
42
39
|
end
|
data/lib/riffer/mcp.rb
CHANGED
|
@@ -1,49 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Integration with Model Context Protocol (MCP) servers. Register servers
|
|
5
|
-
# globally; agents opt-in by tag via the +use_mcp+ DSL. Tags are
|
|
6
|
-
# application-defined; see +docs/MCP.md+.
|
|
7
|
-
#
|
|
8
|
-
# Riffer::Mcp.register(
|
|
9
|
-
# name: "github",
|
|
10
|
-
# tags: [:github],
|
|
11
|
-
# endpoint: "https://mcp.github.com",
|
|
12
|
-
# discovery_headers: -> { {"Authorization" => "Bearer #{ENV['GITHUB_TOKEN']}"} }
|
|
13
|
-
# )
|
|
14
|
-
#
|
|
15
|
-
# class MyAgent < Riffer::Agent
|
|
16
|
-
# model "openai/gpt-4o"
|
|
17
|
-
# use_mcp :github
|
|
18
|
-
# end
|
|
19
|
-
#
|
|
20
4
|
module Riffer::Mcp
|
|
21
5
|
extend self
|
|
22
6
|
|
|
23
|
-
# Base error for all MCP-related failures.
|
|
24
7
|
class Error < Riffer::Error; end
|
|
25
8
|
|
|
26
9
|
# Raised when +Riffer.config.mcp.credentials+ returns +nil+ during +tools/call+
|
|
27
|
-
# after the server's tools were included for this run.
|
|
10
|
+
# after the server's tools were already included for this run.
|
|
28
11
|
class CredentialsDeniedError < Error; end
|
|
29
12
|
|
|
30
|
-
#
|
|
31
|
-
# on discovery failure.
|
|
13
|
+
# Blocks until tool discovery completes.
|
|
32
14
|
#--
|
|
33
15
|
#: ((Hash[Symbol, untyped] | Riffer::Mcp::Manifest)) -> Riffer::Mcp::Registration
|
|
34
16
|
def register(manifest_or_hash)
|
|
35
17
|
Registry.register(manifest_or_hash)
|
|
36
18
|
end
|
|
37
19
|
|
|
38
|
-
# Removes a registration by name; subsequent agent runs won't see its tools.
|
|
39
20
|
#--
|
|
40
21
|
#: (String) -> void
|
|
41
22
|
def unregister(name)
|
|
42
23
|
Registry.unregister(name)
|
|
43
24
|
end
|
|
44
25
|
|
|
45
|
-
# Returns all current registrations keyed by name.
|
|
46
|
-
#
|
|
47
26
|
#--
|
|
48
27
|
#: () -> Hash[String, Riffer::Mcp::Registration]
|
|
49
28
|
def registrations
|
|
@@ -1,35 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# provider that requires its own thinking blocks back gets them unchanged.
|
|
4
|
+
# Stored and replayed verbatim, never interpreted, so a provider that requires its own thinking
|
|
5
|
+
# blocks back gets them unchanged.
|
|
7
6
|
class Riffer::Messages::Assistant::ReasoningPart
|
|
8
7
|
TYPES = %i[text summary encrypted].freeze #: Array[Symbol]
|
|
9
8
|
|
|
10
|
-
# What the part carries: readable reasoning (+:text+), a provider-condensed
|
|
11
|
-
# digest (+:summary+), or an opaque payload (+:encrypted+).
|
|
12
9
|
attr_reader :type #: Symbol # @dynamic type
|
|
13
|
-
|
|
14
|
-
# The reasoning prose, for +:text+ and +:summary+ parts.
|
|
15
10
|
attr_reader :text #: String? # @dynamic text
|
|
16
|
-
|
|
17
|
-
# The opaque payload, for +:encrypted+ parts.
|
|
18
11
|
attr_reader :data #: String? # @dynamic data
|
|
19
|
-
|
|
20
|
-
# The provider's signature over the part, when it issues one.
|
|
21
12
|
attr_reader :signature #: String? # @dynamic signature
|
|
22
|
-
|
|
23
|
-
# The provider's identifier for the part, when it issues one.
|
|
24
13
|
attr_reader :id #: String? # @dynamic id
|
|
25
14
|
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# recognize, so it is never validated here.
|
|
15
|
+
# Owned by the producing adapter (e.g. <tt>"anthropic-claude-v1"</tt>); adapters replay only the
|
|
16
|
+
# formats they recognize, so it is never validated here.
|
|
29
17
|
attr_reader :format #: String? # @dynamic format
|
|
30
18
|
|
|
31
|
-
# Builds a ReasoningPart from a hash, or returns +part+ unchanged when it is
|
|
32
|
-
# already a ReasoningPart.
|
|
33
19
|
#--
|
|
34
20
|
#: ((Hash[Symbol, untyped] | Riffer::Messages::Assistant::ReasoningPart)) -> Riffer::Messages::Assistant::ReasoningPart
|
|
35
21
|
def self.from_hash(part)
|
|
@@ -45,7 +31,6 @@ class Riffer::Messages::Assistant::ReasoningPart
|
|
|
45
31
|
)
|
|
46
32
|
end
|
|
47
33
|
|
|
48
|
-
# Raises Riffer::ArgumentError on a +type+ outside TYPES.
|
|
49
34
|
#--
|
|
50
35
|
#: (type: Symbol, ?text: String?, ?data: String?, ?signature: String?, ?id: String?, ?format: String?) -> void
|
|
51
36
|
def initialize(type:, text: nil, data: nil, signature: nil, id: nil, format: nil)
|
|
@@ -62,8 +47,6 @@ class Riffer::Messages::Assistant::ReasoningPart
|
|
|
62
47
|
@format = format
|
|
63
48
|
end
|
|
64
49
|
|
|
65
|
-
# Serializes the part to a hash, omitting the fields it doesn't carry.
|
|
66
|
-
#
|
|
67
50
|
#--
|
|
68
51
|
#: () -> Hash[Symbol, untyped]
|
|
69
52
|
def to_h
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Represents one tool invocation the model requested on an assistant message.
|
|
5
4
|
class Riffer::Messages::Assistant::ToolCall
|
|
6
|
-
# The provider's identifier for the call, echoed back on the tool result.
|
|
7
5
|
attr_reader :call_id #: String # @dynamic call_id
|
|
8
6
|
|
|
9
|
-
# The name of the tool to invoke.
|
|
10
7
|
attr_reader :name #: String # @dynamic name
|
|
11
8
|
|
|
12
|
-
#
|
|
9
|
+
# JSON-encoded, exactly as the provider emitted them.
|
|
13
10
|
attr_reader :arguments #: String # @dynamic arguments
|
|
14
11
|
|
|
15
|
-
# Builds a ToolCall from a hash, or returns +call+ unchanged when it is
|
|
16
|
-
# already a ToolCall. Raises Riffer::ArgumentError when the hash is missing
|
|
17
|
-
# a field.
|
|
18
12
|
#--
|
|
19
13
|
#: ((Hash[Symbol, untyped] | Riffer::Messages::Assistant::ToolCall)) -> Riffer::Messages::Assistant::ToolCall
|
|
20
14
|
def self.from_hash(call)
|
|
@@ -35,8 +29,6 @@ class Riffer::Messages::Assistant::ToolCall
|
|
|
35
29
|
@arguments = arguments
|
|
36
30
|
end
|
|
37
31
|
|
|
38
|
-
# Serializes the call to a hash.
|
|
39
|
-
#
|
|
40
32
|
#--
|
|
41
33
|
#: () -> Hash[Symbol, untyped]
|
|
42
34
|
def to_h
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Represents an assistant (LLM) message in a conversation; may include tool
|
|
5
|
-
# calls when the LLM requests tool execution.
|
|
6
4
|
class Riffer::Messages::Assistant < Riffer::Messages::Base
|
|
7
|
-
# The reasoning part types
|
|
5
|
+
# The other reasoning part types carry no prose.
|
|
8
6
|
REASONING_TEXT_TYPES = %i[text summary].freeze #: Array[Symbol]
|
|
9
7
|
|
|
10
|
-
# Builds an Assistant message from a hash, or returns +msg+ unchanged when it
|
|
11
|
-
# is already an Assistant message. Raises Riffer::ArgumentError on an invalid
|
|
12
|
-
# tool call, reasoning part, or +finish_reason+.
|
|
13
8
|
#--
|
|
14
9
|
#: ((Hash[Symbol, untyped] | Riffer::Messages::Assistant)) -> Riffer::Messages::Assistant
|
|
15
10
|
def self.from_hash(msg)
|
|
@@ -27,29 +22,18 @@ class Riffer::Messages::Assistant < Riffer::Messages::Base
|
|
|
27
22
|
)
|
|
28
23
|
end
|
|
29
24
|
|
|
30
|
-
# Array of tool calls requested by the assistant.
|
|
31
25
|
attr_reader :tool_calls #: Array[Riffer::Messages::Assistant::ToolCall] # @dynamic tool_calls
|
|
32
26
|
|
|
33
|
-
# The model's reasoning blocks for this response, in the order the provider
|
|
34
|
-
# emitted them.
|
|
35
27
|
attr_reader :reasoning #: Array[Riffer::Messages::Assistant::ReasoningPart] # @dynamic reasoning
|
|
36
28
|
|
|
37
|
-
# Token usage data for this response.
|
|
38
29
|
attr_reader :token_usage #: Riffer::Providers::TokenUsage? # @dynamic token_usage
|
|
39
30
|
|
|
40
|
-
# Parsed structured output hash, or nil when not applicable.
|
|
41
31
|
attr_reader :structured_output #: Hash[Symbol, untyped]? # @dynamic structured_output
|
|
42
32
|
|
|
43
|
-
# Normalized reason the provider finished this response, when reported (see
|
|
44
|
-
# <tt>Riffer::Providers::FinishReason::VALUES</tt>).
|
|
45
33
|
attr_reader :finish_reason #: Symbol? # @dynamic finish_reason
|
|
46
34
|
|
|
47
|
-
# The provider's raw finish-reason value behind +finish_reason+, when one
|
|
48
|
-
# exists on the wire.
|
|
49
35
|
attr_reader :finish_reason_raw #: String? # @dynamic finish_reason_raw
|
|
50
36
|
|
|
51
|
-
# Raises Riffer::ArgumentError when +finish_reason+ is outside the
|
|
52
|
-
# normalized vocabulary.
|
|
53
37
|
#--
|
|
54
38
|
#: (
|
|
55
39
|
# String,
|
|
@@ -109,8 +93,6 @@ class Riffer::Messages::Assistant < Riffer::Messages::Base
|
|
|
109
93
|
!@reasoning.empty?
|
|
110
94
|
end
|
|
111
95
|
|
|
112
|
-
# The readable reasoning across this message's +:text+ and +:summary+ parts,
|
|
113
|
-
# joined by blank lines, or nil when it carries none.
|
|
114
96
|
#--
|
|
115
97
|
#: () -> String?
|
|
116
98
|
def reasoning_text
|
|
@@ -128,8 +110,6 @@ class Riffer::Messages::Assistant < Riffer::Messages::Base
|
|
|
128
110
|
)
|
|
129
111
|
end
|
|
130
112
|
|
|
131
|
-
# Converts the message to a hash.
|
|
132
|
-
#
|
|
133
113
|
#--
|
|
134
114
|
#: () -> Hash[Symbol, untyped]
|
|
135
115
|
def to_h
|
data/lib/riffer/messages/base.rb
CHANGED
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
require "securerandom"
|
|
5
5
|
|
|
6
|
-
# Base class for all message types. Subclasses must implement +role+.
|
|
7
6
|
class Riffer::Messages::Base
|
|
8
|
-
# Builds the matching message subclass from a hash, or returns +msg+ unchanged
|
|
9
|
-
# when it is already a message. Raises Riffer::ArgumentError on an invalid message.
|
|
10
7
|
#--
|
|
11
8
|
#: ((Hash[Symbol, untyped] | Riffer::Messages::Base)) -> Riffer::Messages::Base
|
|
12
9
|
def self.from_hash(msg)
|
|
@@ -23,10 +20,8 @@ class Riffer::Messages::Base
|
|
|
23
20
|
end
|
|
24
21
|
end
|
|
25
22
|
|
|
26
|
-
# The message content.
|
|
27
23
|
attr_reader :content #: String # @dynamic content
|
|
28
24
|
|
|
29
|
-
# The message id, or nil when +Riffer.config.message_id_strategy+ is +:none+.
|
|
30
25
|
attr_reader :id #: String? # @dynamic id
|
|
31
26
|
|
|
32
27
|
#--
|
|
@@ -36,8 +31,6 @@ class Riffer::Messages::Base
|
|
|
36
31
|
@id = id || generate_id
|
|
37
32
|
end
|
|
38
33
|
|
|
39
|
-
# Converts the message to a hash.
|
|
40
|
-
#
|
|
41
34
|
#--
|
|
42
35
|
#: () -> Hash[Symbol, untyped]
|
|
43
36
|
def to_h
|
|
@@ -46,23 +39,18 @@ class Riffer::Messages::Base
|
|
|
46
39
|
hash
|
|
47
40
|
end
|
|
48
41
|
|
|
49
|
-
# Returns the message role.
|
|
50
42
|
#--
|
|
51
43
|
#: () -> Symbol
|
|
52
44
|
def role
|
|
53
45
|
raise NotImplementedError, "Subclasses must implement #role"
|
|
54
46
|
end
|
|
55
47
|
|
|
56
|
-
# Whether this message carries pending tool calls (overridden by
|
|
57
|
-
# +Riffer::Messages::Assistant+).
|
|
58
48
|
#--
|
|
59
49
|
#: () -> bool
|
|
60
50
|
def has_tool_calls?
|
|
61
51
|
false
|
|
62
52
|
end
|
|
63
53
|
|
|
64
|
-
# Merges another same-role message into this one. +Tool+ messages are never
|
|
65
|
-
# merged.
|
|
66
54
|
#--
|
|
67
55
|
#: (untyped) -> Riffer::Messages::Base
|
|
68
56
|
def +(other)
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Represents a system message (instructions) in a conversation.
|
|
5
4
|
class Riffer::Messages::System < Riffer::Messages::Base
|
|
6
|
-
# Builds a System message from a hash, or returns +msg+ unchanged when it is
|
|
7
|
-
# already a System message.
|
|
8
5
|
#--
|
|
9
6
|
#: ((Hash[Symbol, untyped] | Riffer::Messages::System)) -> Riffer::Messages::System
|
|
10
7
|
def self.from_hash(msg)
|
data/lib/riffer/messages/tool.rb
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Represents a tool execution result in a conversation.
|
|
5
4
|
class Riffer::Messages::Tool < Riffer::Messages::Base
|
|
6
|
-
# Builds a Tool message from a hash, or returns +msg+ unchanged when it is
|
|
7
|
-
# already a Tool message.
|
|
8
5
|
#--
|
|
9
6
|
#: ((Hash[Symbol, untyped] | Riffer::Messages::Tool)) -> Riffer::Messages::Tool
|
|
10
7
|
def self.from_hash(msg)
|
|
@@ -20,17 +17,9 @@ class Riffer::Messages::Tool < Riffer::Messages::Base
|
|
|
20
17
|
)
|
|
21
18
|
end
|
|
22
19
|
|
|
23
|
-
# The ID of the tool call this result responds to.
|
|
24
20
|
attr_reader :tool_call_id #: String # @dynamic tool_call_id
|
|
25
|
-
|
|
26
|
-
# The name of the tool that was called.
|
|
27
21
|
attr_reader :name #: String # @dynamic name
|
|
28
|
-
|
|
29
|
-
# The error message if the tool execution failed.
|
|
30
22
|
attr_reader :error #: String? # @dynamic error
|
|
31
|
-
|
|
32
|
-
# The type of error (:unknown_tool, :validation_error, :execution_error,
|
|
33
|
-
# :timeout_error, :unhandled_error).
|
|
34
23
|
attr_reader :error_type #: Symbol? # @dynamic error_type
|
|
35
24
|
|
|
36
25
|
#--
|
|
@@ -43,8 +32,6 @@ class Riffer::Messages::Tool < Riffer::Messages::Base
|
|
|
43
32
|
@error_type = error_type
|
|
44
33
|
end
|
|
45
34
|
|
|
46
|
-
# Returns true if the tool execution resulted in an error.
|
|
47
|
-
#
|
|
48
35
|
#--
|
|
49
36
|
#: () -> bool
|
|
50
37
|
def error?
|
|
@@ -57,8 +44,6 @@ class Riffer::Messages::Tool < Riffer::Messages::Base
|
|
|
57
44
|
:tool
|
|
58
45
|
end
|
|
59
46
|
|
|
60
|
-
# Converts the message to a hash.
|
|
61
|
-
#
|
|
62
47
|
#--
|
|
63
48
|
#: () -> Hash[Symbol, untyped]
|
|
64
49
|
def to_h
|
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
require "base64"
|
|
5
5
|
require "uri"
|
|
6
6
|
|
|
7
|
-
# Represents a file attachment (image or document) — from a URL (+from_url+) or
|
|
8
|
-
# raw base64 data (+new+).
|
|
9
7
|
class Riffer::Messages::User::FilePart
|
|
10
8
|
# @rbs @url_string: String?
|
|
11
9
|
# @rbs @data: String?
|
|
@@ -28,17 +26,12 @@ class Riffer::Messages::User::FilePart
|
|
|
28
26
|
SUPPORTED_MEDIA_TYPES = MEDIA_TYPES.values.uniq.freeze #: Array[String]
|
|
29
27
|
SHA256_PATTERN = /\A[0-9a-f]{64}\z/i #: Regexp
|
|
30
28
|
|
|
31
|
-
# The MIME type of the file.
|
|
32
29
|
attr_reader :media_type #: String # @dynamic media_type
|
|
33
30
|
|
|
34
|
-
# The filename, if available.
|
|
35
31
|
attr_reader :filename #: String? # @dynamic filename
|
|
36
32
|
|
|
37
|
-
# The expected SHA-256 of the file contents, if the caller supplied one.
|
|
38
33
|
attr_reader :sha256 #: String? # @dynamic sha256
|
|
39
34
|
|
|
40
|
-
# Raises Riffer::ArgumentError unless +data+ or +url+ is given and
|
|
41
|
-
# +media_type+ is supported.
|
|
42
35
|
#--
|
|
43
36
|
#: (media_type: String, ?data: String?, ?filename: String?, ?url: String?, ?sha256: String?) -> void
|
|
44
37
|
def initialize(media_type:, data: nil, filename: nil, url: nil, sha256: nil)
|
|
@@ -59,17 +52,12 @@ class Riffer::Messages::User::FilePart
|
|
|
59
52
|
@url_string = url
|
|
60
53
|
end
|
|
61
54
|
|
|
62
|
-
# Creates a FilePart from a URL, detecting +media_type+ from the path
|
|
63
|
-
# extension when omitted. Raises Riffer::ArgumentError if it can't be detected.
|
|
64
55
|
#--
|
|
65
56
|
#: (String, ?media_type: String?, ?filename: String?, ?sha256: String?) -> Riffer::Messages::User::FilePart
|
|
66
57
|
def self.from_url(url, media_type: nil, filename: nil, sha256: nil)
|
|
67
58
|
new(url: url, media_type: media_type || detect_media_type(url), filename: filename, sha256: sha256)
|
|
68
59
|
end
|
|
69
60
|
|
|
70
|
-
# Builds a FilePart from a +{url:, media_type:}+ or +{data:, media_type:}+ hash,
|
|
71
|
-
# or returns +file+ unchanged when it is already a FilePart. Raises
|
|
72
|
-
# Riffer::ArgumentError on an invalid hash.
|
|
73
61
|
#--
|
|
74
62
|
#: ((Hash[Symbol, untyped] | Riffer::Messages::User::FilePart)) -> Riffer::Messages::User::FilePart
|
|
75
63
|
def self.from_hash(file)
|
|
@@ -90,7 +78,6 @@ class Riffer::Messages::User::FilePart
|
|
|
90
78
|
end
|
|
91
79
|
end
|
|
92
80
|
|
|
93
|
-
# Raises Riffer::ArgumentError when the URL's extension isn't a known media type.
|
|
94
81
|
#--
|
|
95
82
|
#: (String) -> String
|
|
96
83
|
def self.detect_media_type(url)
|
|
@@ -99,8 +86,6 @@ class Riffer::Messages::User::FilePart
|
|
|
99
86
|
end
|
|
100
87
|
private_class_method :detect_media_type
|
|
101
88
|
|
|
102
|
-
# The base64-encoded contents - caller-supplied, or filled in by the file
|
|
103
|
-
# resolver after a download. Nil for a URL source riffer hasn't fetched.
|
|
104
89
|
#--
|
|
105
90
|
#: () -> String?
|
|
106
91
|
def data
|
|
@@ -124,60 +109,47 @@ class Riffer::Messages::User::FilePart
|
|
|
124
109
|
@data_bytes = bytes
|
|
125
110
|
end
|
|
126
111
|
|
|
127
|
-
# Whether data was supplied directly, as opposed to filled in later by the
|
|
128
|
-
# file resolver after a download
|
|
129
112
|
#: () -> bool
|
|
130
113
|
def inline_data?
|
|
131
114
|
!@data.nil?
|
|
132
115
|
end
|
|
133
116
|
|
|
134
|
-
# Caches bytes fetched for a URL source. Deliberately absent from +to_h+:
|
|
135
|
-
# the agent loop re-sends history on every turn, so the cache saves refreshing
|
|
136
|
-
# the same file, while persisted history stays free of megabytes of base64
|
|
137
117
|
#--
|
|
138
118
|
#: (String) -> void
|
|
139
119
|
def cache_downloaded_data(data)
|
|
140
120
|
@downloaded_data = data
|
|
141
121
|
end
|
|
142
122
|
|
|
143
|
-
# Returns the URL if the source was a URL, nil otherwise.
|
|
144
|
-
#
|
|
145
123
|
#--
|
|
146
124
|
#: () -> String?
|
|
147
125
|
def url
|
|
148
126
|
@url_string
|
|
149
127
|
end
|
|
150
128
|
|
|
151
|
-
# Returns true if the source was a URL.
|
|
152
|
-
#
|
|
153
129
|
#--
|
|
154
130
|
#: () -> bool
|
|
155
131
|
def url?
|
|
156
132
|
!@url_string.nil?
|
|
157
133
|
end
|
|
158
134
|
|
|
159
|
-
# Returns true if the file is an image.
|
|
160
|
-
#
|
|
161
135
|
#--
|
|
162
136
|
#: () -> bool
|
|
163
137
|
def image?
|
|
164
138
|
media_type.start_with?("image/")
|
|
165
139
|
end
|
|
166
140
|
|
|
167
|
-
# Returns true if the file is a document (not an image).
|
|
168
|
-
#
|
|
169
141
|
#--
|
|
170
142
|
#: () -> bool
|
|
171
143
|
def document?
|
|
172
144
|
!image?
|
|
173
145
|
end
|
|
174
146
|
|
|
175
|
-
# Serializes the FilePart to a hash.
|
|
176
|
-
#
|
|
177
147
|
#--
|
|
178
148
|
#: () -> Hash[Symbol, untyped]
|
|
179
149
|
def to_h
|
|
180
150
|
hash = { media_type: media_type } #: Hash[Symbol, untyped]
|
|
151
|
+
# Downloaded data is left out so persisted history stays free of megabytes
|
|
152
|
+
# of base64; the in-memory cache still spares refetching on every turn.
|
|
181
153
|
hash[:data] = @data if @data
|
|
182
154
|
hash[:url] = @url_string if @url_string
|
|
183
155
|
hash[:filename] = filename if filename
|
data/lib/riffer/messages/user.rb
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Represents a user message in a conversation.
|
|
5
4
|
class Riffer::Messages::User < Riffer::Messages::Base
|
|
6
|
-
# Builds a User message from a hash, or returns +msg+ unchanged when it is
|
|
7
|
-
# already a User message. Raises Riffer::ArgumentError on an invalid file.
|
|
8
5
|
#--
|
|
9
6
|
#: ((Hash[Symbol, untyped] | Riffer::Messages::User)) -> Riffer::Messages::User
|
|
10
7
|
def self.from_hash(msg)
|
|
@@ -14,7 +11,6 @@ class Riffer::Messages::User < Riffer::Messages::Base
|
|
|
14
11
|
new(msg[:content], id: msg[:id], files: files)
|
|
15
12
|
end
|
|
16
13
|
|
|
17
|
-
# File attachments for this message.
|
|
18
14
|
attr_reader :files #: Array[Riffer::Messages::User::FilePart] # @dynamic files
|
|
19
15
|
|
|
20
16
|
#--
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
# (+true+/+false+ are +TrueClass+/+FalseClass+).
|
|
6
|
-
#
|
|
7
|
-
# required :verbose, Riffer::Params::Boolean
|
|
8
|
-
#
|
|
4
|
+
# Ruby has no +Boolean+ class, so params declare booleans with this sentinel.
|
|
9
5
|
module Riffer::Params::Boolean
|
|
10
6
|
end
|
data/lib/riffer/params/param.rb
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# A single parameter definition, handling type validation and JSON Schema
|
|
5
|
-
# generation.
|
|
6
4
|
class Riffer::Params::Param
|
|
7
|
-
# Maps Ruby types to JSON Schema type strings
|
|
8
5
|
TYPE_MAPPINGS = {
|
|
9
6
|
String => "string",
|
|
10
7
|
Integer => "integer",
|
|
@@ -16,11 +13,8 @@ class Riffer::Params::Param
|
|
|
16
13
|
Hash => "object",
|
|
17
14
|
}.freeze #: Hash[Module, String]
|
|
18
15
|
|
|
19
|
-
# Primitive types allowed for the <tt>of:</tt> keyword on Array params
|
|
20
16
|
PRIMITIVE_TYPES = (TYPE_MAPPINGS.keys - [Array, Hash]).freeze #: Array[Module]
|
|
21
17
|
|
|
22
|
-
# Maps JSON Schema type strings back to Ruby types (inverse of TYPE_MAPPINGS),
|
|
23
|
-
# collapsing the three boolean spellings onto Riffer::Params::Boolean.
|
|
24
18
|
JSON_TYPE_MAPPINGS = {
|
|
25
19
|
"string" => String,
|
|
26
20
|
"integer" => Integer,
|
|
@@ -30,32 +24,22 @@ class Riffer::Params::Param
|
|
|
30
24
|
"object" => Hash,
|
|
31
25
|
}.freeze #: Hash[String, Module]
|
|
32
26
|
|
|
33
|
-
# The parameter name.
|
|
34
27
|
attr_reader :name #: Symbol # @dynamic name
|
|
35
28
|
|
|
36
|
-
# The Ruby type.
|
|
37
29
|
attr_reader :type #: Module # @dynamic type
|
|
38
30
|
|
|
39
|
-
# Whether the parameter is required.
|
|
40
31
|
attr_reader :required #: bool # @dynamic required
|
|
41
32
|
|
|
42
|
-
# The parameter description, if any.
|
|
43
33
|
attr_reader :description #: String? # @dynamic description
|
|
44
34
|
|
|
45
|
-
# Allowed values, if constrained.
|
|
46
35
|
attr_reader :enum #: Array[untyped]? # @dynamic enum
|
|
47
36
|
|
|
48
|
-
# The default value, if any.
|
|
49
37
|
attr_reader :default #: untyped # @dynamic default
|
|
50
38
|
|
|
51
|
-
# Element type for a typed array (+of:+).
|
|
52
39
|
attr_reader :item_type #: Module? # @dynamic item_type
|
|
53
40
|
|
|
54
|
-
# Nested Params for object / array-of-object types.
|
|
55
41
|
attr_reader :nested_params #: Riffer::Params? # @dynamic nested_params
|
|
56
42
|
|
|
57
|
-
# Reconstructs a Param from a single JSON Schema property. Raises
|
|
58
|
-
# Riffer::ArgumentError on a type outside the Params-expressible subset.
|
|
59
43
|
#--
|
|
60
44
|
#: (Symbol, Hash[Symbol, untyped], required: bool) -> Riffer::Params::Param
|
|
61
45
|
def self.from_json_schema(name, schema, required:)
|
|
@@ -88,9 +72,6 @@ class Riffer::Params::Param
|
|
|
88
72
|
end
|
|
89
73
|
private_class_method :resolve_nesting
|
|
90
74
|
|
|
91
|
-
# Resolves a JSON Schema +type+ (or a <tt>[type, "null"]</tt> union) to its
|
|
92
|
-
# Ruby type. Returns a Module — Riffer::Params::Boolean is a Module, not a
|
|
93
|
-
# Class. Raises Riffer::ArgumentError on an unsupported type.
|
|
94
75
|
#--
|
|
95
76
|
#: (untyped) -> Module
|
|
96
77
|
def self.json_type_to_ruby(type)
|
|
@@ -112,8 +93,6 @@ class Riffer::Params::Param
|
|
|
112
93
|
@nested_params = nested_params
|
|
113
94
|
end
|
|
114
95
|
|
|
115
|
-
# Validates that a value matches the expected type.
|
|
116
|
-
#
|
|
117
96
|
#--
|
|
118
97
|
#: (untyped) -> bool
|
|
119
98
|
def valid_type?(value)
|
|
@@ -128,28 +107,20 @@ class Riffer::Params::Param
|
|
|
128
107
|
end
|
|
129
108
|
end
|
|
130
109
|
|
|
131
|
-
# Returns the JSON Schema type name for this parameter.
|
|
132
|
-
#
|
|
133
110
|
#--
|
|
134
111
|
#: () -> String
|
|
135
112
|
def type_name
|
|
136
113
|
TYPE_MAPPINGS[type] || type.to_s.downcase
|
|
137
114
|
end
|
|
138
115
|
|
|
139
|
-
# Converts this parameter to JSON Schema format. When +strict+, optional
|
|
140
|
-
# params are made nullable (<tt>["type", "null"]</tt>) so strict providers
|
|
141
|
-
# distinguish absent from present; optional params with an +enum+ use +anyOf+
|
|
142
|
-
# instead, since providers like Anthropic reject
|
|
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+.
|
|
147
116
|
#--
|
|
148
117
|
#: (?strict: bool) -> Hash[Symbol, untyped]
|
|
149
118
|
def to_json_schema(strict: false)
|
|
150
119
|
validate_strict_shape! if strict
|
|
120
|
+
# Nullable so strict providers can distinguish absent from present.
|
|
151
121
|
nullable = strict && !required
|
|
152
122
|
|
|
123
|
+
# Providers like Anthropic reject a nullable type union combined with enum.
|
|
153
124
|
if nullable && enum
|
|
154
125
|
schema = { anyOf: [{ type: type_name, enum: enum }, { type: "null" }] } #: Hash[Symbol, untyped]
|
|
155
126
|
schema[:description] = description if description
|
|
@@ -196,6 +167,7 @@ class Riffer::Params::Param
|
|
|
196
167
|
#--
|
|
197
168
|
#: () -> void
|
|
198
169
|
def validate_strict_shape!
|
|
170
|
+
# Strict providers reject objects without properties and arrays without items.
|
|
199
171
|
if type == Hash && nested_params.nil?
|
|
200
172
|
raise Riffer::ArgumentError,
|
|
201
173
|
"#{name}: a Hash param requires a block defining its properties under strict schemas"
|