riffer 0.29.0 → 0.30.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/rbs-inline.md +51 -0
  3. data/.release-please-manifest.json +1 -1
  4. data/CHANGELOG.md +18 -0
  5. data/README.md +1 -0
  6. data/Steepfile +2 -1
  7. data/docs/01_OVERVIEW.md +1 -0
  8. data/docs/03_AGENTS.md +1 -1
  9. data/docs/15_SERIALIZATION.md +103 -0
  10. data/lib/riffer/agent/config.rb +2 -2
  11. data/lib/riffer/agent/context.rb +2 -0
  12. data/lib/riffer/agent/response.rb +2 -0
  13. data/lib/riffer/agent/run.rb +2 -1
  14. data/lib/riffer/agent/serializer.rb +215 -0
  15. data/lib/riffer/agent/session.rb +2 -0
  16. data/lib/riffer/agent.rb +84 -18
  17. data/lib/riffer/evals/evaluator.rb +5 -0
  18. data/lib/riffer/evals/judge.rb +5 -0
  19. data/lib/riffer/mcp/client.rb +2 -0
  20. data/lib/riffer/mcp/registration.rb +4 -0
  21. data/lib/riffer/mcp/registry.rb +3 -0
  22. data/lib/riffer/messages/file_part.rb +2 -0
  23. data/lib/riffer/params/param.rb +84 -4
  24. data/lib/riffer/params.rb +34 -3
  25. data/lib/riffer/providers/amazon_bedrock.rb +28 -21
  26. data/lib/riffer/providers/anthropic.rb +13 -9
  27. data/lib/riffer/providers/base.rb +2 -0
  28. data/lib/riffer/providers/gemini.rb +4 -0
  29. data/lib/riffer/providers/mock.rb +4 -0
  30. data/lib/riffer/providers/open_ai.rb +10 -7
  31. data/lib/riffer/providers/open_router.rb +25 -18
  32. data/lib/riffer/runner/fibers.rb +2 -0
  33. data/lib/riffer/runner/threaded.rb +2 -0
  34. data/lib/riffer/skills/config.rb +5 -0
  35. data/lib/riffer/skills/context.rb +3 -0
  36. data/lib/riffer/skills/filesystem_backend.rb +3 -0
  37. data/lib/riffer/tools/response.rb +2 -0
  38. data/lib/riffer/tools/runtime.rb +2 -0
  39. data/lib/riffer/tools/toolable.rb +7 -0
  40. data/lib/riffer/version.rb +1 -1
  41. data/lib/riffer.rb +2 -0
  42. data/sig/_private/anthropic.rbs +16 -0
  43. data/sig/_private/openai.rbs +29 -0
  44. data/sig/_private/riffer/providers/amazon_bedrock.rbs +4 -0
  45. data/sig/_private/riffer/providers/anthropic.rbs +4 -0
  46. data/sig/_private/riffer/providers/open_ai.rbs +4 -0
  47. data/sig/_private/riffer/providers/open_router.rbs +4 -0
  48. data/sig/generated/riffer/agent/config.rbs +3 -3
  49. data/sig/generated/riffer/agent/context.rbs +2 -0
  50. data/sig/generated/riffer/agent/response.rbs +2 -0
  51. data/sig/generated/riffer/agent/serializer.rbs +132 -0
  52. data/sig/generated/riffer/agent/session.rbs +2 -0
  53. data/sig/generated/riffer/agent.rbs +67 -11
  54. data/sig/generated/riffer/evals/evaluator.rbs +8 -0
  55. data/sig/generated/riffer/evals/judge.rbs +8 -0
  56. data/sig/generated/riffer/mcp/client.rbs +2 -0
  57. data/sig/generated/riffer/mcp/registration.rbs +6 -0
  58. data/sig/generated/riffer/mcp/registry.rbs +4 -0
  59. data/sig/generated/riffer/messages/file_part.rbs +2 -0
  60. data/sig/generated/riffer/params/param.rbs +46 -5
  61. data/sig/generated/riffer/params.rbs +25 -6
  62. data/sig/generated/riffer/providers/amazon_bedrock.rbs +20 -20
  63. data/sig/generated/riffer/providers/anthropic.rbs +10 -10
  64. data/sig/generated/riffer/providers/base.rbs +2 -0
  65. data/sig/generated/riffer/providers/gemini.rbs +6 -0
  66. data/sig/generated/riffer/providers/mock.rbs +6 -0
  67. data/sig/generated/riffer/providers/open_ai.rbs +8 -8
  68. data/sig/generated/riffer/providers/open_router.rbs +16 -16
  69. data/sig/generated/riffer/runner/fibers.rbs +2 -0
  70. data/sig/generated/riffer/runner/threaded.rbs +2 -0
  71. data/sig/generated/riffer/skills/config.rbs +8 -0
  72. data/sig/generated/riffer/skills/context.rbs +4 -0
  73. data/sig/generated/riffer/skills/filesystem_backend.rbs +4 -0
  74. data/sig/generated/riffer/tools/response.rbs +2 -0
  75. data/sig/generated/riffer/tools/runtime.rbs +2 -0
  76. data/sig/generated/riffer/tools/toolable.rbs +12 -0
  77. data/sig/generated/riffer.rbs +2 -0
  78. data/sig/manifest.yaml +3 -0
  79. data/sig/manual/riffer/agent/run.rbs +5 -0
  80. data/sig/manual/riffer/agent/serializer.rbs +5 -0
  81. data/sig/manual/riffer/helpers/call_or_value.rbs +5 -0
  82. data/sig/manual/riffer/tools/toolable.rbs +6 -0
  83. metadata +20 -11
  84. data/sig/stubs/agent_ivars.rbs +0 -7
  85. data/sig/stubs/extend_self.rbs +0 -11
  86. data/sig/stubs/lib_ivars.rbs +0 -101
  87. data/sig/stubs/provider_ivars.rbs +0 -36
  88. data/sig/stubs/provider_sdk_methods.rbs +0 -50
  89. /data/sig/{stubs → _private}/async.rbs +0 -0
  90. /data/sig/{stubs → _private}/aws-sdk-core/seahorse_request_context.rbs +0 -0
  91. /data/sig/{stubs → _private}/aws-sdk-core/static_token_provider.rbs +0 -0
  92. /data/sig/{stubs/mcp_sdk.rbs → _private/mcp.rbs} +0 -0
  93. /data/sig/{stubs → _private}/zeitwerk.rbs +0 -0
