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
data/lib/riffer/agent.rb CHANGED
@@ -19,6 +19,8 @@ require "json"
19
19
  # agent.generate('Hello!')
20
20
  #
21
21
  class Riffer::Agent
22
+ # @rbs self.@config: Riffer::Agent::Config?
23
+
22
24
  include Riffer::Messages::Converter
23
25
  extend Riffer::Helpers::ClassNameConverter
24
26
 
@@ -100,13 +102,19 @@ class Riffer::Agent
100
102
 
101
103
  # Gets or sets the maximum number of LLM call steps in the tool-use loop.
102
104
  #
103
- # Defaults to Riffer::Agent::Config::DEFAULT_MAX_STEPS (16). Set to
104
- # +Float::INFINITY+ for unlimited steps.
105
+ # Defaults to Riffer::Agent::Config::DEFAULT_MAX_STEPS (16). Set to +nil+
106
+ # for unlimited steps. The splat distinguishes a getter call (no argument)
107
+ # from setting the limit to +nil+.
108
+ #
109
+ # max_steps # reads the current limit
110
+ # max_steps 8 # cap the loop at 8 steps
111
+ # max_steps nil # unlimited
105
112
  #
106
113
  #--
107
- #: (?Numeric?) -> Numeric
108
- def self.max_steps(value = nil)
109
- value.nil? ? config.max_steps : (config.max_steps = value)
114
+ #: (*Numeric?) -> Numeric?
115
+ def self.max_steps(*value)
116
+ return config.max_steps if value.empty?
117
+ config.max_steps = value.first
110
118
  end
111
119
 
112
120
  # Gets or sets the tools used by this agent.
@@ -204,6 +212,30 @@ class Riffer::Agent
204
212
  new(context: context).stream(prompt, files: files)
205
213
  end
206
214
 
