riffer 0.40.0 → 0.42.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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/{AGENTS.md → .claude/CLAUDE.md} +0 -8
  3. data/.claude/rules/comments.md +13 -0
  4. data/.claude/rules/rbs-inline.md +93 -0
  5. data/.release-please-manifest.json +1 -1
  6. data/CHANGELOG.md +22 -0
  7. data/README.md +20 -24
  8. data/docs/AGENTS.md +24 -14
  9. data/docs/AGENT_LIFECYCLE.md +2 -2
  10. data/docs/CONFIGURATION.md +105 -21
  11. data/docs/SERIALIZATION.md +4 -3
  12. data/docs/TOOLS.md +18 -1
  13. data/docs/providers/AMAZON_BEDROCK.md +14 -8
  14. data/docs/providers/ANTHROPIC.md +9 -7
  15. data/docs/providers/AZURE_OPENAI.md +12 -12
  16. data/docs/providers/CUSTOM_PROVIDERS.md +32 -16
  17. data/docs/providers/GEMINI.md +28 -5
  18. data/docs/providers/OPENAI.md +21 -5
  19. data/docs/providers/OPENROUTER.md +11 -6
  20. data/docs/providers/PROVIDERS.md +16 -1
  21. data/lib/riffer/agent/config.rb +0 -6
  22. data/lib/riffer/agent/run.rb +2 -4
  23. data/lib/riffer/agent/serializer.rb +0 -2
  24. data/lib/riffer/agent.rb +4 -26
  25. data/lib/riffer/config.rb +6 -6
  26. data/lib/riffer/evals/judge.rb +3 -5
  27. data/lib/riffer/guardrail.rb +1 -1
  28. data/lib/riffer/helpers/identifier.rb +41 -0
  29. data/lib/riffer/providers/amazon_bedrock.rb +31 -19
  30. data/lib/riffer/providers/anthropic.rb +23 -8
  31. data/lib/riffer/providers/azure_open_ai.rb +19 -11
  32. data/lib/riffer/providers/base.rb +32 -1
  33. data/lib/riffer/providers/gemini/client.rb +120 -0
  34. data/lib/riffer/providers/gemini.rb +13 -62
  35. data/lib/riffer/providers/mock.rb +5 -4
  36. data/lib/riffer/providers/open_ai.rb +23 -8
  37. data/lib/riffer/providers/open_router.rb +23 -9
  38. data/lib/riffer/registrable.rb +81 -0
  39. data/lib/riffer/tool.rb +1 -0
  40. data/lib/riffer/tools/toolable.rb +2 -3
  41. data/lib/riffer/version.rb +1 -1
  42. data/lib/riffer.rb +3 -0
  43. data/sig/_private/riffer/providers/amazon_bedrock.rbs +4 -2
  44. data/sig/_private/riffer/providers/anthropic.rbs +4 -2
  45. data/sig/_private/riffer/providers/gemini.rbs +7 -0
  46. data/sig/_private/riffer/providers/open_ai.rbs +4 -2
  47. data/sig/_private/riffer/providers/open_router.rbs +4 -2
  48. data/sig/generated/riffer/agent/config.rbs +1 -5
  49. data/sig/generated/riffer/agent.rbs +2 -18
  50. data/sig/generated/riffer/config.rbs +25 -15
  51. data/sig/generated/riffer/evals/judge.rbs +2 -4
  52. data/sig/generated/riffer/helpers/identifier.rbs +19 -0
  53. data/sig/generated/riffer/providers/amazon_bedrock.rbs +13 -2
  54. data/sig/generated/riffer/providers/anthropic.rbs +13 -2
  55. data/sig/generated/riffer/providers/azure_open_ai.rbs +14 -4
  56. data/sig/generated/riffer/providers/base.rbs +22 -0
  57. data/sig/generated/riffer/providers/gemini/client.rbs +65 -0
  58. data/sig/generated/riffer/providers/gemini.rbs +7 -23
  59. data/sig/generated/riffer/providers/mock.rbs +4 -3
  60. data/sig/generated/riffer/providers/open_ai.rbs +13 -2
  61. data/sig/generated/riffer/providers/open_router.rbs +16 -3
  62. data/sig/generated/riffer/registrable.rbs +51 -0
  63. data/sig/generated/riffer/tool.rbs +2 -0
  64. data/sig/generated/riffer/tools/toolable.rbs +3 -1
  65. data/sig/generated/riffer.rbs +4 -0
  66. data/sig/manual/riffer/agent.rbs +7 -0
  67. data/sig/manual/riffer/helpers/identifier.rbs +5 -0
  68. data/sig/manual/riffer/tool.rbs +7 -0
  69. metadata +14 -11
  70. data/.agents/architecture.md +0 -265
  71. data/.agents/code-style.md +0 -110
  72. data/.agents/providers.md +0 -45
  73. data/.agents/rbs-inline.md +0 -169
  74. data/.agents/testing.md +0 -60
  75. data/CLAUDE.md +0 -1
  76. data/lib/riffer/helpers/class_name_converter.rb +0 -22
  77. data/sig/generated/riffer/helpers/class_name_converter.rbs +0 -12
  78. data/sig/manual/riffer/helpers/class_name_converter.rbs +0 -5
