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,24 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Represents the result of evaluating a single scenario.
|
|
5
4
|
class Riffer::Evals::ScenarioResult
|
|
6
|
-
# The input that was evaluated.
|
|
7
5
|
attr_reader :input #: String # @dynamic input
|
|
8
|
-
|
|
9
|
-
# The agent output for this scenario.
|
|
10
6
|
attr_reader :output #: String # @dynamic output
|
|
11
|
-
|
|
12
|
-
# The ground truth used during evaluation.
|
|
13
7
|
attr_reader :ground_truth #: String? # @dynamic ground_truth
|
|
14
|
-
|
|
15
|
-
# Individual evaluation results.
|
|
16
8
|
attr_reader :results #: Array[Riffer::Evals::Result] # @dynamic results
|
|
17
|
-
|
|
18
|
-
# The full message history from the agent conversation.
|
|
19
9
|
attr_reader :messages #: Array[Riffer::Messages::Base] # @dynamic messages
|
|
20
|
-
|
|
21
|
-
# Token usage the agent under test spent generating this scenario's output.
|
|
22
10
|
attr_reader :token_usage #: Riffer::Providers::TokenUsage? # @dynamic token_usage
|
|
23
11
|
|
|
24
12
|
#--
|
|
@@ -32,8 +20,6 @@ class Riffer::Evals::ScenarioResult
|
|
|
32
20
|
@token_usage = token_usage
|
|
33
21
|
end
|
|
34
22
|
|
|
35
|
-
# Returns scores keyed by evaluator class.
|
|
36
|
-
#
|
|
37
23
|
#--
|
|
38
24
|
#: () -> Hash[singleton(Riffer::Evals::Evaluator), Float]
|
|
39
25
|
def scores
|
|
@@ -43,17 +29,12 @@ class Riffer::Evals::ScenarioResult
|
|
|
43
29
|
end
|
|
44
30
|
end
|
|
45
31
|
|
|
46
|
-
# Returns the summed token usage across this scenario's LLM-as-judge
|
|
47
|
-
# evaluators, or nil when none reported usage.
|
|
48
|
-
#
|
|
49
32
|
#--
|
|
50
33
|
#: () -> Riffer::Providers::TokenUsage?
|
|
51
34
|
def evaluator_token_usage
|
|
52
35
|
results.filter_map(&:token_usage).reduce(:+)
|
|
53
36
|
end
|
|
54
37
|
|
|
55
|
-
# Returns a hash representation of the scenario result.
|
|
56
|
-
#
|
|
57
38
|
#--
|
|
58
39
|
#: () -> Hash[Symbol, untyped]
|
|
59
40
|
def to_h
|
|
@@ -33,9 +33,8 @@ class Riffer::Files::Downloader
|
|
|
33
33
|
http.open_timeout = timeout
|
|
34
34
|
http.read_timeout = timeout
|
|
35
35
|
|
|
36
|
-
# request_get without a block reads
|
|
37
|
-
#
|
|
38
|
-
# block it yields to, where the body hasn't been consumed yet.
|
|
36
|
+
# request_get without a block reads the whole body before returning, so the
|
|
37
|
+
# size cap has to run inside the block, before the body is consumed.
|
|
39
38
|
redirect_location = nil #: String?
|
|
40
39
|
content = nil #: String?
|
|
41
40
|
|
|
@@ -14,10 +14,6 @@ class Riffer::Files::Resolver
|
|
|
14
14
|
@config = Riffer.config.files
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
# Resolves every file in every User message in place - downloading,
|
|
18
|
-
# verifying, and caching as the provider's capability and each file's
|
|
19
|
-
# sha256 require. Raises Riffer::FileError on any file that can't be
|
|
20
|
-
# resolved
|
|
21
17
|
#: (Array[Riffer::Messages::Base]) -> void
|
|
22
18
|
def resolve!(messages)
|
|
23
19
|
files = messages.flat_map do |message|
|
|
@@ -51,6 +47,8 @@ class Riffer::Files::Resolver
|
|
|
51
47
|
|
|
52
48
|
case delivery
|
|
53
49
|
when :url
|
|
50
|
+
# The request still sends the URL, never these bytes, so caching them
|
|
51
|
+
# would hold memory nothing reads and let later turns skip re-verifying.
|
|
54
52
|
download!(file, cache: false) if file.sha256
|
|
55
53
|
when :base64
|
|
56
54
|
file.data ? verify_inline!(file) : download!(file, cache: :base64)
|
|
@@ -69,9 +67,6 @@ class Riffer::Files::Resolver
|
|
|
69
67
|
verify_bytes!(file.data_bytes, file.sha256)
|
|
70
68
|
end
|
|
71
69
|
|
|
72
|
-
# +cache:+ is false for a :url-delivery provider verifying a sha256 — the
|
|
73
|
-
# request still sends the URL, never the downloaded bytes, so caching them
|
|
74
|
-
# would hold memory nothing reads and let later turns skip re-verifying.
|
|
75
70
|
#: (Riffer::Messages::User::FilePart, cache: (false | Symbol)) -> void
|
|
76
71
|
def download!(file, cache:)
|
|
77
72
|
raise Riffer::FileDownloadsDisabledError, "File attachments are disabled" unless @config.allow_downloads
|
data/lib/riffer/guardrail.rb
CHANGED
|
@@ -1,38 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Base class for guardrails that process input and output in the agent pipeline.
|
|
5
|
-
#
|
|
6
|
-
# class MyGuardrail < Riffer::Guardrail
|
|
7
|
-
# def process_input(messages, context:)
|
|
8
|
-
# # Return pass(messages), transform(modified_messages), or block(reason)
|
|
9
|
-
# pass(messages)
|
|
10
|
-
# end
|
|
11
|
-
#
|
|
12
|
-
# def process_output(response, messages:, context:)
|
|
13
|
-
# # Return pass(response), transform(modified_response), or block(reason)
|
|
14
|
-
# pass(response)
|
|
15
|
-
# end
|
|
16
|
-
# end
|
|
17
4
|
class Riffer::Guardrail
|
|
18
|
-
# Processes input messages before they're sent to the LLM; override in
|
|
19
|
-
# subclasses.
|
|
20
5
|
#--
|
|
21
6
|
#: (Array[Riffer::Messages::Base], context: untyped) -> Riffer::Guardrails::Result
|
|
22
7
|
def process_input(messages, context:)
|
|
23
8
|
pass(messages)
|
|
24
9
|
end
|
|
25
10
|
|
|
26
|
-
# Processes the output response after it's received from the LLM; override in
|
|
27
|
-
# subclasses.
|
|
28
11
|
#--
|
|
29
12
|
#: (Riffer::Messages::Assistant, messages: Array[Riffer::Messages::Base], context: untyped) -> Riffer::Guardrails::Result
|
|
30
13
|
def process_output(response, messages:, context:)
|
|
31
14
|
pass(response)
|
|
32
15
|
end
|
|
33
16
|
|
|
34
|
-
#
|
|
35
|
-
#
|
|
17
|
+
# Used as the tracing span suffix and the <tt>riffer.guardrail.name</tt>
|
|
18
|
+
# attribute.
|
|
36
19
|
#--
|
|
37
20
|
#: () -> String
|
|
38
21
|
def name
|
|
@@ -41,21 +24,18 @@ class Riffer::Guardrail
|
|
|
41
24
|
|
|
42
25
|
protected
|
|
43
26
|
|
|
44
|
-
# Creates a pass result that continues with unchanged data.
|
|
45
27
|
#--
|
|
46
28
|
#: (untyped) -> Riffer::Guardrails::Result
|
|
47
29
|
def pass(data)
|
|
48
30
|
Riffer::Guardrails::Result.pass(data)
|
|
49
31
|
end
|
|
50
32
|
|
|
51
|
-
# Creates a transform result that continues with transformed data.
|
|
52
33
|
#--
|
|
53
34
|
#: (untyped) -> Riffer::Guardrails::Result
|
|
54
35
|
def transform(data)
|
|
55
36
|
Riffer::Guardrails::Result.transform(data)
|
|
56
37
|
end
|
|
57
38
|
|
|
58
|
-
# Creates a block result that halts execution.
|
|
59
39
|
#--
|
|
60
40
|
#: (String, ?metadata: Hash[Symbol, untyped]?) -> Riffer::Guardrails::Result
|
|
61
41
|
def block(reason, metadata: nil)
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Records a guardrail transformation event.
|
|
5
4
|
class Riffer::Guardrails::Modification
|
|
6
|
-
# The guardrail class that transformed data.
|
|
7
5
|
attr_reader :guardrail #: singleton(Riffer::Guardrail) # @dynamic guardrail
|
|
8
|
-
|
|
9
|
-
# The phase when the transformation occurred (:before or :after).
|
|
10
6
|
attr_reader :phase #: Symbol # @dynamic phase
|
|
11
|
-
|
|
12
|
-
# The indices of messages that were changed.
|
|
13
7
|
attr_reader :message_indices #: Array[Integer] # @dynamic message_indices
|
|
14
8
|
|
|
15
9
|
#--
|
|
@@ -20,8 +14,6 @@ class Riffer::Guardrails::Modification
|
|
|
20
14
|
@message_indices = message_indices
|
|
21
15
|
end
|
|
22
16
|
|
|
23
|
-
# Converts the modification to a hash.
|
|
24
|
-
#
|
|
25
17
|
#--
|
|
26
18
|
#: () -> Hash[Symbol, untyped]
|
|
27
19
|
def to_h
|
|
@@ -1,36 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Represents the result of a guardrail execution: +pass+ (continue unchanged),
|
|
5
|
-
# +transform+ (continue with changed data), or +block+ (halt with a reason).
|
|
6
4
|
class Riffer::Guardrails::Result
|
|
7
5
|
TYPES = %i[pass transform block].freeze #: Array[Symbol]
|
|
8
6
|
|
|
9
|
-
# The result type (:pass, :transform, or :block).
|
|
10
7
|
attr_reader :type #: Symbol # @dynamic type
|
|
11
|
-
|
|
12
|
-
# The data (for pass/transform) or reason (for block).
|
|
13
8
|
attr_reader :data #: untyped # @dynamic data
|
|
14
|
-
|
|
15
|
-
# Optional metadata for block results.
|
|
16
9
|
attr_reader :metadata #: Hash[Symbol, untyped]? # @dynamic metadata
|
|
17
10
|
|
|
18
11
|
class << self
|
|
19
|
-
# Creates a pass result that continues with unchanged data.
|
|
20
12
|
#--
|
|
21
13
|
#: (untyped) -> Riffer::Guardrails::Result
|
|
22
14
|
def pass(data)
|
|
23
15
|
new(:pass, data)
|
|
24
16
|
end
|
|
25
17
|
|
|
26
|
-
# Creates a transform result that continues with transformed data.
|
|
27
18
|
#--
|
|
28
19
|
#: (untyped) -> Riffer::Guardrails::Result
|
|
29
20
|
def transform(data)
|
|
30
21
|
new(:transform, data)
|
|
31
22
|
end
|
|
32
23
|
|
|
33
|
-
# Creates a block result that halts execution.
|
|
34
24
|
#--
|
|
35
25
|
#: (String, ?metadata: Hash[Symbol, untyped]?) -> Riffer::Guardrails::Result
|
|
36
26
|
def block(reason, metadata: nil)
|
|
@@ -38,7 +28,6 @@ class Riffer::Guardrails::Result
|
|
|
38
28
|
end
|
|
39
29
|
end
|
|
40
30
|
|
|
41
|
-
# Raises Riffer::ArgumentError if +type+ is not :pass, :transform, or :block.
|
|
42
31
|
#--
|
|
43
32
|
#: (Symbol, untyped, ?metadata: Hash[Symbol, untyped]?) -> void
|
|
44
33
|
def initialize(type, data, metadata: nil)
|
|
@@ -49,24 +38,18 @@ class Riffer::Guardrails::Result
|
|
|
49
38
|
@metadata = metadata
|
|
50
39
|
end
|
|
51
40
|
|
|
52
|
-
# Returns true if this is a pass result.
|
|
53
|
-
#
|
|
54
41
|
#--
|
|
55
42
|
#: () -> bool
|
|
56
43
|
def pass?
|
|
57
44
|
type == :pass
|
|
58
45
|
end
|
|
59
46
|
|
|
60
|
-
# Returns true if this is a transform result.
|
|
61
|
-
#
|
|
62
47
|
#--
|
|
63
48
|
#: () -> bool
|
|
64
49
|
def transform?
|
|
65
50
|
type == :transform
|
|
66
51
|
end
|
|
67
52
|
|
|
68
|
-
# Returns true if this is a block result.
|
|
69
|
-
#
|
|
70
53
|
#--
|
|
71
54
|
#: () -> bool
|
|
72
55
|
def block?
|
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Executes guardrails sequentially, passing each one's output to the next; if
|
|
5
|
-
# any blocks, execution stops and a tripwire is returned.
|
|
6
4
|
class Riffer::Guardrails::Runner
|
|
7
|
-
# The guardrail configs to execute.
|
|
8
5
|
attr_reader :guardrail_configs #: Array[Hash[Symbol, untyped]] # @dynamic guardrail_configs
|
|
9
6
|
|
|
10
|
-
# The execution phase (:before or :after).
|
|
11
7
|
attr_reader :phase #: Symbol # @dynamic phase
|
|
12
8
|
|
|
13
|
-
# The context passed to guardrails.
|
|
14
9
|
attr_reader :context #: untyped # @dynamic context
|
|
15
10
|
|
|
16
|
-
# The normalized per-call tags, stamped as +riffer.tag.*+ on guardrail spans.
|
|
17
11
|
attr_reader :tags #: Hash[String, String] # @dynamic tags
|
|
18
12
|
|
|
19
13
|
#--
|
|
@@ -25,9 +19,6 @@ class Riffer::Guardrails::Runner
|
|
|
25
19
|
@tags = tags
|
|
26
20
|
end
|
|
27
21
|
|
|
28
|
-
# Runs the guardrails sequentially. For the +:before+ phase +data+ is the
|
|
29
|
-
# messages array; for +:after+ it's the response (and +messages+ must be
|
|
30
|
-
# provided).
|
|
31
22
|
#--
|
|
32
23
|
#: (untyped, ?messages: Array[Riffer::Messages::Base]?) -> [untyped, Riffer::Guardrails::Tripwire?, Array[Riffer::Guardrails::Modification]]
|
|
33
24
|
def run(data, messages: nil)
|
|
@@ -124,12 +115,12 @@ class Riffer::Guardrails::Runner
|
|
|
124
115
|
}.merge(tags.transform_keys { |key| "riffer.tag.#{key}" })
|
|
125
116
|
end
|
|
126
117
|
|
|
127
|
-
# A block is a handled outcome, so its span status stays unset — an error
|
|
128
|
-
# span status is reserved for a raised exception.
|
|
129
118
|
#--
|
|
130
119
|
#: ((Riffer::Tracing::Otel::Span | Riffer::Tracing::NoOp::Span), Riffer::Guardrails::Result) -> void
|
|
131
120
|
def record_guardrail_outcome(span, result)
|
|
132
121
|
span.set_attribute("riffer.guardrail.action", result.type.to_s)
|
|
122
|
+
# A block is a handled outcome, so span status stays unset — error status
|
|
123
|
+
# is reserved for a raised exception.
|
|
133
124
|
span.set_attribute("riffer.tripwire.reason", result.data) if result.block?
|
|
134
125
|
end
|
|
135
126
|
end
|
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Captures information about a blocked guardrail execution.
|
|
5
4
|
class Riffer::Guardrails::Tripwire
|
|
6
5
|
PHASES = Riffer::Guardrails::PHASES #: Array[Symbol]
|
|
7
6
|
|
|
8
|
-
# The reason for blocking.
|
|
9
7
|
attr_reader :reason #: String # @dynamic reason
|
|
10
8
|
|
|
11
|
-
# The guardrail class that triggered the block.
|
|
12
9
|
attr_reader :guardrail #: singleton(Riffer::Guardrail) # @dynamic guardrail
|
|
13
10
|
|
|
14
|
-
# The phase when the block occurred (:before or :after).
|
|
15
11
|
attr_reader :phase #: Symbol # @dynamic phase
|
|
16
12
|
|
|
17
|
-
# Optional metadata about the block.
|
|
18
13
|
attr_reader :metadata #: Hash[Symbol, untyped]? # @dynamic metadata
|
|
19
14
|
|
|
20
|
-
# Raises Riffer::ArgumentError if +phase+ is invalid.
|
|
21
15
|
#--
|
|
22
16
|
#: (reason: String, guardrail: singleton(Riffer::Guardrail), phase: Symbol, ?metadata: Hash[Symbol, untyped]?) -> void
|
|
23
17
|
def initialize(reason:, guardrail:, phase:, metadata: nil)
|
|
@@ -29,8 +23,6 @@ class Riffer::Guardrails::Tripwire
|
|
|
29
23
|
@metadata = metadata
|
|
30
24
|
end
|
|
31
25
|
|
|
32
|
-
# Converts the tripwire to a hash.
|
|
33
|
-
#
|
|
34
26
|
#--
|
|
35
27
|
#: () -> Hash[Symbol, untyped]
|
|
36
28
|
def to_h
|
data/lib/riffer/guardrails.rb
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Coercion for boolean-ish configuration values.
|
|
5
4
|
module Riffer::Helpers::Boolean
|
|
6
5
|
extend self
|
|
7
6
|
|
|
8
|
-
# Coerces +value+ to a boolean so an env-var +"false"+ (truthy in Ruby)
|
|
9
|
-
# doesn't silently read as +true+. Raises Riffer::ArgumentError on an
|
|
10
|
-
# unrecognized value, naming +attribute+ in the message.
|
|
11
7
|
#--
|
|
12
8
|
#: (untyped, attribute: String) -> bool
|
|
13
9
|
def coerce(value, attribute:)
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Resolves the Proc-or-value idiom.
|
|
5
4
|
module Riffer::Helpers::CallOrValue
|
|
6
5
|
extend self
|
|
7
6
|
|
|
8
|
-
# Calls +thing+ when it's a Proc (passing +context+ if its arity is non-zero),
|
|
9
|
-
# returns it unchanged otherwise, or +default+ when +nil+.
|
|
10
7
|
#--
|
|
11
8
|
#: (untyped, ?context: untyped, ?default: untyped) -> untyped
|
|
12
9
|
def resolve(thing, context: nil, default: nil)
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Copying for the nested Hashes and Arrays a configuration object holds.
|
|
5
4
|
module Riffer::Helpers::DeepDup
|
|
6
5
|
extend self
|
|
7
6
|
|
|
8
|
-
# Returns +value+ with every Hash and Array rebuilt, so a copy shares no
|
|
9
|
-
# collection with its source.
|
|
10
|
-
#
|
|
11
|
-
# Anything else is returned as-is, which a Class or a Proc needs: +Class#dup+
|
|
12
|
-
# answers a new anonymous class. One source collection maps to one copy, so
|
|
13
|
-
# references shared within +value+ stay shared in the result — an +:around+
|
|
14
|
-
# guardrail registered under two phases is still one registration afterwards.
|
|
15
7
|
#--
|
|
16
8
|
#: (untyped) -> untyped
|
|
17
9
|
def call(value)
|
|
10
|
+
# References shared within +value+ stay shared in the copy — an +:around+
|
|
11
|
+
# guardrail registered under two phases is still one registration.
|
|
18
12
|
seen = {} #: Hash[untyped, untyped]
|
|
19
13
|
|
|
20
14
|
rebuild(value, seen.compare_by_identity)
|
|
@@ -36,6 +30,7 @@ module Riffer::Helpers::DeepDup
|
|
|
36
30
|
copy = seen[value] = []
|
|
37
31
|
value.each { |entry| copy << rebuild(entry, seen) }
|
|
38
32
|
copy
|
|
33
|
+
# Class#dup answers a new anonymous class, so non-collections stay as-is.
|
|
39
34
|
else value
|
|
40
35
|
end
|
|
41
36
|
end
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Lazy-loads gem dependencies — used by providers to load required gems only
|
|
5
|
-
# when needed.
|
|
6
4
|
module Riffer::Helpers::Dependencies
|
|
7
5
|
extend self
|
|
8
6
|
|
|
9
|
-
# Raised when a required gem cannot be loaded.
|
|
10
7
|
class LoadError < ::LoadError; end
|
|
11
8
|
|
|
12
|
-
# Requires a gem by name; raises LoadError if it isn't installed.
|
|
13
9
|
#--
|
|
14
10
|
#: (String) -> true
|
|
15
11
|
def depends_on(gem_name)
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Helper module for deriving snake_case identifiers from class names.
|
|
5
4
|
module Riffer::Helpers::Identifier
|
|
6
5
|
extend self
|
|
7
6
|
|
|
8
|
-
# Derives a snake_case identifier from a class name string.
|
|
9
|
-
#
|
|
10
7
|
#--
|
|
11
8
|
#: (String?) -> String
|
|
12
9
|
def derive(class_name)
|
|
@@ -18,11 +15,6 @@ module Riffer::Helpers::Identifier
|
|
|
18
15
|
downcase
|
|
19
16
|
end
|
|
20
17
|
|
|
21
|
-
# Derives and memoizes the identifier for a class or module. Anonymous
|
|
22
|
-
# classes return "" without caching, so a class named later still derives its
|
|
23
|
-
# real identifier — a guard that must travel with the cache, so callers never
|
|
24
|
-
# memoize their own.
|
|
25
|
-
#
|
|
26
18
|
#--
|
|
27
19
|
#: (Module) -> String
|
|
28
20
|
def for(klass)
|
|
@@ -30,6 +22,8 @@ module Riffer::Helpers::Identifier
|
|
|
30
22
|
return cached if cached
|
|
31
23
|
|
|
32
24
|
real_name = real_name(klass)
|
|
25
|
+
# Anonymous classes skip the cache so a class named later still derives its
|
|
26
|
+
# real identifier; callers must not memoize their own for the same reason.
|
|
33
27
|
return "" if real_name.nil?
|
|
34
28
|
|
|
35
29
|
derived = derive(real_name)
|
|
@@ -37,13 +31,10 @@ module Riffer::Helpers::Identifier
|
|
|
37
31
|
derived
|
|
38
32
|
end
|
|
39
33
|
|
|
40
|
-
# Returns the class-path name of a class or module, or +nil+ when anonymous.
|
|
41
|
-
# Tool classes shadow Module#name with the identifier DSL, so the real name
|
|
42
|
-
# must come from Module's own implementation.
|
|
43
|
-
#
|
|
44
34
|
#--
|
|
45
35
|
#: (Module) -> String?
|
|
46
36
|
def real_name(klass)
|
|
37
|
+
# Tool classes shadow Module#name with the identifier DSL.
|
|
47
38
|
Module.instance_method(:name).bind_call(klass) #: String?
|
|
48
39
|
end
|
|
49
40
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Riffer::Helpers::Validate
|
|
5
|
+
extend self
|
|
6
|
+
|
|
7
|
+
# The error names the class, not the value, so a misassigned secret never
|
|
8
|
+
# lands in a log.
|
|
9
|
+
#--
|
|
10
|
+
#: (untyped, attribute: String) -> String?
|
|
11
|
+
def optional_string(value, attribute:)
|
|
12
|
+
return value if value.nil? || value.is_a?(String)
|
|
13
|
+
|
|
14
|
+
raise Riffer::ArgumentError, "#{attribute} must be a String or nil, got #{value.class}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#--
|
|
18
|
+
#: (untyped, attribute: String) -> Integer
|
|
19
|
+
def positive_integer(value, attribute:)
|
|
20
|
+
return value if value.is_a?(Integer) && value.positive?
|
|
21
|
+
|
|
22
|
+
raise Riffer::ArgumentError, "#{attribute} must be a positive integer"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#--
|
|
26
|
+
#: (untyped, attribute: String) -> Riffer::Runner
|
|
27
|
+
def runner(value, attribute:)
|
|
28
|
+
return value if value.is_a?(Riffer::Runner)
|
|
29
|
+
|
|
30
|
+
raise Riffer::ArgumentError, "#{attribute} must be a Riffer::Runner instance"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#--
|
|
34
|
+
#: (untyped, attribute: String) -> String
|
|
35
|
+
def model_id(value, attribute:)
|
|
36
|
+
segments = value.to_s.split("/", 2)
|
|
37
|
+
valid = value.is_a?(String) && segments.length == 2 && segments.none? { |segment| segment.strip.empty? }
|
|
38
|
+
return value if valid
|
|
39
|
+
|
|
40
|
+
raise Riffer::ArgumentError, "#{attribute} must be in \"provider/model\" form, got #{value.inspect}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Wraps MCP-generated tool classes so +tools/call+ resolves
|
|
5
|
-
# +Riffer.config.mcp.credentials+ per invocation, copying metadata from the
|
|
6
|
-
# inner class at wrap time.
|
|
7
4
|
module Riffer::Mcp::AuthenticatedTool
|
|
8
5
|
extend self
|
|
9
6
|
|
|
10
|
-
# Returns one wrapper class per inner tool, sharing +manifest+ and +matched_tags+.
|
|
11
|
-
#
|
|
12
7
|
#--
|
|
13
8
|
#: (Array[singleton(Riffer::Mcp::Tool)], Riffer::Mcp::Manifest, Array[Symbol]) -> Array[singleton(Riffer::Mcp::Tool)]
|
|
14
9
|
def wrap_all(tool_classes, manifest, matched_tags)
|
|
@@ -23,8 +18,7 @@ module Riffer::Mcp::AuthenticatedTool
|
|
|
23
18
|
tags = matched_tags
|
|
24
19
|
|
|
25
20
|
# steep does not model Class.new's class_eval semantics — the block body
|
|
26
|
-
# typechecks against the enclosing module
|
|
27
|
-
# define_method bodies are unresolvable.
|
|
21
|
+
# typechecks against the enclosing module.
|
|
28
22
|
Class.new(Riffer::Mcp::Tool) do
|
|
29
23
|
# steep:ignore:start
|
|
30
24
|
@identifier = inner.identifier
|
|
@@ -32,11 +26,9 @@ module Riffer::Mcp::AuthenticatedTool
|
|
|
32
26
|
@input_schema = inner.parameters_schema
|
|
33
27
|
@mcp_server_tool_name = inner.mcp_server_tool_name
|
|
34
28
|
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# requires a follow-up investigation to determine how to invalidate failing
|
|
39
|
-
# clients.
|
|
29
|
+
# A fresh client per +tools/call+ keeps credentials-proc headers current at
|
|
30
|
+
# the cost of connection churn; a per-headers cache would first need a way
|
|
31
|
+
# to invalidate failing clients.
|
|
40
32
|
define_method(:build_call_client) do |endpoint, headers|
|
|
41
33
|
Riffer::Mcp::Client.new(endpoint: endpoint, headers: headers)
|
|
42
34
|
end
|
data/lib/riffer/mcp/client.rb
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Thin wrapper around the MCP Ruby SDK client (mcp gem v0.8+). Resolves headers
|
|
5
|
-
# (if a Proc) once at init, then provides +tools_list+ / +tools_call+ — used for
|
|
6
|
-
# discovery and for +tools/call+ when no +credentials+ proc is configured.
|
|
7
4
|
class Riffer::Mcp::Client
|
|
8
5
|
# @rbs @client: untyped
|
|
9
6
|
|
|
@@ -20,8 +17,6 @@ class Riffer::Mcp::Client
|
|
|
20
17
|
end
|
|
21
18
|
end
|
|
22
19
|
|
|
23
|
-
# Returns tool definition hashes with +:name+, +:description+, and
|
|
24
|
-
# +:input_schema+ keys.
|
|
25
20
|
#--
|
|
26
21
|
#: () -> Array[Hash[Symbol, untyped]]
|
|
27
22
|
def tools_list
|
|
@@ -34,8 +29,6 @@ class Riffer::Mcp::Client
|
|
|
34
29
|
end
|
|
35
30
|
end
|
|
36
31
|
|
|
37
|
-
# Calls a tool on the MCP server and returns joined text content from the response.
|
|
38
|
-
#
|
|
39
32
|
#--
|
|
40
33
|
#: (String, ?Hash[untyped, untyped]) -> String
|
|
41
34
|
def tools_call(name, arguments = {})
|
data/lib/riffer/mcp/manifest.rb
CHANGED
|
@@ -3,22 +3,18 @@
|
|
|
3
3
|
|
|
4
4
|
require "uri"
|
|
5
5
|
|
|
6
|
-
# Holds the configuration for a single MCP server.
|
|
7
6
|
class Riffer::Mcp::Manifest
|
|
8
|
-
# Identifier used as the registration key and generated-agent identifier.
|
|
9
7
|
attr_reader :name #: String # @dynamic name
|
|
10
8
|
|
|
11
|
-
# Tags for matching +use_mcp+.
|
|
12
9
|
attr_reader :tags #: Array[Symbol] # @dynamic tags
|
|
13
10
|
|
|
14
|
-
# HTTPS URL passed to the MCP transport.
|
|
15
11
|
attr_reader :endpoint #: String # @dynamic endpoint
|
|
16
12
|
|
|
17
|
-
#
|
|
13
|
+
# Resolved once, when building the discovery client.
|
|
18
14
|
attr_reader :discovery_headers #: (Hash[String, untyped] | ::Proc)? # @dynamic discovery_headers
|
|
19
15
|
|
|
20
|
-
#
|
|
21
|
-
#
|
|
16
|
+
# +:global+, +:tenant+, or +:user+: whether invocation credentials depend on
|
|
17
|
+
# tenant/user keys in +context+.
|
|
22
18
|
attr_reader :credentials_scope #: Symbol? # @dynamic credentials_scope
|
|
23
19
|
|
|
24
20
|
# Raises Riffer::ArgumentError unless +name+ is present and +endpoint+ is a
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Per-server state managed by Riffer::Mcp::Registry — discovers tools via
|
|
5
|
-
# +tools/list+ and generates tool classes when a server is registered.
|
|
6
4
|
class Riffer::Mcp::Registration
|
|
7
5
|
# @rbs @cancelled: bool
|
|
8
6
|
# @rbs @tools: Array[singleton(Riffer::Mcp::Tool)]
|
|
9
7
|
# @rbs @mutex: Thread::Mutex
|
|
10
8
|
|
|
11
|
-
# The manifest that describes this server.
|
|
12
9
|
attr_reader :manifest #: Riffer::Mcp::Manifest # @dynamic manifest
|
|
13
10
|
|
|
14
|
-
# Generated Riffer::Mcp::Tool subclasses.
|
|
15
|
-
#
|
|
16
11
|
#--
|
|
17
12
|
#: () -> Array[singleton(Riffer::Mcp::Tool)]
|
|
18
13
|
def tools
|
|
@@ -29,17 +24,12 @@ class Riffer::Mcp::Registration
|
|
|
29
24
|
run_discovery
|
|
30
25
|
end
|
|
31
26
|
|
|
32
|
-
# Retires this registration, preventing in-flight discovery from publishing
|
|
33
|
-
# state.
|
|
34
|
-
#
|
|
35
27
|
#--
|
|
36
28
|
#: () -> void
|
|
37
29
|
def retire!
|
|
38
30
|
@mutex.synchronize { @cancelled = true }
|
|
39
31
|
end
|
|
40
32
|
|
|
41
|
-
# Returns true if this registration has been retired.
|
|
42
|
-
#
|
|
43
33
|
#--
|
|
44
34
|
#: () -> bool
|
|
45
35
|
def retired?
|
data/lib/riffer/mcp/registry.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Thread-safe global store for MCP server registrations, keyed by manifest name.
|
|
5
4
|
module Riffer::Mcp::Registry
|
|
6
5
|
extend self
|
|
7
6
|
|
|
@@ -11,8 +10,6 @@ module Riffer::Mcp::Registry
|
|
|
11
10
|
@mutex = Mutex.new
|
|
12
11
|
@store = {} #: Hash[String, Riffer::Mcp::Registration]
|
|
13
12
|
|
|
14
|
-
# Registers an MCP server and starts async tool discovery, replacing any
|
|
15
|
-
# existing registration with the same name.
|
|
16
13
|
#--
|
|
17
14
|
#: ((Hash[Symbol, untyped] | Riffer::Mcp::Manifest)) -> Riffer::Mcp::Registration
|
|
18
15
|
def register(manifest_or_hash)
|
|
@@ -33,8 +30,6 @@ module Riffer::Mcp::Registry
|
|
|
33
30
|
registration
|
|
34
31
|
end
|
|
35
32
|
|
|
36
|
-
# Removes a registration by name.
|
|
37
|
-
#
|
|
38
33
|
#--
|
|
39
34
|
#: ((String | Symbol)) -> void
|
|
40
35
|
def unregister(name)
|
|
@@ -42,16 +37,12 @@ module Riffer::Mcp::Registry
|
|
|
42
37
|
removed&.retire!
|
|
43
38
|
end
|
|
44
39
|
|
|
45
|
-
# Returns a frozen snapshot of all current registrations.
|
|
46
|
-
#
|
|
47
40
|
#--
|
|
48
41
|
#: () -> Hash[String, Riffer::Mcp::Registration]
|
|
49
42
|
def registrations
|
|
50
43
|
@mutex.synchronize { @store.dup.freeze }
|
|
51
44
|
end
|
|
52
45
|
|
|
53
|
-
# Returns all registrations whose manifest tags intersect the given tags
|
|
54
|
-
# (normalized to symbols).
|
|
55
46
|
#--
|
|
56
47
|
#: (Array[Symbol]) -> Array[Riffer::Mcp::Registration]
|
|
57
48
|
def find_by_tags(tags)
|