215
+ # Reconstructs a runnable agent from a wire dict produced by +#to_h+.
216
+ #
217
+ # Delegates to Riffer::Agent::Serializer.from_h. See it for the
218
+ # +tool_resolver+ / +tool_runtime+ injection points and what does not
219
+ # transfer.
220
+ #
221
+ #--
222
+ #: (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
223
+ def self.from_h(hash, context: nil, tool_resolver: Riffer::Agent::Serializer::DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
224
+ Riffer::Agent::Serializer.from_h(hash, context: context, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
225
+ end
226
+
227
+ # Reconstructs a runnable agent from a JSON string produced by +#to_json+.
228
+ #
229
+ # Delegates to Riffer::Agent::Serializer.from_json, which parses the JSON
230
+ # (with symbol keys) for you. See Riffer::Agent::Serializer.from_h for the
231
+ # +tool_resolver+ / +tool_runtime+ injection points.
232
+ #
233
+ #--
234
+ #: (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
235
+ def self.from_json(json, context: nil, tool_resolver: Riffer::Agent::Serializer::DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
236
+ Riffer::Agent::Serializer.from_json(json, context: context, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
237
+ end
238
+
207
239
  # Registers a guardrail for input, output, or both phases.
208
240
  #
209
241
  # [phase] :before, :after, or :around.
@@ -259,6 +291,11 @@ class Riffer::Agent
259
291
  # reserved and cannot be passed by the caller.
260
292
  attr_reader :context #: Riffer::Agent::Context
261
293
 
294
+ # The resolved provider name (the part before "provider/"), e.g. +"openai"+.
295
+ # Resolved eagerly at +Agent.new+ alongside +model_name+; together they
296
+ # form the provider-neutral model identifier the agent serializes.
297
+ attr_reader :provider_name #: String
298
+
262
299
  # The resolved model name (the part after "provider/"), used as the model
263
300
  # argument on every LLM call. Resolved eagerly at +Agent.new+.
264
301
  attr_reader :model_name #: String
@@ -306,10 +343,10 @@ class Riffer::Agent
306
343
  @config = config || self.class.config
307
344
  @context = Riffer::Agent::Context.new(context || {})
308
345
 
309
- provider_class, @model_name = resolve_provider_and_model
310
- @provider = provider_class.new(**@config.provider_options)
346
+ @provider_name, @model_name = resolve_provider_and_model
347
+ @provider = build_provider
311
348
 
312
- @context.skills = resolve_skills(provider_class)
349
+ @context.skills = resolve_skills
313
350
 
314
351
  @structured_output = resolve_structured_output
315
352
  @tools = resolve_tools
@@ -374,6 +411,25 @@ class Riffer::Agent
374
411
  throw :riffer_interrupt, reason
375
412
  end
376
413
 
414
+ # Snapshots this resolved agent into a self-contained, provider-neutral
415
+ # wire dict. Delegates to Riffer::Agent::Serializer.to_h.
416
+ #
417
+ #--
418
+ #: () -> Hash[Symbol, untyped]
419
+ def to_h
420
+ Riffer::Agent::Serializer.to_h(agent: self)
421
+ end
422
+
423
+ # Snapshots this resolved agent into a wire JSON string. Delegates to
424
+ # Riffer::Agent::Serializer.to_json. The +*+ absorbs the JSON generator
425
+ # state argument so <tt>JSON.generate(agent)</tt> works too.
426
+ #
427
+ #--
428
+ #: (*untyped) -> String
429
+ def to_json(*)
430
+ Riffer::Agent::Serializer.to_json(agent: self)
431
+ end
432
+
377
433
  private
378
434
 
379
435
  #--
@@ -393,13 +449,13 @@ class Riffer::Agent
393
449
  end
394
450
 
395
451
  # Resolves +Config#model+ to a "provider/model" string (calling the Proc
396
- # form against +@context+), parses it, and looks up the provider class.
452
+ # form against +@context+) and parses it.
397
453
  #
398
- # Returns +[provider_class, model_name]+. Raises Riffer::ArgumentError on
399
- # an invalid model string or an unregistered provider.
454
+ # Returns +[provider_name, model_name]+. Raises Riffer::ArgumentError on an
455
+ # invalid model string.
400
456
  #
401
457
  #--
402
- #: () -> [singleton(Riffer::Providers::Base), String]
458
+ #: () -> [String, String]
403
459
  def resolve_provider_and_model
404
460
  model_string = Riffer::Helpers::CallOrValue.resolve(@config.model, context: @context)
405
461
  raise Riffer::ArgumentError, "Invalid model string: #{model_string}" unless model_string.is_a?(String)
@@ -410,18 +466,28 @@ class Riffer::Agent
410
466
  raise Riffer::ArgumentError, "Invalid model string: #{model_string}"
411
467
  end
412
468
 
413
- provider_class = Riffer::Providers::Repository.find(provider_name)
414
- raise Riffer::ArgumentError, "Provider not found: #{provider_name}" unless provider_class
469
+ [provider_name, model_name]
470
+ end
415
471
 
416
- [provider_class, model_name]
472
+ # Builds the provider client from the resolved +@provider_name+ and the
473
+ # configured +provider_options+.
474
+ #
475
+ # Raises Riffer::ArgumentError on an unregistered provider.
476
+ #
477
+ #--
478
+ #: () -> Riffer::Providers::Base
479
+ def build_provider
480
+ provider_class = Riffer::Providers::Repository.find(@provider_name)
481
+ raise Riffer::ArgumentError, "Provider not found: #{@provider_name}" unless provider_class
482
+ provider_class.new(**@config.provider_options)
417
483
  end
418
484
 
419
485
  # Resolves the skills backend, lists skills, and selects an adapter.
420
486
  # Returns nil if skills are unconfigured or the backend is empty.
421
487
  #
422
488
  #--
423
- #: (singleton(Riffer::Providers::Base)) -> Riffer::Skills::Context?
424
- def resolve_skills(provider_class)
489
+ #: () -> Riffer::Skills::Context?
490
+ def resolve_skills
425
491
  skills_config = @config.skills_config
426
492
  return nil unless skills_config
427
493
 
@@ -432,7 +498,7 @@ class Riffer::Agent
432
498
  return nil if backend.list_skills.empty?
433
499
 
434
500
  skills = backend.list_skills.to_h { |s| [s.name, s] }
435
- adapter_class = skills_config.adapter || provider_class.skills_adapter(@model_name)
501
+ adapter_class = skills_config.adapter || @provider.class.skills_adapter(@model_name)
436
502
  skill_activate_tool_class = skills_config.activate_tool || Riffer.config.skills.default_activate_tool
437
503
 
438
504
  skills_context = Riffer::Skills::Context.new(
@@ -16,6 +16,11 @@
16
16
  # end
17
17
  #
18
18
  class Riffer::Evals::Evaluator
19
+ # @rbs self.@instructions: String?
20
+ # @rbs self.@higher_is_better: bool?
21
+ # @rbs self.@judge_model: String?
22
+ # @rbs @judge: Riffer::Evals::Judge?
23
+
19
24
  class << self
20
25
  # Gets or sets the evaluation instructions (criteria and scoring rubric).
21
26
  #
@@ -19,6 +19,11 @@ require "json"
19
19
  # result[:reason] # => "The response is relevant..."
20
20
  #
21
21
  class Riffer::Evals::Judge
22
+ # @rbs @provider_options: Hash[Symbol, untyped]
23
+ # @rbs @provider_instance: Riffer::Providers::Base?
24
+ # @rbs @provider_name: String?
25
+ # @rbs @model_name: String?
26
+
22
27
  # Internal tool for structured evaluation output.
23
28
  class EvaluationTool < Riffer::Tool
24
29
  identifier "evaluation"
@@ -16,6 +16,8 @@
16
16
  class Riffer::Mcp::Client
17
17
  include Riffer::Helpers::Dependencies
18
18
 
19
+ # @rbs @client: untyped
20
+
19
21
  #--
20
22
  #: (endpoint: String, ?headers: (Hash[String, String] | Proc), ?client: untyped?) -> void
21
23
  def initialize(endpoint:, headers: {}, client: nil)
@@ -7,6 +7,10 @@
7
7
  # +tools/list+ call, then generates tool classes.
8
8
  #
9
9
  class Riffer::Mcp::Registration
10
+ # @rbs @cancelled: bool
11
+ # @rbs @tools: Array[singleton(Riffer::Tool)]
12
+ # @rbs @mutex: Thread::Mutex
13
+
10
14
  # The manifest that describes this server.
11
15
  attr_reader :manifest #: Riffer::Mcp::Manifest
12
16
 
@@ -6,6 +6,9 @@
6
6
  # Keyed by manifest name. All public methods are mutex-guarded.
7
7
  #
8
8
  module Riffer::Mcp::Registry
9
+ # @rbs self.@mutex: Thread::Mutex
10
+ # @rbs self.@store: Hash[String, Riffer::Mcp::Registration]
11
+
9
12
  @mutex = Mutex.new
10
13
  @store = {} #: Hash[String, Riffer::Mcp::Registration]
11
14
 
@@ -15,6 +15,8 @@ require "uri"
15
15
  # file.document? # => true
16
16
  #
17
17
  class Riffer::Messages::FilePart
18
+ # @rbs @url_string: String?
19
+
18
20
  MEDIA_TYPES = {
19
21
  ".jpg" => "image/jpeg",
20
22
  ".jpeg" => "image/jpeg",
@@ -18,19 +18,90 @@ class Riffer::Params::Param
18
18
  }.freeze #: Hash[Module, String]
19
19
 
20
20
  # Primitive types allowed for the <tt>of:</tt> keyword on Array params
21
- PRIMITIVE_TYPES = (TYPE_MAPPINGS.keys - [Array, Hash]).freeze #: Array[Class]
21
+ PRIMITIVE_TYPES = (TYPE_MAPPINGS.keys - [Array, Hash]).freeze #: Array[Module]
22
+
23
+ # Maps JSON Schema type strings back to Ruby types. The inverse of
24
+ # TYPE_MAPPINGS, collapsing the three boolean spellings onto
25
+ # Riffer::Params::Boolean. Used by +from_json_schema+.
26
+ JSON_TYPE_MAPPINGS = {
27
+ "string" => String,
28
+ "integer" => Integer,
29
+ "number" => Float,
30
+ "boolean" => Riffer::Params::Boolean,
31
+ "array" => Array,
32
+ "object" => Hash
33
+ }.freeze #: Hash[String, Module]
22
34
 
23
35
  attr_reader :name #: Symbol
24
- attr_reader :type #: Class
36
+ attr_reader :type #: Module
25
37
  attr_reader :required #: bool
26
38
  attr_reader :description #: String?
27
39
  attr_reader :enum #: Array[untyped]?
28
40
  attr_reader :default #: untyped
29
- attr_reader :item_type #: Class?
41
+ attr_reader :item_type #: Module?
30
42
  attr_reader :nested_params #: Riffer::Params?
31
43
 
32
44
  #--
33
- #: (name: Symbol, type: Class, required: bool, ?description: String?, ?enum: Array[untyped]?, ?default: untyped, ?item_type: Class?, ?nested_params: Riffer::Params?) -> void
45
+ # Reconstructs a Param from a single JSON Schema property.
46
+ #
47
+ # [name] the parameter name (Symbol).
48
+ # [schema] the property's JSON Schema (Symbol-keyed).
49
+ # [required] whether the property appeared in the parent's +required+ list.
50
+ #
51
+ # Raises Riffer::ArgumentError on a type outside the Params-expressible subset.
52
+ #
53
+ #--
54
+ #: (Symbol, Hash[Symbol, untyped], required: bool) -> Riffer::Params::Param
55
+ def self.from_json_schema(name, schema, required:)
56
+ ruby_type = json_type_to_ruby(schema[:type])
57
+ item_type, nested = resolve_nesting(ruby_type, schema)
58
+
59
+ new(
60
+ name: name,
61
+ type: ruby_type,
62
+ required: required,
63
+ description: schema[:description],
64
+ enum: schema[:enum],
65
+ default: schema[:default],
66
+ item_type: item_type,
67
+ nested_params: nested
68
+ )
69
+ end
70
+
71
+ # Resolves the +[item_type, nested_params]+ pair for a reconstructed Param:
72
+ # a nested Params for object / array-of-object schemas, an +item_type+ for
73
+ # typed primitive arrays, and +nil+ for everything else.
74
+ #
75
+ #--
76
+ #: (Module, Hash[Symbol, untyped]) -> [Module?, Riffer::Params?]
77
+ def self.resolve_nesting(ruby_type, schema)
78
+ return [nil, Riffer::Params.from_json_schema(schema)] if ruby_type == Hash && schema[:properties]
79
+ return [nil, nil] unless ruby_type == Array
80
+
81
+ items = schema[:items]
82
+ return [nil, nil] unless items.is_a?(Hash)
83
+ return [nil, Riffer::Params.from_json_schema(items)] if items[:properties]
84
+
85
+ [json_type_to_ruby(items[:type]), nil]
86
+ end
87
+ private_class_method :resolve_nesting
88
+
89
+ # Resolves a JSON Schema +type+ (a String, or a <tt>[type, "null"]</tt>
90
+ # union) back to its Ruby type. Returns a Module because
91
+ # Riffer::Params::Boolean is a Module, not a Class — the same widening the
92
+ # +type+ attribute uses. Raises Riffer::ArgumentError on a type outside the
93
+ # Params-expressible subset (the block runs only for an unmapped type).
94
+ #
95
+ #--
96
+ #: (untyped) -> Module
97
+ def self.json_type_to_ruby(type)
98
+ key = type.is_a?(Array) ? type.find { |t| t != "null" } : type
99
+ JSON_TYPE_MAPPINGS.fetch(key) { raise Riffer::ArgumentError, "Unsupported JSON Schema type: #{type.inspect}" }
100
+ end
101
+ private_class_method :json_type_to_ruby
102
+
103
+ #--
104
+ #: (name: Symbol, type: Module, required: bool, ?description: String?, ?enum: Array[untyped]?, ?default: untyped, ?item_type: Module?, ?nested_params: Riffer::Params?) -> void
34
105
  def initialize(name:, type:, required:, description: nil, enum: nil, default: nil, item_type: nil, nested_params: nil)
35
106
  @name = name.to_sym
36
107
  @type = type
@@ -74,6 +145,11 @@ class Riffer::Params::Param
74
145
  # constraint from the null type, since providers like Anthropic reject
75
146
  # <tt>{"type": ["string", "null"], "enum": [...]}</tt>.
76
147
  #
148
+ # In non-strict mode a +default+ is emitted when set (a standard JSON
149
+ # Schema keyword), making the schema a lossless source for
150
+ # +Riffer::Params.from_json_schema+. Strict mode omits it, since strict
151
+ # providers reject the keyword.
152
+ #
77
153
  #--
78
154
  #: (?strict: bool) -> Hash[Symbol, untyped]
79
155
  def to_json_schema(strict: false)
@@ -91,6 +167,10 @@ class Riffer::Params::Param
91
167
  schema = {type: type} #: Hash[Symbol, untyped]
92
168
  schema[:description] = description if description
93
169
  schema[:enum] = enum if enum
170
+ # Strict providers reject the +default+ keyword; emit it only in
171
+ # non-strict mode, where it makes the schema a lossless round-trip
172
+ # source for +from_json_schema+.
173
+ schema[:default] = default unless strict || default.nil?
94
174
 
95
175
  if self.type == Array && nested_params
96
176
  schema[:items] = nested_params.to_json_schema(strict: strict)
data/lib/riffer/params.rb CHANGED
@@ -20,10 +20,41 @@ class Riffer::Params
20
20
  @parameters = []
21
21
  end
22
22
 
23
+ # Reconstructs a Params from a JSON Schema object (the inverse of
24
+ # +to_json_schema(strict: false)+).
25
+ #
26
+ # Accepts the Symbol-keyed object schema produced by +to_json_schema+
27
+ # (property-name keys may be String or Symbol — both are normalized).
28
+ # Reconstructs types, +required+, +description+, +enum+, +default+,
29
+ # typed-array +item_type+, and nested object/array Params recursively.
30
+ #
31
+ # Round-trips losslessly with +to_json_schema(strict: false)+ over the
32
+ # Params-expressible subset of JSON Schema. Raises Riffer::ArgumentError
33
+ # on a schema using features outside that subset.
34
+ #
35
+ # schema = params.to_json_schema(strict: false)
36
+ # Riffer::Params.from_json_schema(schema) # => equivalent Riffer::Params
37
+ #
38
+ #--
39
+ #: (Hash[Symbol, untyped]) -> Riffer::Params
40
+ def self.from_json_schema(schema)
41
+ params = new
42
+ properties = schema[:properties] || {}
43
+ required = (schema[:required] || []).map { |key| key.to_s }
44
+
45
+ properties.each do |name, property_schema|
46
+ params.parameters << Riffer::Params::Param.from_json_schema(
47
+ name.to_sym, property_schema, required: required.include?(name.to_s)
48
+ )
49
+ end
50
+
51
+ params
52
+ end
53
+
23
54
  # Defines a required parameter.
24
55
  #
25
56
  #--
26
- #: (Symbol, Class, ?description: String?, ?enum: Array[untyped]?, ?of: Class?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> void
57
+ #: (Symbol, Module, ?description: String?, ?enum: Array[untyped]?, ?of: Module?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> void
27
58
  def required(name, type, description: nil, enum: nil, of: nil, &block)
28
59
  nested = build_nested(type, of, &block)
29
60
  @parameters << Riffer::Params::Param.new(
@@ -40,7 +71,7 @@ class Riffer::Params
40
71
  # Defines an optional parameter.
41
72
  #
42
73
  #--
43
- #: (Symbol, Class, ?description: String?, ?enum: Array[untyped]?, ?default: untyped, ?of: Class?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> void
74
+ #: (Symbol, Module, ?description: String?, ?enum: Array[untyped]?, ?default: untyped, ?of: Module?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> void
44
75
  def optional(name, type, description: nil, enum: nil, default: nil, of: nil, &block)
45
76
  nested = build_nested(type, of, &block)
46
77
  @parameters << Riffer::Params::Param.new(
@@ -126,7 +157,7 @@ class Riffer::Params
126
157
  private
127
158
 
128
159
  #--
129
- #: (Class, Class?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
160
+ #: (Module, Module?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
130
161
  def build_nested(type, of, &block)
131
162
  if of && block
132
163
  raise Riffer::ArgumentError, "cannot use both of: and a block"
@@ -92,15 +92,16 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
92
92
  end
93
93
 
94
94
  #--
95
- #: (Hash[Symbol, untyped]) -> Aws::BedrockRuntime::Client::_ConverseResponseSuccess
95
+ #: (Hash[Symbol, untyped]) -> untyped
96
96
  def execute_generate(params)
97
97
  @client.converse(**params)
98
98
  end
99
99
 
100
100
  #--
101
- #: (Aws::BedrockRuntime::Client::_ConverseResponseSuccess) -> Riffer::Providers::TokenUsage?
101
+ #: (untyped) -> Riffer::Providers::TokenUsage?
102
102
  def extract_token_usage(response)
103
- usage = response.usage
103
+ typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
104
+ usage = typed_response.usage
104
105
 
105
106
  Riffer::Providers::TokenUsage.new(
106
107
  input_tokens: usage.input_tokens,
@@ -111,9 +112,10 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
111
112
  end
112
113
 
113
114
  #--
114
- #: (Aws::BedrockRuntime::Client::_ConverseResponseSuccess) -> String
115
+ #: (untyped) -> String
115
116
  def extract_content(response)
116
- content_blocks = response.output&.message&.content
117
+ typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
118
+ content_blocks = typed_response.output&.message&.content
117
119
  return "" if content_blocks.nil? || content_blocks.empty?
118
120
 
119
121
  text_content = ""
@@ -126,9 +128,10 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
126
128
  end
127
129
 
128
130
  #--
129
- #: (Aws::BedrockRuntime::Client::_ConverseResponseSuccess) -> Array[Riffer::Messages::Assistant::ToolCall]
131
+ #: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
130
132
  def extract_tool_calls(response)
131
- content_blocks = response.output&.message&.content
133
+ typed_response = response #: Aws::BedrockRuntime::Client::_ConverseResponseSuccess
134
+ content_blocks = typed_response.output&.message&.content
132
135
  return [] if content_blocks.nil? || content_blocks.empty?
133
136
 
134
137
  tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
@@ -195,28 +198,31 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
195
198
  end
196
199
 
197
200
  #--
198
- #: (Aws::BedrockRuntime::Types::ContentBlockStartEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
201
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
199
202
  def handle_content_block_start_tool_use(event, state:, yielder:)
203
+ typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockStartEvent
200
204
  state[:tool_call] = {
201
- id: event.start.tool_use.tool_use_id,
202
- name: decode_tool_name(event.start.tool_use.name, tools: @current_tools),
205
+ id: typed_event.start.tool_use.tool_use_id,
206
+ name: decode_tool_name(typed_event.start.tool_use.name, tools: @current_tools),
203
207
  arguments: ""
204
208
  }
205
209
  end
206
210
 
207
211
  #--
208
- #: (Aws::BedrockRuntime::Types::ContentBlockDeltaEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
212
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
209
213
  def handle_content_block_delta_text_delta(event, state:, yielder:)
210
- delta_text = event.delta.text
214
+ typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockDeltaEvent
215
+ delta_text = typed_event.delta.text
211
216
  state[:text] ||= ""
212
217
  state[:text] += delta_text
213
218
  yielder << Riffer::StreamEvents::TextDelta.new(delta_text)
214
219
  end
215
220
 
216
221
  #--
217
- #: (Aws::BedrockRuntime::Types::ContentBlockDeltaEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
222
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
218
223
  def handle_content_block_delta_tool_use(event, state:, yielder:)
219
- input_delta = event.delta.tool_use.input
224
+ typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockDeltaEvent
225
+ input_delta = typed_event.delta.tool_use.input
220
226
 
221
227
  state[:tool_call][:arguments] += input_delta
222
228
 
@@ -228,14 +234,14 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
228
234
  end
229
235
 
230
236
  #--
231
- #: (Aws::BedrockRuntime::Types::ContentBlockStopEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
237
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
232
238
  def handle_content_block_stop_text_delta(_event, state:, yielder:)
233
239
  yielder << Riffer::StreamEvents::TextDone.new(state[:text])
234
240
  state[:text] = nil
235
241
  end
236
242
 
237
243
  #--
238
- #: (Aws::BedrockRuntime::Types::ContentBlockStopEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
244
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
239
245
  def handle_content_block_stop_tool_use(_event, state:, yielder:)
240
246
  tool_call = state[:tool_call]
241
247
  yielder << Riffer::StreamEvents::ToolCallDone.new(
@@ -248,14 +254,15 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
248
254
  end
249
255
 
250
256
  #--
251
- #: (Aws::BedrockRuntime::Types::ConverseStreamMetadataEvent, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
257
+ #: (untyped, state: Hash[Symbol, untyped], yielder: Enumerator::Yielder) -> void
252
258
  def handle_metadata_usage(event, state:, yielder:)
259
+ typed_event = event #: Aws::BedrockRuntime::Types::ConverseStreamMetadataEvent
253
260
  yielder << Riffer::StreamEvents::TokenUsageDone.new(
254
261
  token_usage: Riffer::Providers::TokenUsage.new(
255
- input_tokens: event.usage.input_tokens,
256
- output_tokens: event.usage.output_tokens,
257
- cache_creation_tokens: event.usage.cache_write_input_tokens,
258
- cache_read_tokens: event.usage.cache_read_input_tokens
262
+ input_tokens: typed_event.usage.input_tokens,
263
+ output_tokens: typed_event.usage.output_tokens,
264
+ cache_creation_tokens: typed_event.usage.cache_write_input_tokens,
265
+ cache_read_tokens: typed_event.usage.cache_read_input_tokens
259
266
  )
260
267
  )
261
268
  end
@@ -77,15 +77,16 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
77
77
  end
78
78
 
79
79
  #--
80
- #: (Hash[Symbol, untyped]) -> Anthropic::Models::Message
80
+ #: (Hash[Symbol, untyped]) -> untyped
81
81
  def execute_generate(params)
82
82
  @client.messages.create(**params)
83
83
  end
84
84
 
85
85
  #--
86
- #: (Anthropic::Models::Message) -> Riffer::Providers::TokenUsage?
86
+ #: (untyped) -> Riffer::Providers::TokenUsage?
87
87
  def extract_token_usage(response)
88
- usage = response.usage
88
+ message = response #: Anthropic::Models::Message
89
+ usage = message.usage
89
90
 
90
91
  Riffer::Providers::TokenUsage.new(
91
92
  input_tokens: usage.input_tokens,
@@ -96,9 +97,10 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
96
97
  end
97
98
 
98
99
  #--
99
- #: (Anthropic::Models::Message) -> String
100
+ #: (untyped) -> String
100
101
  def extract_content(response)
101
- content_blocks = response.content
102
+ message = response #: Anthropic::Models::Message
103
+ content_blocks = message.content
102
104
  return "" if content_blocks.nil? || content_blocks.empty?
103
105
 
104
106
  text_content = ""
@@ -111,9 +113,10 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
111
113
  end
112
114
 
113
115
  #--
114
- #: (Anthropic::Models::Message) -> Array[Riffer::Messages::Assistant::ToolCall]
116
+ #: (untyped) -> Array[Riffer::Messages::Assistant::ToolCall]
115
117
  def extract_tool_calls(response)
116
- content_blocks = response.content
118
+ message = response #: Anthropic::Models::Message
119
+ content_blocks = message.content
117
120
  return [] if content_blocks.nil? || content_blocks.empty?
118
121
 
119
122
  tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
@@ -285,9 +288,10 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
285
288
  end
286
289
 
287
290
  #--
288
- #: (untyped, accumulated_message: Anthropic::Models::Message?, yielder: Enumerator::Yielder) -> void
291
+ #: (untyped, accumulated_message: untyped, yielder: Enumerator::Yielder) -> void
289
292
  def handle_message_stop(_event, accumulated_message:, yielder:)
290
- usage = accumulated_message&.usage
293
+ message = accumulated_message #: Anthropic::Models::Message?
294
+ usage = message&.usage
291
295
  return unless usage
292
296
 
293
297
  yielder << Riffer::StreamEvents::TokenUsageDone.new(
@@ -17,6 +17,8 @@ require "json"
17
17
  # [extract_content] extract text content from the SDK response
18
18
  # [extract_tool_calls] extract tool calls from the SDK response
19
19
  class Riffer::Providers::Base
20
+ # @rbs @current_tools: Array[singleton(Riffer::Tool)]
21
+
20
22
  include Riffer::Helpers::Dependencies
21
23
  include Riffer::Messages::Converter
22
24
 
@@ -8,6 +8,10 @@ require "uri"
8
8
 
9
9
  # Google Gemini provider for Gemini models via the Gemini REST API.
10
10
  class Riffer::Providers::Gemini < Riffer::Providers::Base
11
+ # @rbs @api_key: String?
12
+ # @rbs @open_timeout: Integer
13
+ # @rbs @read_timeout: Integer
14
+
11
15
  BASE_URI = URI("https://generativelanguage.googleapis.com") #: URI::Generic
12
16
  VALID_MODEL_PATTERN = /\A[a-zA-Z0-9._-]+\z/ #: Regexp
13
17
  DEFAULT_OPEN_TIMEOUT = 10 #: Integer
@@ -5,6 +5,10 @@
5
5
  #
6
6
  # No external gems required.
7
7
  class Riffer::Providers::Mock < Riffer::Providers::Base
8
+ # @rbs @responses: Array[Hash[Symbol, untyped]]
9
+ # @rbs @current_index: Integer
10
+ # @rbs @stubbed_responses: Array[Hash[Symbol, untyped]]
11
+
8
12
  # Returns the preferred skill adapter for the given mock model.
9
13
  #
10
14
  # Mock is used to stand in for any real provider in tests, so the model