@@ -1,4 +1,6 @@
1
- # `@client` is typed against the SDK, so this ivar stub stays in _private (never ships)
1
+ # `client` is typed against the SDK, so this narrowing stays in _private (never ships)
2
2
  class Riffer::Providers::AmazonBedrock
3
- @client: ::Aws::BedrockRuntime::Client
3
+ private
4
+
5
+ def client: () -> ::Aws::BedrockRuntime::Client
4
6
  end
@@ -1,4 +1,6 @@
1
- # `@client` is typed against the SDK, so this ivar stub stays in _private (never ships)
1
+ # `client` is typed against the SDK, so this narrowing stays in _private (never ships)
2
2
  class Riffer::Providers::Anthropic
3
- @client: ::Anthropic::Client
3
+ private
4
+
5
+ def client: () -> ::Anthropic::Client
4
6
  end
@@ -0,0 +1,7 @@
1
+ # Narrows the private `client` to riffer's Gemini transport, mirroring the
2
+ # SDK-typed narrowings alongside it.
3
+ class Riffer::Providers::Gemini
4
+ private
5
+
6
+ def client: () -> Riffer::Providers::Gemini::Client
7
+ end
@@ -1,4 +1,6 @@
1
- # `@client` is typed against the SDK, so this ivar stub stays in _private (never ships)
1
+ # `client` is typed against the SDK, so this narrowing stays in _private (never ships)
2
2
  class Riffer::Providers::OpenAI
3
- @client: ::OpenAI::Client
3
+ private
4
+
5
+ def client: () -> ::OpenAI::Client
4
6
  end
@@ -1,4 +1,6 @@
1
- # `@client` is typed against the SDK, so this ivar stub stays in _private (never ships)
1
+ # `client` is typed against the SDK, so this narrowing stays in _private (never ships)
2
2
  class Riffer::Providers::OpenRouter
3
- @client: ::OpenAI::Client
3
+ private
4
+
5
+ def client: () -> ::OpenAI::Client
4
6
  end
@@ -15,9 +15,6 @@ class Riffer::Agent::Config
15
15
  # The configured instructions.
16
16
  attr_reader instructions: (String | Proc)?
17
17
 
18
- # Options passed to the provider client.
19
- attr_accessor provider_options: Hash[Symbol, untyped]
20
-
21
18
  # Options passed to generate_text/stream_text.
22
19
  attr_accessor model_options: Hash[Symbol, untyped]
23
20
 
@@ -49,7 +46,6 @@ class Riffer::Agent::Config
49
46
  # ?identifier: String?,
50
47
  # ?model: (String | Proc)?,
51
48
  # ?instructions: (String | Proc)?,
52
- # ?provider_options: Hash[Symbol, untyped],
53
49
  # ?model_options: Hash[Symbol, untyped],
54
50
  # ?structured_output: Riffer::Params?,
55
51
  # ?max_steps: Numeric?,
@@ -59,7 +55,7 @@ class Riffer::Agent::Config
59
55
  # ?skills_config: Riffer::Skills::Config?,
60
56
  # ?guardrails: Hash[Symbol, Array[Hash[Symbol, untyped]]]
61
57
  # ) -> void