@@ -66,15 +66,16 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
66
66
  end
67
67
 
68
68
  #--
69
- #: (Hash[Symbol, untyped]) -> OpenAI::Models::Responses::Response
69
+ #: (Hash[Symbol, untyped]) -> untyped
70
70
  def execute_generate(params)
71
71
  @client.responses.create(params)
72
72
  end
73
73
 
74
74
  #--
75
- #: (OpenAI::Models::Responses::Response) -> Riffer::Providers::TokenUsage?
75
+ #: (untyped) -> Riffer::Providers::TokenUsage?
76
76
  def extract_token_usage(response)
77
- usage = response.usage
77
+ typed_response = response #: OpenAI::Models::Responses::Response
78
+ usage = typed_response.usage
78
79
  return nil unless usage
79
80
 
80
81
  Riffer::Providers::TokenUsage.new(
@@ -84,11 +85,12 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
84
85
  end
85
86
 
86
87
  #--
87
- #: (OpenAI::Models::Responses::Response) -> String
88
+ #: (untyped) -> String
88
89
  def extract_content(response)
90
+ typed_response = response #: OpenAI::Models::Responses::Response
89
91
  text_content = ""
90
92
 
91
- response.output.each do |item|
93
+ typed_response.output.each do |item|
92
94
  next unless item.is_a?(::OpenAI::Models::Responses::ResponseOutputMessage)
93
95
 
94
96
  text_block = item.content.find { |c| c.is_a?(::OpenAI::Models::Responses::ResponseOutputText) }
@@ -99,11 +101,12 @@ class Riffer::Providers::OpenAI < Riffer::Providers::Base
99
101
  end
100
102
 
101
103
  #--
102
- #: (OpenAI::Models::Responses::Response) -> Array[Riffer::Messages::Assistant::ToolCall]
104
+ #: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
103
105
  def extract_tool_calls(response)
106
+ typed_response = response #: OpenAI::Models::Responses::Response
104
107
  tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
105
108
 
106
- response.output.each do |item|
109
+ typed_response.output.each do |item|
107
110
  next unless item.is_a?(::OpenAI::Models::Responses::ResponseFunctionToolCall)
108
111
 
109
112
  tool_calls << Riffer::Messages::Assistant::ToolCall.new(
@@ -64,15 +64,16 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
64
64
  end
65
65
 
66
66
  #--
67
- #: (Hash[Symbol, untyped]) -> OpenAI::Models::Chat::ChatCompletion
67
+ #: (Hash[Symbol, untyped]) -> untyped
68
68
  def execute_generate(params)
69
69
  @client.chat.completions.create(**params)
70
70
  end
71
71
 
72
72
  #--
73
- #: (OpenAI::Models::Chat::ChatCompletion) -> Riffer::Providers::TokenUsage?
73
+ #: (untyped) -> Riffer::Providers::TokenUsage?
74
74
  def extract_token_usage(response)
75
- usage = response.usage
75
+ typed_response = response #: OpenAI::Models::Chat::ChatCompletion
76
+ usage = typed_response.usage
76
77
  return nil unless usage
77
78
 
78
79
  Riffer::Providers::TokenUsage.new(
@@ -82,15 +83,17 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
82
83
  end
83
84
 
84
85
  #--
85
- #: (OpenAI::Models::Chat::ChatCompletion) -> String
86
+ #: (untyped) -> String
86
87
  def extract_content(response)
87
- response.choices.first&.message&.content || ""
88
+ typed_response = response #: OpenAI::Models::Chat::ChatCompletion
89
+ typed_response.choices.first&.message&.content || ""
88
90
  end
89
91
 
90
92
  #--
91
- #: (OpenAI::Models::Chat::ChatCompletion) -> Array[Riffer::Messages::Assistant::ToolCall]
93
+ #: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
92
94
  def extract_tool_calls(response)
93
- message = response.choices.first&.message
95
+ typed_response = response #: OpenAI::Models::Chat::ChatCompletion
96
+ message = typed_response.choices.first&.message
94
97
  return [] unless message
95
98
 
96
99
  tool_calls = message.tool_calls
@@ -145,9 +148,10 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
145
148
  end
146
149
 
147
150
  #--
148
- #: (OpenAI::Models::Chat::ChatCompletionChunk, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
151
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
149
152
  def handle_stream_chunk(chunk, state:, yielder:)
150
- choice = chunk.choices&.first
153
+ typed_chunk = chunk #: OpenAI::Models::Chat::ChatCompletionChunk
154
+ choice = typed_chunk.choices&.first
151
155
  delta = choice&.delta
152
156
 
153
157
  if delta
@@ -160,20 +164,21 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
160
164
  emit_tool_call_done_events(state: state, yielder: yielder)
161
165
  end
162
166
 
163
- return unless chunk.usage
167
+ return unless typed_chunk.usage
164
168
 
165
169
  yielder << Riffer::StreamEvents::TokenUsageDone.new(
166
170
  token_usage: Riffer::Providers::TokenUsage.new(
167
- input_tokens: chunk.usage.prompt_tokens,
168
- output_tokens: chunk.usage.completion_tokens
171
+ input_tokens: typed_chunk.usage.prompt_tokens,
172
+ output_tokens: typed_chunk.usage.completion_tokens
169
173
  )
170
174
  )
171
175
  end
172
176
 
173
177
  #--
174
- #: (OpenAI::Models::Chat::ChatCompletionChunk::Choice::Delta, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
178
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
175
179
  def handle_text_delta(delta, state:, yielder:)
176
- content = delta.content
180
+ typed_delta = delta #: OpenAI::Models::Chat::ChatCompletionChunk::Choice::Delta
181
+ content = typed_delta.content
177
182
  return if content.nil? || content.empty?
178
183
 
179
184
  state[:text] << content
@@ -194,9 +199,10 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
194
199
  end
195
200
 
196
201
  #--
197
- #: (OpenAI::Models::Chat::ChatCompletionChunk::Choice::Delta, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
202
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
198
203
  def handle_tool_call_deltas(delta, state:, yielder:)
199
- tool_calls = delta.tool_calls
204
+ typed_delta = delta #: OpenAI::Models::Chat::ChatCompletionChunk::Choice::Delta
205
+ tool_calls = typed_delta.tool_calls
200
206
  return if tool_calls.nil? || tool_calls.empty?
201
207
 
202
208
  tool_calls.each do |tc|
@@ -236,9 +242,10 @@ class Riffer::Providers::OpenRouter < Riffer::Providers::Base
236
242
  end
237
243
 
238
244
  #--
239
- #: (OpenAI::Models::Chat::ChatCompletionChunk::Choice) -> bool
245
+ #: (untyped) -> bool
240
246
  def finish_reason_is_tool_calls?(choice)
241
- choice.finish_reason.to_s == "tool_calls"
247
+ typed_choice = choice #: OpenAI::Models::Chat::ChatCompletionChunk::Choice
248
+ typed_choice.finish_reason.to_s == "tool_calls"
242
249
  end
243
250
 
244
251
  #--
@@ -14,6 +14,8 @@
14
14
  # runner.map(items) { |item| expensive_operation(item) }
15
15
  #
16
16
  class Riffer::Runner::Fibers < Riffer::Runner
17
+ # @rbs @max_concurrency: Integer?
18
+
17
19
  include Riffer::Helpers::Dependencies
18
20
 
19
21
  # [max_concurrency] maximum number of fibers to run simultaneously.
@@ -14,6 +14,8 @@
14
14
  # runner.map(items) { |item| expensive_operation(item) }
15
15
  #
16
16
  class Riffer::Runner::Threaded < Riffer::Runner
17
+ # @rbs @max_concurrency: Integer
18
+
17
19
  DEFAULT_MAX_CONCURRENCY = 5 #: Integer
18
20
 
19
21
  # [max_concurrency] maximum number of threads to run simultaneously.
@@ -11,6 +11,11 @@
11
11
  # activate ["code-review"]
12
12
  # end
13
13
  class Riffer::Skills::Config
14
+ # @rbs @backend: (Riffer::Skills::Backend | Proc)?
15
+ # @rbs @adapter: singleton(Riffer::Skills::Adapter)?
16
+ # @rbs @activate: (Array[String] | Proc)?
17
+ # @rbs @activate_tool: singleton(Riffer::Tool)?
18
+
14
19
  # Creates a new Config with all options unset.
15
20
  #
16
21
  #--
@@ -11,6 +11,9 @@
11
11
  #
12
12
  # See Riffer::Skills::Backend, Riffer::Skills::Frontmatter.
13
13
  class Riffer::Skills::Context
14
+ # @rbs @backend: Riffer::Skills::Backend
15
+ # @rbs @activated: Hash[String, String]
16
+
14
17
  # Skill catalog indexed by name.
15
18
  attr_reader :skills #: Hash[String, Riffer::Skills::Frontmatter]
16
19
 
@@ -11,6 +11,9 @@
11
11
  # backend.read_skill("code-review") # => "Full skill instructions..."
12
12
  #
13
13
  class Riffer::Skills::FilesystemBackend < Riffer::Skills::Backend
14
+ # @rbs @paths: Array[String]
15
+ # @rbs @skills_cache: Hash[String, String]?
16
+
14
17
  # Creates a new FilesystemBackend.
15
18
  #
16
19
  # [paths] one or more directory paths to scan for skills.
@@ -18,6 +18,8 @@ require "json"
18
18
  # end
19
19
  #
20
20
  class Riffer::Tools::Response
21
+ # @rbs @success: bool
22
+
21
23
  VALID_FORMATS = %i[text json].freeze #: Array[Symbol]
22
24
 
23
25
  attr_reader :content #: String
@@ -15,6 +15,8 @@ require "json"
15
15
  # results = runtime.execute(tool_calls, tools: tools, context: context)
16
16
  #
17
17
  class Riffer::Tools::Runtime
18
+ # @rbs @runner: Riffer::Runner
19
+
18
20
  # [runner] the concurrency runner to use for batch execution.
19
21
  #
20
22
  # Subclasses must provide a runner; instantiating Riffer::Tools::Runtime directly
@@ -23,6 +23,13 @@
23
23
  # end
24
24
  #
25
25
  module Riffer::Tools::Toolable
26
+ # @rbs self.@extenders: Array[Module]?
27
+ # @rbs @description: String?
28
+ # @rbs @identifier: String?
29
+ # @rbs @timeout: (Integer | Float)?
30
+ # @rbs @params_builder: Riffer::Params?
31
+ # @rbs @kind: Symbol?
32
+
26
33
  DEFAULT_TIMEOUT = 10 #: Integer
27
34
 
28
35
  # Tracks all classes that extend Toolable.
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module Riffer
5
- VERSION = "0.29.0" #: String
5
+ VERSION = "0.30.0" #: String
6
6
  end
data/lib/riffer.rb CHANGED
@@ -17,6 +17,8 @@ loader.inflector.inflect(
17
17
  loader.setup
18
18
 
19
19
  module Riffer
20
+ # @rbs self.@config: Riffer::Config?
21
+
20
22
  # Base error class for Riffer.
21
23
  class Error < StandardError; end
22
24
 
@@ -0,0 +1,16 @@
1
+ # Arity patch for the `anthropic` gem (see openai.rbs for the rationale).
2
+ # `Messages#create` is positional at runtime but typed keyword-only in the gem's
3
+ # RBS; `Messages#stream` is omitted from the gem's RBS but present at runtime
4
+ # (returns a MessageStream helper). Loaded only for riffer's own Steep run.
5
+ module Anthropic
6
+ module Resources
7
+ class Messages
8
+ def create: (Hash[Symbol, untyped] params) -> Anthropic::Models::Message
9
+ | (**untyped) -> Anthropic::Models::Message
10
+ | ...
11
+
12
+ def stream: (Hash[Symbol, untyped] params) -> untyped
13
+ | (**untyped) -> untyped
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,29 @@
1
+ # Arity patch for the `openai` gem. Its Resources methods are positional
2
+ # `def x(params)` at runtime, but the gem's shipped RBS advertises keyword-only
3
+ # signatures. We accept a positional hash or a keyword splat (`| ...` extends the
4
+ # gem's existing overloads). Streaming returns `untyped` — the event objects are a
5
+ # ~60-member union dispatched dynamically on `#type`. Loaded only for riffer's own
6
+ # Steep run (consumers skip sig/_private).
7
+ module OpenAI
8
+ module Resources
9
+ class Responses
10
+ def create: (Hash[Symbol, untyped] params) -> OpenAI::Models::Responses::Response
11
+ | (**untyped) -> OpenAI::Models::Responses::Response
12
+ | ...
13
+ def stream: (Hash[Symbol, untyped] params) -> untyped
14
+ | (**untyped) -> untyped
15
+ | ...
16
+ end
17
+
18
+ class Chat
19
+ class Completions
20
+ def create: (Hash[Symbol, untyped] params) -> OpenAI::Models::Chat::ChatCompletion
21
+ | (**untyped) -> OpenAI::Models::Chat::ChatCompletion
22
+ | ...
23
+ def stream_raw: (Hash[Symbol, untyped] params) -> untyped
24
+ | (**untyped) -> untyped
25
+ | ...
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,4 @@
1
+ # `@client` is typed against the SDK, so this ivar stub stays in _private (never ships)
2
+ class Riffer::Providers::AmazonBedrock
3
+ @client: ::Aws::BedrockRuntime::Client
4
+ end
@@ -0,0 +1,4 @@
1
+ # `@client` is typed against the SDK, so this ivar stub stays in _private (never ships)
2
+ class Riffer::Providers::Anthropic
3
+ @client: ::Anthropic::Client
4
+ end
@@ -0,0 +1,4 @@
1
+ # `@client` is typed against the SDK, so this ivar stub stays in _private (never ships)
2
+ class Riffer::Providers::OpenAI
3
+ @client: ::OpenAI::Client
4
+ end
@@ -0,0 +1,4 @@
1
+ # `@client` is typed against the SDK, so this ivar stub stays in _private (never ships)
2
+ class Riffer::Providers::OpenRouter
3
+ @client: ::OpenAI::Client
4
+ end
@@ -26,7 +26,7 @@ class Riffer::Agent::Config
26
26
 
27
27
  attr_reader structured_output: Riffer::Params?
28
28
 
29
- attr_accessor max_steps: Numeric
29
+ attr_accessor max_steps: Numeric?
30
30
 
31
31
  attr_accessor tools_config: (Array[singleton(Riffer::Tool)] | Proc)?
32
32
 
@@ -45,8 +45,8 @@ class Riffer::Agent::Config
45
45
  # as a non-String, non-Proc value (or as an empty String).
46
46
  #
47
47
  # --
48
- # : (?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
49
- 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
48
+ # : (?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
49
+ 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
50
50
 
51
51
  # Sets +identifier+. Accepts +nil+ or any value, coerced to String.
52
52
  #
@@ -17,6 +17,8 @@
17
17
  # context.skills # => nil
18
18
  # context.token_usage # => nil
19
19
  class Riffer::Agent::Context
20
+ @data: Hash[Symbol, untyped]
21
+
20
22
  # Keys reserved for framework use. Passing any of these to the
21
23
  # constructor raises +Riffer::ArgumentError+.
22
24
  RESERVED_KEYS: Array[Symbol]
@@ -12,6 +12,8 @@
12
12
  # puts response.content
13
13
  # end
14
14
  class Riffer::Agent::Response
15
+ @interrupted: bool
16
+
15
17
  # The response content.
16
18
  attr_reader content: String
17
19
 
@@ -0,0 +1,132 @@
1
+ # Generated from lib/riffer/agent/serializer.rb with RBS::Inline
2
+
3
+ # Riffer::Agent::Serializer turns a resolved agent into a self-contained,
4
+ # provider-neutral data dict and back into a runnable agent. A pure module
5
+ # (sibling to Riffer::Agent::Run), reached most often through the
6
+ # +Riffer::Agent#to_h+ / +Riffer::Agent.from_h+ delegators.
7
+ #
8
+ # The dict carries only data — no Procs, no class references, no tool
9
+ # runtime. The same dict serves two rehydration targets:
10
+ #
11
+ # - <b>In-process</b> (a monolith persisting agent definitions): pass a
12
+ # +tool_resolver+ that looks tool descriptors up in a local registry and
13
+ # returns the real, body-bearing classes. They run on the default runtime.
14
+ # - <b>Distributed</b> (a receiver holding only the Riffer gem): the default
15
+ # resolver synthesizes body-less tool shells; inject a remote
16
+ # +Riffer::Tools::Runtime+ to forward each call back to the origin.
17
+ #
18
+ # dict = Riffer::Agent::Serializer.to_h(agent: agent)
19
+ # rebuilt = Riffer::Agent::Serializer.from_h(dict, context: {tenant: "acme"})
20
+ #
21
+ # == What does not transfer
22
+ #
23
+ # Guardrails and the skills subsystem (backend/adapter/catalog) are not
24
+ # serialized; a rebuilt agent enforces no guardrails and renders no skills
25
+ # catalog (the +skill_activate+ tool, if present, crosses as an ordinary
26
+ # tool). Secrets must not be placed in +provider_options+/+model_options+:
27
+ # both ride on the wire as plain data.
28
+ module Riffer::Agent::Serializer
29
+ # The wire format version. Bumped only on an incompatible change to the
30
+ # dict shape; +from_h+ refuses any other version. See +from_h+ for the
31
+ # dispatch seam that carries back-compat decoders.
32
+ SCHEMA_VERSION: Integer
33
+
34
+ # Raised by +from_h+ when the dict's +schema_version+ is unsupported.
35
+ class VersionError < Riffer::ArgumentError
36
+ end
37
+
38
+ # The default +tool_resolver+: synthesizes a body-less tool shell from a
39
+ # descriptor. Its +#call+ raises — route shells through a remote runtime.
40
+ DEFAULT_TOOL_RESOLVER: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool)
41
+
42
+ # Snapshots a resolved agent into a self-contained wire dict.
43
+ #
44
+ # Reads the agent's resolved instance state — Proc-based settings have
45
+ # already been evaluated against the agent's own context, so the dict
46
+ # carries plain strings/data, never Procs. Tools are emitted as
47
+ # +{name, description, parameters_schema, timeout}+ descriptors (the
48
+ # resolved +agent.tools+, including MCP tools and +skill_activate+).
49
+ #
50
+ # [agent] a resolved Riffer::Agent instance.
51
+ #
52
+ # --
53
+ # : (agent: Riffer::Agent) -> Hash[Symbol, untyped]
54
+ def to_h: (agent: Riffer::Agent) -> Hash[Symbol, untyped]
55
+
56
+ # Reconstructs a runnable agent from a wire dict.
57
+ #
58
+ # [hash] a Symbol-keyed wire dict (parse JSON with +symbolize_names: true+).
59
+ # [context] the rebuilt agent's runtime context — the same value you'd pass
60
+ # to +Agent.new(context:)+. It is *not* used to re-resolve serialized
61
+ # config (the dict is already resolved); it is threaded into tool dispatch
62
+ # and read by tools/runtimes at call time (e.g. a remote runtime keying off
63
+ # <tt>context[:tenant]</tt>). Defaults to an empty context.
64
+ # [tool_resolver] maps a tool descriptor to a Riffer::Tool class. Defaults
65
+ # to DEFAULT_TOOL_RESOLVER (body-less shells). Pass a registry lookup to
66
+ # rebuild real, in-process tools.
67
+ # [tool_runtime] an optional Riffer::Tools::Runtime to inject (e.g. a
68
+ # remote runtime for shells). When omitted, the agent uses the configured
69
+ # default (+Riffer.config.tool_runtime+).
70
+ #
71
+ # Raises Riffer::Agent::Serializer::VersionError on an unsupported
72
+ # +schema_version+, and Riffer::ArgumentError on a malformed dict.
73
+ #
74
+ # --
75
+ # : (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
76
+ def from_h: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
77
+
78
+ # Snapshots a resolved agent to a JSON string. Convenience over
79
+ # <tt>JSON.generate(to_h(agent:))</tt>.
80
+ #
81
+ # --
82
+ # : (agent: Riffer::Agent) -> String
83
+ def to_json: (agent: Riffer::Agent) -> String
84
+
85
+ # Reconstructs a runnable agent from a JSON string produced by +to_json+.
86
+ # Handles the JSON parse (with symbol keys) so callers don't have to. See
87
+ # +from_h+ for the arguments.
88
+ #
89
+ # --
90
+ # : (String, ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
91
+ def from_json: (String, ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
92
+
93
+ private
94
+
95
+ # --
96
+ # : (Hash[Symbol, untyped], context: Hash[Symbol, untyped]?, tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
97
+ def decode_v1: (Hash[Symbol, untyped], context: Hash[Symbol, untyped]?, tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
98
+
99
+ # --
100
+ # : (Hash[Symbol, untyped]?) -> Riffer::Params?
101
+ def decode_structured_output: (Hash[Symbol, untyped]?) -> Riffer::Params?
102
+
103
+ # The DSL represents unlimited steps as +nil+, but the wire encodes it as
104
+ # +-1+ so the dict stays portable across transports where JSON +null+ is
105
+ # awkward (e.g. proto3, which can't tell null from an absent field). The
106
+ # magic value lives only on the wire — +encode_max_steps+/+decode_max_steps+
107
+ # translate at the boundary so neither the DSL nor consumers see it.
108
+ # --
109
+ # : (Numeric?) -> Numeric
110
+ def encode_max_steps: (Numeric?) -> Numeric
111
+
112
+ # Reverses +encode_max_steps+: +-1+ (or a literal +null+) means unlimited.
113
+ # An absent key falls back to the default — a partial dict must not silently
114
+ # become an unbounded loop.
115
+ # --
116
+ # : (Hash[Symbol, untyped]) -> Numeric?
117
+ def decode_max_steps: (Hash[Symbol, untyped]) -> Numeric?
118
+
119
+ # --
120
+ # : (singleton(Riffer::Tool)) -> Hash[Symbol, untyped]
121
+ def tool_descriptor: (singleton(Riffer::Tool)) -> Hash[Symbol, untyped]
122
+
123
+ # Builds an anonymous, body-less Riffer::Tool subclass that advertises the
124
+ # descriptor's schema to the LLM. Its +#call+ raises — a shell only has
125
+ # identity, not behavior; route its calls through a remote runtime.
126
+ #
127
+ # Returns +untyped+: steep can't see that +Class.new(Riffer::Tool)+ is a
128
+ # +singleton(Riffer::Tool)+ (cf. Riffer::Mcp::ToolFactory#build_tool_class).
129
+ # --
130
+ # : (Hash[Symbol, untyped]) -> untyped
131
+ def build_tool_shell: (Hash[Symbol, untyped]) -> untyped
132
+ end
@@ -16,6 +16,8 @@
16
16
  class Riffer::Agent::Session
17
17
  include Enumerable[Riffer::Messages::Base]
18
18
 
19
+ @callbacks: Array[^(Riffer::Messages::Base) -> void]
20
+
19
21
  # The message history.
20
22
  attr_reader messages: Array[Riffer::Messages::Base]
21
23
 
@@ -15,6 +15,8 @@
15
15
  # agent = MyAgent.new
16
16
  # agent.generate('Hello!')
17
17
  class Riffer::Agent
18
+ self.@config: Riffer::Agent::Config?
19
+
18
20
  include Riffer::Messages::Converter
19
21
 
20
22
  extend Riffer::Helpers::ClassNameConverter
@@ -78,12 +80,17 @@ class Riffer::Agent
78
80
 
79
81
  # Gets or sets the maximum number of LLM call steps in the tool-use loop.
80
82
  #
81
- # Defaults to Riffer::Agent::Config::DEFAULT_MAX_STEPS (16). Set to
82
- # +Float::INFINITY+ for unlimited steps.
83
+ # Defaults to Riffer::Agent::Config::DEFAULT_MAX_STEPS (16). Set to +nil+
84
+ # for unlimited steps. The splat distinguishes a getter call (no argument)
85
+ # from setting the limit to +nil+.
86
+ #
87
+ # max_steps # reads the current limit
88
+ # max_steps 8 # cap the loop at 8 steps
89
+ # max_steps nil # unlimited
83
90
  #
84
91
  # --
85
- # : (?Numeric?) -> Numeric
86
- def self.max_steps: (?Numeric?) -> Numeric
92
+ # : (*Numeric?) -> Numeric?
93
+ def self.max_steps: (*Numeric?) -> Numeric?
87
94
 
88
95
  # Gets or sets the tools used by this agent.
89
96
  #
@@ -157,6 +164,26 @@ class Riffer::Agent
157
164
  # : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Enumerator[Riffer::StreamEvents::Base, void]
158
165
  def self.stream: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Enumerator[Riffer::StreamEvents::Base, void]
159
166
 
167
+ # Reconstructs a runnable agent from a wire dict produced by +#to_h+.
168
+ #
169
+ # Delegates to Riffer::Agent::Serializer.from_h. See it for the
170
+ # +tool_resolver+ / +tool_runtime+ injection points and what does not
171
+ # transfer.
172
+ #
173
+ # --
174
+ # : (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
175
+ def self.from_h: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
176
+
177
+ # Reconstructs a runnable agent from a JSON string produced by +#to_json+.
178
+ #
179
+ # Delegates to Riffer::Agent::Serializer.from_json, which parses the JSON
180
+ # (with symbol keys) for you. See Riffer::Agent::Serializer.from_h for the
181
+ # +tool_resolver+ / +tool_runtime+ injection points.
182
+ #
183
+ # --
184
+ # : (String, ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
185
+ def self.from_json: (String, ?context: Hash[Symbol, untyped]?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
186
+
160
187
  # Registers a guardrail for input, output, or both phases.
161
188
  #
162
189
  # [phase] :before, :after, or :around.
@@ -208,6 +235,11 @@ class Riffer::Agent
208
235
  # reserved and cannot be passed by the caller.
209
236
  attr_reader context: Riffer::Agent::Context
210
237
 
238
+ # The resolved provider name (the part before "provider/"), e.g. +"openai"+.
239
+ # Resolved eagerly at +Agent.new+ alongside +model_name+; together they
240
+ # form the provider-neutral model identifier the agent serializes.
241
+ attr_reader provider_name: String
242
+
211
243
  # The resolved model name (the part after "provider/"), used as the model
212
244
  # argument on every LLM call. Resolved eagerly at +Agent.new+.
213
245
  attr_reader model_name: String
@@ -298,6 +330,21 @@ class Riffer::Agent
298
330
  # : (?(String | Symbol)?) -> void
299
331
  def interrupt!: (?(String | Symbol)?) -> void
300
332
 
333
+ # Snapshots this resolved agent into a self-contained, provider-neutral
334
+ # wire dict. Delegates to Riffer::Agent::Serializer.to_h.
335
+ #
336
+ # --
337
+ # : () -> Hash[Symbol, untyped]
338
+ def to_h: () -> Hash[Symbol, untyped]
339
+
340
+ # Snapshots this resolved agent into a wire JSON string. Delegates to
341
+ # Riffer::Agent::Serializer.to_json. The +*+ absorbs the JSON generator
342
+ # state argument so <tt>JSON.generate(agent)</tt> works too.
343
+ #
344
+ # --
345
+ # : (*untyped) -> String
346
+ def to_json: (*untyped) -> String
347
+
301
348
  private
302
349
 
303
350
  # --
@@ -309,21 +356,30 @@ class Riffer::Agent
309
356
  def build_skills_message: () -> Riffer::Messages::System?
310
357
 
311
358
  # Resolves +Config#model+ to a "provider/model" string (calling the Proc
312
- # form against +@context+), parses it, and looks up the provider class.
359
+ # form against +@context+) and parses it.
360
+ #
361
+ # Returns +[provider_name, model_name]+. Raises Riffer::ArgumentError on an
362
+ # invalid model string.
363
+ #
364
+ # --
365
+ # : () -> [String, String]
366
+ def resolve_provider_and_model: () -> [ String, String ]
367
+
368
+ # Builds the provider client from the resolved +@provider_name+ and the
369
+ # configured +provider_options+.
313
370
  #
314
- # Returns +[provider_class, model_name]+. Raises Riffer::ArgumentError on
315
- # an invalid model string or an unregistered provider.
371
+ # Raises Riffer::ArgumentError on an unregistered provider.
316
372
  #
317
373
  # --
318
- # : () -> [singleton(Riffer::Providers::Base), String]
319
- def resolve_provider_and_model: () -> [ singleton(Riffer::Providers::Base), String ]
374
+ # : () -> Riffer::Providers::Base
375
+ def build_provider: () -> Riffer::Providers::Base
320
376
 
321
377
  # Resolves the skills backend, lists skills, and selects an adapter.
322
378
  # Returns nil if skills are unconfigured or the backend is empty.
323
379
  #
324
380
  # --
325
- # : (singleton(Riffer::Providers::Base)) -> Riffer::Skills::Context?
326
- def resolve_skills: (singleton(Riffer::Providers::Base)) -> Riffer::Skills::Context?
381
+ # : () -> Riffer::Skills::Context?
382
+ def resolve_skills: () -> Riffer::Skills::Context?
327
383
 
328
384
  # --
329
385
  # : () -> Riffer::Agent::StructuredOutput?