62
- def initialize: (?identifier: String?, ?model: (String | Proc)?, ?instructions: (String | Proc)?, ?provider_options: Hash[Symbol, untyped], ?model_options: Hash[Symbol, untyped], ?structured_output: Riffer::Params?, ?max_steps: Numeric?, ?tools_config: (Array[singleton(Riffer::Tool)] | Proc)?, ?mcp_configs: Array[Hash[Symbol, untyped]], ?tool_runtime: singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc, ?skills_config: Riffer::Skills::Config?, ?guardrails: Hash[Symbol, Array[Hash[Symbol, untyped]]]) -> void
58
+ def initialize: (?identifier: String?, ?model: (String | Proc)?, ?instructions: (String | Proc)?, ?model_options: Hash[Symbol, untyped], ?structured_output: Riffer::Params?, ?max_steps: Numeric?, ?tools_config: (Array[singleton(Riffer::Tool)] | Proc)?, ?mcp_configs: Array[Hash[Symbol, untyped]], ?tool_runtime: singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc, ?skills_config: Riffer::Skills::Config?, ?guardrails: Hash[Symbol, Array[Hash[Symbol, untyped]]]) -> void
63
59
 
64
60
  # Sets +identifier+, coercing the value to String.
65
61
  # --
@@ -11,6 +11,8 @@
11
11
  # agent = MyAgent.new
12
12
  # agent.generate('Hello!')
13
13
  class Riffer::Agent
14
+ extend Riffer::Registrable
15
+
14
16
  self.@config: Riffer::Agent::Config?
15
17
 
16
18
  INTERRUPT_MAX_STEPS: Symbol
@@ -45,12 +47,6 @@ class Riffer::Agent
45
47
  # : (?(String | Proc)?) -> (String | Proc)?
46
48
  def self.instructions: (?(String | Proc)?) -> (String | Proc)?
47
49
 
48
- # Gets or sets provider options passed to the provider client.
49
- #
50
- # --
51
- # : (?Hash[Symbol, untyped]?) -> Hash[Symbol, untyped]
52
- def self.provider_options: (?Hash[Symbol, untyped]?) -> Hash[Symbol, untyped]
53
-
54
50
  # Gets or sets model options passed to generate_text/stream_text.
55
51
  #
56
52
  # --
@@ -110,18 +106,6 @@ class Riffer::Agent
110
106
  # : () ?{ (Riffer::Skills::Config) [self: Riffer::Skills::Config] -> void } -> Riffer::Skills::Config?
111
107
  def self.skills: () ?{ (Riffer::Skills::Config) [self: Riffer::Skills::Config] -> void } -> Riffer::Skills::Config?
112
108
 
113
- # Finds an agent class by identifier.
114
- #
115
- # --
116
- # : (String) -> singleton(Riffer::Agent)?
117
- def self.find: (String) -> singleton(Riffer::Agent)?
118
-
119
- # Returns all agent subclasses.
120
- #
121
- # --
122
- # : () -> Array[singleton(Riffer::Agent)]
123
- def self.all: () -> Array[singleton(Riffer::Agent)]
124
-
125
109
  # Generates a response using a new agent instance.
126
110
  # --
127
111
  # : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?, ?tags: Hash[(String | Symbol), untyped]) -> Riffer::Agent::Response
@@ -7,15 +7,19 @@ class Riffer::Config
7
7
 
8
8
  attr_accessor region(): untyped
9
9
 
10
- def self.new: (?untyped api_token, ?untyped region) -> instance
11
- | (?api_token: untyped, ?region: untyped) -> instance
10
+ attr_accessor client(): untyped
11
+
12
+ def self.new: (?untyped api_token, ?untyped region, ?untyped client) -> instance
13
+ | (?api_token: untyped, ?region: untyped, ?client: untyped) -> instance
12
14
  end
13
15
 
14
16
  class Anthropic < Struct[untyped]
15
17
  attr_accessor api_key(): untyped
16
18
 
17
- def self.new: (?untyped api_key) -> instance
18
- | (?api_key: untyped) -> instance
19
+ attr_accessor client(): untyped
20
+
21
+ def self.new: (?untyped api_key, ?untyped client) -> instance
22
+ | (?api_key: untyped, ?client: untyped) -> instance
19
23
  end
20
24
 
21
25
  class AzureOpenAI < Struct[untyped]
@@ -23,33 +27,39 @@ class Riffer::Config
23
27
 
24
28
  attr_accessor endpoint(): untyped
25
29
 
26
- def self.new: (?untyped api_key, ?untyped endpoint) -> instance
27
- | (?api_key: untyped, ?endpoint: untyped) -> instance
30
+ attr_accessor client(): untyped
31
+
32
+ def self.new: (?untyped api_key, ?untyped endpoint, ?untyped client) -> instance
33
+ | (?api_key: untyped, ?endpoint: untyped, ?client: untyped) -> instance
28
34
  end
29
35
 
30
36
  class Gemini < Struct[untyped]
31
37
  attr_accessor api_key(): untyped
32
38
 
33
- attr_accessor open_timeout(): untyped
34
-
35
- attr_accessor read_timeout(): untyped
39
+ attr_accessor client(): untyped
36
40
 
37
- def self.new: (?untyped api_key, ?untyped open_timeout, ?untyped read_timeout) -> instance
38
- | (?api_key: untyped, ?open_timeout: untyped, ?read_timeout: untyped) -> instance
41
+ def self.new: (?untyped api_key, ?untyped client) -> instance
42
+ | (?api_key: untyped, ?client: untyped) -> instance
39
43
  end
40
44
 
41
45
  class OpenAI < Struct[untyped]
42
46
  attr_accessor api_key(): untyped
43
47
 
44
- def self.new: (?untyped api_key) -> instance
45
- | (?api_key: untyped) -> instance
48
+ attr_accessor base_url(): untyped
49
+
50
+ attr_accessor client(): untyped
51
+
52
+ def self.new: (?untyped api_key, ?untyped base_url, ?untyped client) -> instance
53
+ | (?api_key: untyped, ?base_url: untyped, ?client: untyped) -> instance
46
54
  end
47
55
 
48
56
  class OpenRouter < Struct[untyped]
49
57
  attr_accessor api_key(): untyped
50
58
 
51
- def self.new: (?untyped api_key) -> instance
52
- | (?api_key: untyped) -> instance
59
+ attr_accessor client(): untyped
60
+
61
+ def self.new: (?untyped api_key, ?untyped client) -> instance
62
+ | (?api_key: untyped, ?client: untyped) -> instance
53
63
  end
54
64
 
55
65
  class Evals < Struct[untyped]
@@ -3,8 +3,6 @@
3
3
  # Executes LLM-as-judge evaluations, using tool calling internally to get
4
4
  # structured output from the judge model.
5
5
  class Riffer::Evals::Judge
6
- @provider_options: Hash[Symbol, untyped]
7
-
8
6
  @provider_instance: Riffer::Providers::Base?
9
7
 
10
8
  @provider_name: String?
@@ -23,8 +21,8 @@ class Riffer::Evals::Judge
23
21
 
24
22
  # Raises Riffer::ArgumentError unless +model+ is "provider/model" format.
25
23
  # --
26
- # : (model: String, ?provider_options: Hash[Symbol, untyped]) -> void
27
- def initialize: (model: String, ?provider_options: Hash[Symbol, untyped]) -> void
24
+ # : (model: String) -> void
25
+ def initialize: (model: String) -> void
28
26
 
29
27
  # Evaluates an input/output pair using the configured LLM.
30
28
  # --
@@ -0,0 +1,19 @@
1
+ # Generated from lib/riffer/helpers/identifier.rb with RBS::Inline
2
+
3
+ # Helper module for deriving snake_case identifiers from class names.
4
+ module Riffer::Helpers::Identifier
5
+ # Derives a snake_case identifier from a class name string.
6
+ #
7
+ # --
8
+ # : (String?) -> String
9
+ def derive: (String?) -> String
10
+
11
+ # Derives and memoizes the identifier for a class or module. Anonymous
12
+ # classes return "" without caching, so a class named later still derives its
13
+ # real identifier — a guard that must travel with the cache, so callers never
14
+ # memoize their own.
15
+ #
16
+ # --
17
+ # : (Module) -> String
18
+ def for: (Module) -> String
19
+ end
@@ -21,11 +21,22 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
21
21
  def self.semconv_provider_name: () -> String
22
22
 
23
23
  # --
24
- # : (?api_token: String?, ?region: String?, **untyped) -> void
25
- def initialize: (?api_token: String?, ?region: String?, **untyped) -> void
24
+ # : () -> void
25
+ def initialize: () -> void
26
26
 
27
27
  private
28
28
 
29
+ # --
30
+ # : () -> untyped
31
+ def global_client: () -> untyped
32
+
33
+ # Compacted so an unset region stays absent: the AWS SDK resolves +AWS_REGION+
34
+ # and the shared config only for a missing argument, and raises
35
+ # +Aws::Errors::MissingRegionError+ on an explicit nil.
36
+ # --
37
+ # : () -> untyped
38
+ def build_client: () -> untyped
39
+
29
40
  # --
30
41
  # : (Array[Riffer::Messages::Base], String?, Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
31
42
  def build_request_params: (Array[Riffer::Messages::Base], String?, Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
@@ -19,11 +19,22 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
19
19
  def self.semconv_provider_name: () -> String
20
20
 
21
21
  # --
22
- # : (?api_key: String?, **untyped) -> void
23
- def initialize: (?api_key: String?, **untyped) -> void
22
+ # : () -> void
23
+ def initialize: () -> void
24
24
 
25
25
  private
26
26
 
27
+ # --
28
+ # : () -> untyped
29
+ def global_client: () -> untyped
30
+
31
+ # Compacted for the same reason as the other providers: never hand an SDK an
32
+ # explicit nil credential, so its own +ANTHROPIC_API_KEY+ resolution stays
33
+ # reachable regardless of how that SDK distinguishes nil from absent.
34
+ # --
35
+ # : () -> untyped
36
+ def build_client: () -> untyped
37
+
27
38
  # --
28
39
  # : (Array[Riffer::Messages::Base], String?, Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
29
40
  def build_request_params: (Array[Riffer::Messages::Base], String?, Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
@@ -1,15 +1,25 @@
1
1
  # Generated from lib/riffer/providers/azure_open_ai.rb with RBS::Inline
2
2
 
3
3
  # Azure OpenAI provider for GPT models hosted on Azure. Requires the +openai+
4
- # gem. Credentials resolve from kwargs, then config, then
5
- # +AZURE_OPENAI_API_KEY+ / +AZURE_OPENAI_ENDPOINT+.
4
+ # gem. Credentials resolve from config, then +AZURE_OPENAI_API_KEY+ /
5
+ # +AZURE_OPENAI_ENDPOINT+.
6
6
  class Riffer::Providers::AzureOpenAI < Riffer::Providers::OpenAI
7
7
  # The GenAI semconv well-known provider name.
8
8
  # --
9
9
  # : () -> String
10
10
  def self.semconv_provider_name: () -> String
11
11
 
12
+ private
13
+
14
+ # --
15
+ # : () -> untyped
16
+ def global_client: () -> untyped
17
+
18
+ # Deliberately not compacted: this borrows the OpenAI SDK to talk to Azure, so
19
+ # omitting an unset argument would let the SDK fall back to +OPENAI_API_KEY+
20
+ # and +OPENAI_BASE_URL+ — sending Azure traffic, and an OpenAI credential, to
21
+ # whatever those name. Passing nil raises in the SDK instead.
12
22
  # --
13
- # : (**untyped) -> void
14
- def initialize: (**untyped) -> void
23
+ # : () -> untyped
24
+ def build_client: () -> untyped
15
25
  end
@@ -5,6 +5,10 @@
5
5
  # +extract_token_usage+, +extract_content+, +extract_tool_calls+) and the base
6
6
  # class orchestrates them.
7
7
  class Riffer::Providers::Base
8
+ self.@semconv_provider_name: String?
9
+
10
+ @client: untyped
11
+
8
12
  @current_model: String?
9
13
 
10
14
  @current_tools: Array[singleton(Riffer::Tool)]
@@ -42,6 +46,24 @@ class Riffer::Providers::Base
42
46
  # : (String) -> true
43
47
  def depends_on: (String) -> true
44
48
 
49
+ # Returns the client for the current LLM call. A configured client wins,
50
+ # resolved on every call so a Proc can vary the client by process or
51
+ # credential lifetime; otherwise the provider builds one from the configured
52
+ # credentials, memoized for the life of the provider.
53
+ # --
54
+ # : () -> untyped
55
+ def client: () -> untyped
56
+
57
+ # Returns the consumer-configured client for this provider; nil when none is
58
+ # configured, and for providers that take no configuration at all.
59
+ # --
60
+ # : () -> untyped
61
+ def global_client: () -> untyped
62
+
63
+ # --
64
+ # : () -> untyped
65
+ def build_client: () -> untyped
66
+
45
67
  # --
46
68
  # : (String) -> String
47
69
  def encode_tool_name: (String) -> String
@@ -0,0 +1,65 @@
1
+ # Generated from lib/riffer/providers/gemini/client.rb with RBS::Inline
2
+
3
+ # HTTP transport for the Gemini REST API. Riffer builds one from the
4
+ # configured +api_key+ by default; construct your own to tune the HTTP knobs
5
+ # and assign it to <tt>Riffer.config.gemini.client</tt>. Any object
6
+ # implementing +post+ and +post_stream+ with these contracts works there —
7
+ # the class is a default implementation, not a required base.
8
+ #
9
+ # Riffer.configure do |config|
10
+ # config.gemini.client = Riffer::Providers::Gemini::Client.new(
11
+ # api_key: ENV["GEMINI_API_KEY"],
12
+ # read_timeout: 120
13
+ # )
14
+ # end
15
+ class Riffer::Providers::Gemini::Client
16
+ @proxy_port: Integer?
17
+
18
+ @proxy_address: String?
19
+
20
+ @write_timeout: Integer?
21
+
22
+ @read_timeout: Integer
23
+
24
+ @open_timeout: Integer
25
+
26
+ @base_url: String
27
+
28
+ @api_key: String?
29
+
30
+ DEFAULT_BASE_URL: String
31
+
32
+ DEFAULT_OPEN_TIMEOUT: Integer
33
+
34
+ DEFAULT_READ_TIMEOUT: Integer
35
+
36
+ # : (?api_key: String?, ?base_url: String, ?open_timeout: Integer, ?read_timeout: Integer, ?write_timeout: Integer?, ?proxy_address: String?, ?proxy_port: Integer?) -> void
37
+ def initialize: (?api_key: String?, ?base_url: String, ?open_timeout: Integer, ?read_timeout: Integer, ?write_timeout: Integer?, ?proxy_address: String?, ?proxy_port: Integer?) -> void
38
+
39
+ # POSTs a JSON body to an API path and returns the parsed response hash.
40
+ # Raises Riffer::Error when the API responds with a non-success status.
41
+ # --
42
+ # : (String, Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
43
+ def post: (String, Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
44
+
45
+ # POSTs a JSON body to an API path, yielding raw response body chunks as
46
+ # they arrive. Raises Riffer::Error when the API responds with a
47
+ # non-success status.
48
+ # --
49
+ # : (String, Hash[Symbol, untyped]) { (String) -> void } -> void
50
+ def post_stream: (String, Hash[Symbol, untyped]) { (String) -> void } -> void
51
+
52
+ private
53
+
54
+ # --
55
+ # : (URI::Generic, Hash[Symbol, untyped]) -> Net::HTTP::Post
56
+ def build_request: (URI::Generic, Hash[Symbol, untyped]) -> Net::HTTP::Post
57
+
58
+ # --
59
+ # : [R] (URI::Generic) { (Net::HTTP) -> R } -> R
60
+ def start_http: [R] (URI::Generic) { (Net::HTTP) -> R } -> R
61
+
62
+ # --
63
+ # : (Net::HTTPResponse) -> void
64
+ def handle_api_error!: (Net::HTTPResponse) -> void
65
+ end
@@ -2,20 +2,8 @@
2
2
 
3
3
  # Google Gemini provider for Gemini models via the Gemini REST API.
4
4
  class Riffer::Providers::Gemini < Riffer::Providers::Base
5
- @read_timeout: Integer
6
-
7
- @open_timeout: Integer
8
-
9
- @api_key: String?
10
-
11
- BASE_URI: URI::Generic
12
-
13
5
  VALID_MODEL_PATTERN: Regexp
14
6
 
15
- DEFAULT_OPEN_TIMEOUT: Integer
16
-
17
- DEFAULT_READ_TIMEOUT: Integer
18
-
19
7
  FINISH_REASONS: Hash[String, Symbol]
20
8
 
21
9
  # The GenAI semconv well-known provider name.
@@ -23,11 +11,15 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
23
11
  # : () -> String
24
12
  def self.semconv_provider_name: () -> String
25
13
 
14
+ private
15
+
26
16
  # --
27
- # : (?api_key: String?, ?open_timeout: Integer?, ?read_timeout: Integer?, **untyped) -> void
28
- def initialize: (?api_key: String?, ?open_timeout: Integer?, ?read_timeout: Integer?, **untyped) -> void
17
+ # : () -> untyped
18
+ def global_client: () -> untyped
29
19
 
30
- private
20
+ # --
21
+ # : () -> untyped
22
+ def build_client: () -> untyped
31
23
 
32
24
  # --
33
25
  # : (Array[Riffer::Messages::Base], String?, Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
@@ -89,10 +81,6 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
89
81
  # : (untyped) -> String
90
82
  def encode_tool_arguments: (untyped) -> String
91
83
 
92
- # --
93
- # : (String, Hash[Symbol, untyped]) -> Net::HTTPResponse
94
- def post_request: (String, Hash[Symbol, untyped]) -> Net::HTTPResponse
95
-
96
84
  # --
97
85
  # : (String, String) -> String
98
86
  def api_path: (String, String) -> String
@@ -104,8 +92,4 @@ class Riffer::Providers::Gemini < Riffer::Providers::Base
104
92
  # --
105
93
  # : (Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
106
94
  def strip_additional_properties: (Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
107
-
108
- # --
109
- # : (Net::HTTPResponse) -> void
110
- def handle_api_error!: (Net::HTTPResponse) -> void
111
95
  end
@@ -23,7 +23,8 @@ class Riffer::Providers::Mock < Riffer::Providers::Base
23
23
  attr_reader calls: Array[Hash[Symbol, untyped]]
24
24
 
25
25
  # +responses:+ pre-configures canned responses (same shape as
26
- # +#stub_response+), typically set via +provider_options responses: [...]+.
26
+ # +#stub_response+) for standalone use; agent tests queue responses on
27
+ # <tt>agent.provider</tt> via +#stub_response+ instead.
27
28
  #
28
29
  # Riffer::Providers::Mock.new(responses: [
29
30
  # {content: "", tool_calls: [{name: "tool_a", arguments: "{}"}]},
@@ -31,8 +32,8 @@ class Riffer::Providers::Mock < Riffer::Providers::Base
31
32
  # ])
32
33
  #
33
34
  # --
34
- # : (**untyped) -> void
35
- def initialize: (**untyped) -> void
35
+ # : (?responses: Array[Hash[Symbol, untyped]]) -> void
36
+ def initialize: (?responses: Array[Hash[Symbol, untyped]]) -> void
36
37
 
37
38
  # Stubs the next response; call repeatedly to queue several. +finish_reason+
38
39
  # defaults to +:tool_calls+ when tool calls are present, else +:stop+.
@@ -10,11 +10,22 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
10
10
  def self.semconv_provider_name: () -> String
11
11
 
12
12
  # --
13
- # : (**untyped) -> void
14
- def initialize: (**untyped) -> void
13
+ # : () -> void
14
+ def initialize: () -> void
15
15
 
16
16
  private
17
17
 
18
+ # --
19
+ # : () -> untyped
20
+ def global_client: () -> untyped
21
+
22
+ # Compacted so an unset value stays absent: the SDK reads +OPENAI_API_KEY+ /
23
+ # +OPENAI_BASE_URL+ only for a missing argument, and an explicit nil would
24
+ # suppress that fallback.
25
+ # --
26
+ # : () -> untyped
27
+ def build_client: () -> untyped
28
+
18
29
  # --
19
30
  # : (Array[Riffer::Messages::Base], String?, Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
20
31
  def build_request_params: (Array[Riffer::Messages::Base], String?, Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
@@ -2,7 +2,7 @@
2
2
 
3
3
  # OpenRouter provider (https://openrouter.ai). Requires the +openai+ gem —
4
4
  # OpenRouter exposes an OpenAI-compatible endpoint, so this reuses the OpenAI
5
- # SDK with a +base_url+ override. +api_key+ falls back to config, then
5
+ # SDK with a +base_url+ override. +api_key+ resolves from config, then
6
6
  # +OPENROUTER_API_KEY+.
7
7
  class Riffer::Providers::OpenRouter < Riffer::Providers::Base
8
8
  BASE_URL: String
@@ -15,11 +15,24 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
15
15
  def self.semconv_provider_name: () -> String
16
16
 
17
17
  # --
18
- # : (?api_key: String?, **untyped) -> void
19
- def initialize: (?api_key: String?, **untyped) -> void
18
+ # : () -> void
19
+ def initialize: () -> void
20
20
 
21
21
  private
22
22
 
23
+ # --
24
+ # : () -> untyped
25
+ def global_client: () -> untyped
26
+
27
+ # Deliberately not compacted: this borrows the OpenAI SDK to talk to a
28
+ # different vendor, so omitting an unset +api_key+ would let the SDK fall
29
+ # back to +OPENAI_API_KEY+ and send an OpenAI credential to OpenRouter.
30
+ # Passing nil raises in the SDK instead. +OPENROUTER_API_KEY+ is read here
31
+ # rather than left to the SDK for the same reason.
32
+ # --
33
+ # : () -> untyped
34
+ def build_client: () -> untyped
35
+
23
36
  # --
24
37
  # : (Array[Riffer::Messages::Base], String?, Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
25
38
  def build_request_params: (Array[Riffer::Messages::Base], String?, Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
@@ -0,0 +1,51 @@
1
+ # Generated from lib/riffer/registrable.rb with RBS::Inline
2
+
3
+ # Registry of a class's named direct subclasses, keyed by identifier. Extend it
4
+ # onto a base class to look up subclasses in constant time via +find+ and +all+.
5
+ #
6
+ # class Riffer::Tool
7
+ # extend Riffer::Registrable
8
+ # end
9
+ #
10
+ # Riffer::Tool.find("weather_tool") # => WeatherTool
11
+ #
12
+ # @rbs module-self Class
13
+ module Riffer::Registrable : Class
14
+ @identifier_registry: Hash[String, Class]?
15
+
16
+ # Finds a registered subclass by identifier, or +nil+ when none matches.
17
+ # Only *named direct* subclasses are registered: grandchildren are not
18
+ # visible to a grandparent's +find+ (call +find+ on their direct parent
19
+ # instead), anonymous classes are never registered, and duplicate identifiers
20
+ # raise Riffer::DuplicateIdentifierError at first lookup.
21
+ #
22
+ # --
23
+ # : (String | Symbol) -> Class?
24
+ def find: (String | Symbol) -> Class?
25
+
26
+ # Returns all registered subclasses. Only *named direct* subclasses are
27
+ # registered: grandchildren are not included (call +all+ on their direct
28
+ # parent instead), anonymous classes are never registered, and duplicate
29
+ # identifiers raise Riffer::DuplicateIdentifierError at first lookup.
30
+ #
31
+ # --
32
+ # : () -> Array[Class]
33
+ def all: () -> Array[Class]
34
+
35
+ private
36
+
37
+ # Ruby invokes +inherited+ with +self+ bound to the direct superclass — the
38
+ # only registry the new subclass joins — so busting self's memo is exactly
39
+ # sufficient.
40
+ # --
41
+ # : (Class) -> void
42
+ def inherited: (Class) -> void
43
+
44
+ # --
45
+ # : () -> Hash[String, Class]
46
+ def identifier_registry: () -> Hash[String, Class]
47
+
48
+ # --
49
+ # : () -> Hash[String, Class]
50
+ def build_identifier_registry: () -> Hash[String, Class]
51
+ end
@@ -18,6 +18,8 @@
18
18
  class Riffer::Tool
19
19
  extend Riffer::Tools::Toolable
20
20
 
21
+ extend Riffer::Registrable
22
+
21
23
  # Executes the tool with the given arguments.
22
24
  # --
23
25
  # : (context: Riffer::Agent::Context?, **untyped) -> Riffer::Tools::Response
@@ -13,7 +13,9 @@
13
13
  # required :input, String
14
14
  # end
15
15
  # end
16
- module Riffer::Tools::Toolable
16
+ #
17
+ # @rbs module-self Module
18
+ module Riffer::Tools::Toolable : Module
17
19
  @kind: Symbol?
18
20
 
19
21
  @params_builder: Riffer::Params?
@@ -23,6 +23,10 @@ module Riffer
23
23
  class ToolExecutionError < Error
24
24
  end
25
25
 
26
+ # Raised when two registered subclasses share the same identifier.
27
+ class DuplicateIdentifierError < Error
28
+ end
29
+
26
30
  # Returns the Riffer configuration.
27
31
  #
28
32
  # --
@@ -0,0 +1,7 @@
1
+ # `Riffer::Agent` extends `Riffer::Registrable`, whose generic signatures return
2
+ # `Class`. Narrow them here so callers get the agent singleton type back.
3
+ class Riffer::Agent
4
+ def self.find: (String | Symbol) -> singleton(Riffer::Agent)?
5
+
6
+ def self.all: () -> Array[singleton(Riffer::Agent)]
7
+ end
@@ -0,0 +1,5 @@
1
+ # `Riffer::Helpers::Identifier` uses `extend self`; rbs-inline doesn't emit
2
+ # that, so re-extend here to expose its instance methods as singleton methods.
3
+ module Riffer::Helpers::Identifier
4
+ extend ::Riffer::Helpers::Identifier
5
+ end