riffer 0.47.2 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/rules/comments.md +2 -4
  3. data/.claude/rules/rbs-inline.md +2 -12
  4. data/.release-please-manifest.json +1 -1
  5. data/.rubocop.yml +5 -0
  6. data/CHANGELOG.md +42 -0
  7. data/docs/AGENTS.md +39 -7
  8. data/docs/AGENT_LIFECYCLE.md +14 -16
  9. data/docs/CONFIGURATION.md +23 -34
  10. data/docs/EVALS.md +2 -1
  11. data/docs/MCP.md +0 -4
  12. data/docs/MESSAGES.md +85 -17
  13. data/docs/STREAM_EVENTS.md +8 -4
  14. data/docs/TOOL_ADVANCED.md +1 -3
  15. data/docs/TRACING.md +2 -2
  16. data/docs/providers/AMAZON_BEDROCK.md +32 -1
  17. data/docs/providers/CUSTOM_PROVIDERS.md +56 -4
  18. data/docs/providers/GEMINI.md +1 -1
  19. data/docs/providers/MOCK_PROVIDER.md +17 -0
  20. data/docs/providers/OPENROUTER.md +18 -1
  21. data/docs-site/build.rb +0 -6
  22. data/docs-site/check.rb +1 -5
  23. data/lib/riffer/agent/config.rb +27 -44
  24. data/lib/riffer/agent/context.rb +2 -26
  25. data/lib/riffer/agent/outcome.rb +0 -20
  26. data/lib/riffer/agent/response.rb +4 -35
  27. data/lib/riffer/agent/run.rb +32 -48
  28. data/lib/riffer/agent/serializer.rb +10 -39
  29. data/lib/riffer/agent/session/repair.rb +3 -15
  30. data/lib/riffer/agent/session.rb +26 -38
  31. data/lib/riffer/agent/structured_output/result.rb +0 -8
  32. data/lib/riffer/agent/structured_output.rb +0 -7
  33. data/lib/riffer/agent.rb +31 -127
  34. data/lib/riffer/config/amazon_bedrock.rb +30 -0
  35. data/lib/riffer/config/anthropic.rb +21 -0
  36. data/lib/riffer/config/azure_open_ai.rb +30 -0
  37. data/lib/riffer/config/evals.rb +18 -0
  38. data/lib/riffer/config/files.rb +61 -0
  39. data/lib/riffer/config/gemini.rb +21 -0
  40. data/lib/riffer/config/mcp.rb +31 -0
  41. data/lib/riffer/config/open_ai.rb +30 -0
  42. data/lib/riffer/config/open_router.rb +21 -0
  43. data/lib/riffer/config/pricing/rates.rb +36 -0
  44. data/lib/riffer/config/pricing.rb +64 -0
  45. data/lib/riffer/config/skills.rb +38 -0
  46. data/lib/riffer/config/tracing.rb +43 -0
  47. data/lib/riffer/config.rb +3 -362
  48. data/lib/riffer/evals/evaluator.rb +22 -23
  49. data/lib/riffer/evals/evaluator_runner.rb +0 -15
  50. data/lib/riffer/evals/judge.rb +9 -11
  51. data/lib/riffer/evals/result.rb +1 -11
  52. data/lib/riffer/evals/run_result.rb +0 -12
  53. data/lib/riffer/evals/scenario_result.rb +0 -19
  54. data/lib/riffer/files/downloader.rb +2 -3
  55. data/lib/riffer/files/resolver.rb +5 -10
  56. data/lib/riffer/guardrail.rb +2 -22
  57. data/lib/riffer/guardrails/modification.rb +0 -8
  58. data/lib/riffer/guardrails/result.rb +0 -17
  59. data/lib/riffer/guardrails/runner.rb +2 -11
  60. data/lib/riffer/guardrails/tripwire.rb +0 -8
  61. data/lib/riffer/guardrails.rb +0 -2
  62. data/lib/riffer/helpers/boolean.rb +0 -4
  63. data/lib/riffer/helpers/call_or_value.rb +0 -3
  64. data/lib/riffer/helpers/deep_dup.rb +37 -0
  65. data/lib/riffer/helpers/dependencies.rb +0 -4
  66. data/lib/riffer/helpers/identifier.rb +3 -12
  67. data/lib/riffer/helpers/validate.rb +42 -0
  68. data/lib/riffer/mcp/authenticated_tool.rb +4 -12
  69. data/lib/riffer/mcp/client.rb +0 -7
  70. data/lib/riffer/mcp/manifest.rb +3 -7
  71. data/lib/riffer/mcp/registration.rb +0 -10
  72. data/lib/riffer/mcp/registry.rb +0 -9
  73. data/lib/riffer/mcp/search_tool.rb +0 -4
  74. data/lib/riffer/mcp/tool.rb +1 -4
  75. data/lib/riffer/mcp/tool_factory.rb +3 -6
  76. data/lib/riffer/mcp.rb +2 -23
  77. data/lib/riffer/messages/assistant/reasoning_part.rb +73 -0
  78. data/lib/riffer/messages/assistant/tool_call.rb +55 -0
  79. data/lib/riffer/messages/assistant.rb +43 -15
  80. data/lib/riffer/messages/base.rb +5 -33
  81. data/lib/riffer/messages/system.rb +8 -1
  82. data/lib/riffer/messages/tool.rb +15 -13
  83. data/lib/riffer/messages/{file_part.rb → user/file_part.rb} +16 -40
  84. data/lib/riffer/messages/user.rb +11 -4
  85. data/lib/riffer/params/boolean.rb +1 -5
  86. data/lib/riffer/params/param.rb +15 -31
  87. data/lib/riffer/params.rb +15 -39
  88. data/lib/riffer/providers/amazon_bedrock.rb +104 -44
  89. data/lib/riffer/providers/anthropic.rb +11 -26
  90. data/lib/riffer/providers/azure_open_ai.rb +3 -8
  91. data/lib/riffer/providers/base.rb +30 -32
  92. data/lib/riffer/providers/finish_reason.rb +0 -6
  93. data/lib/riffer/providers/gemini/client.rb +4 -17
  94. data/lib/riffer/providers/gemini.rb +6 -12
  95. data/lib/riffer/providers/mock.rb +18 -27
  96. data/lib/riffer/providers/open_ai.rb +12 -21
  97. data/lib/riffer/providers/open_router.rb +109 -33
  98. data/lib/riffer/providers/repository.rb +2 -14
  99. data/lib/riffer/providers/token_usage.rb +19 -12
  100. data/lib/riffer/registrable.rb +11 -45
  101. data/lib/riffer/runner/fibers.rb +1 -6
  102. data/lib/riffer/runner/sequential.rb +0 -1
  103. data/lib/riffer/runner/threaded.rb +0 -3
  104. data/lib/riffer/runner.rb +0 -3
  105. data/lib/riffer/skills/activate_tool.rb +0 -3
  106. data/lib/riffer/skills/adapter.rb +0 -8
  107. data/lib/riffer/skills/backend.rb +2 -7
  108. data/lib/riffer/skills/config.rb +13 -17
  109. data/lib/riffer/skills/context.rb +0 -29
  110. data/lib/riffer/skills/filesystem_backend.rb +0 -7
  111. data/lib/riffer/skills/frontmatter.rb +2 -16
  112. data/lib/riffer/skills/markdown_adapter.rb +3 -6
  113. data/lib/riffer/skills/xml_adapter.rb +0 -3
  114. data/lib/riffer/stream_events/base.rb +0 -3
  115. data/lib/riffer/stream_events/finish_reason_done.rb +1 -6
  116. data/lib/riffer/stream_events/guardrail_modification.rb +0 -10
  117. data/lib/riffer/stream_events/guardrail_tripwire.rb +0 -10
  118. data/lib/riffer/stream_events/interrupt.rb +2 -12
  119. data/lib/riffer/stream_events/reasoning_delta.rb +0 -3
  120. data/lib/riffer/stream_events/reasoning_done.rb +6 -8
  121. data/lib/riffer/stream_events/skill_activation.rb +0 -3
  122. data/lib/riffer/stream_events/text_delta.rb +0 -2
  123. data/lib/riffer/stream_events/text_done.rb +0 -2
  124. data/lib/riffer/stream_events/token_usage_done.rb +0 -2
  125. data/lib/riffer/stream_events/tool_call_delta.rb +1 -5
  126. data/lib/riffer/stream_events/tool_call_done.rb +0 -5
  127. data/lib/riffer/stream_events/web_search_done.rb +0 -3
  128. data/lib/riffer/stream_events/web_search_status.rb +1 -5
  129. data/lib/riffer/testing/minitest.rb +4 -5
  130. data/lib/riffer/testing.rb +5 -38
  131. data/lib/riffer/tool.rb +2 -28
  132. data/lib/riffer/tools/response.rb +3 -32
  133. data/lib/riffer/tools/runtime/fibers.rb +0 -6
  134. data/lib/riffer/tools/runtime/inline.rb +0 -1
  135. data/lib/riffer/tools/runtime/threaded.rb +0 -6
  136. data/lib/riffer/tools/runtime.rb +8 -30
  137. data/lib/riffer/tools/toolable.rb +0 -37
  138. data/lib/riffer/tracing/capture.rb +5 -9
  139. data/lib/riffer/tracing/no_op.rb +0 -7
  140. data/lib/riffer/tracing/otel.rb +7 -16
  141. data/lib/riffer/tracing/stream_recorder.rb +0 -7
  142. data/lib/riffer/tracing.rb +4 -27
  143. data/lib/riffer/version.rb +1 -1
  144. data/lib/riffer.rb +2 -30
  145. data/sig/generated/riffer/agent/config.rbs +24 -46
  146. data/sig/generated/riffer/agent/context.rbs +2 -26
  147. data/sig/generated/riffer/agent/outcome.rbs +0 -20
  148. data/sig/generated/riffer/agent/response.rbs +4 -27
  149. data/sig/generated/riffer/agent/run.rbs +12 -31
  150. data/sig/generated/riffer/agent/serializer.rbs +2 -27
  151. data/sig/generated/riffer/agent/session/repair.rbs +2 -10
  152. data/sig/generated/riffer/agent/session.rbs +10 -36
  153. data/sig/generated/riffer/agent/structured_output/result.rbs +0 -7
  154. data/sig/generated/riffer/agent/structured_output.rbs +0 -7
  155. data/sig/generated/riffer/agent.rbs +25 -125
  156. data/sig/generated/riffer/config/amazon_bedrock.rbs +21 -0
  157. data/sig/generated/riffer/config/anthropic.rbs +15 -0
  158. data/sig/generated/riffer/config/azure_open_ai.rbs +21 -0
  159. data/sig/generated/riffer/config/evals.rbs +13 -0
  160. data/sig/generated/riffer/config/files.rbs +43 -0
  161. data/sig/generated/riffer/config/gemini.rbs +15 -0
  162. data/sig/generated/riffer/config/mcp.rbs +19 -0
  163. data/sig/generated/riffer/config/open_ai.rbs +21 -0
  164. data/sig/generated/riffer/config/open_router.rbs +15 -0
  165. data/sig/generated/riffer/config/pricing/rates.rbs +19 -0
  166. data/sig/generated/riffer/config/pricing.rbs +31 -0
  167. data/sig/generated/riffer/config/skills.rbs +19 -0
  168. data/sig/generated/riffer/config/tracing.rbs +25 -0
  169. data/sig/generated/riffer/config.rbs +2 -307
  170. data/sig/generated/riffer/evals/evaluator.rbs +12 -21
  171. data/sig/generated/riffer/evals/evaluator_runner.rbs +0 -14
  172. data/sig/generated/riffer/evals/judge.rbs +4 -8
  173. data/sig/generated/riffer/evals/result.rbs +1 -11
  174. data/sig/generated/riffer/evals/run_result.rbs +0 -12
  175. data/sig/generated/riffer/evals/scenario_result.rbs +0 -14
  176. data/sig/generated/riffer/files/resolver.rbs +6 -13
  177. data/sig/generated/riffer/guardrail.rbs +2 -22
  178. data/sig/generated/riffer/guardrails/modification.rbs +0 -6
  179. data/sig/generated/riffer/guardrails/result.rbs +0 -15
  180. data/sig/generated/riffer/guardrails/runner.rbs +0 -11
  181. data/sig/generated/riffer/guardrails/tripwire.rbs +0 -8
  182. data/sig/generated/riffer/guardrails.rbs +0 -2
  183. data/sig/generated/riffer/helpers/boolean.rbs +0 -4
  184. data/sig/generated/riffer/helpers/call_or_value.rbs +0 -3
  185. data/sig/generated/riffer/helpers/deep_dup.rbs +13 -0
  186. data/sig/generated/riffer/helpers/dependencies.rbs +0 -4
  187. data/sig/generated/riffer/helpers/identifier.rbs +0 -12
  188. data/sig/generated/riffer/helpers/validate.rbs +21 -0
  189. data/sig/generated/riffer/mcp/authenticated_tool.rbs +0 -5
  190. data/sig/generated/riffer/mcp/client.rbs +0 -7
  191. data/sig/generated/riffer/mcp/manifest.rbs +3 -7
  192. data/sig/generated/riffer/mcp/registration.rbs +0 -10
  193. data/sig/generated/riffer/mcp/registry.rbs +0 -9
  194. data/sig/generated/riffer/mcp/search_tool.rbs +0 -4
  195. data/sig/generated/riffer/mcp/tool.rbs +0 -4
  196. data/sig/generated/riffer/mcp/tool_factory.rbs +0 -6
  197. data/sig/generated/riffer/mcp.rbs +2 -22
  198. data/sig/generated/riffer/messages/assistant/reasoning_part.rbs +45 -0
  199. data/sig/generated/riffer/messages/assistant/tool_call.rbs +34 -0
  200. data/sig/generated/riffer/messages/assistant.rbs +25 -31
  201. data/sig/generated/riffer/messages/base.rbs +0 -12
  202. data/sig/generated/riffer/messages/system.rbs +4 -1
  203. data/sig/generated/riffer/messages/tool.rbs +4 -10
  204. data/sig/generated/riffer/messages/user/file_part.rbs +76 -0
  205. data/sig/generated/riffer/messages/user.rbs +7 -5
  206. data/sig/generated/riffer/params/boolean.rbs +1 -4
  207. data/sig/generated/riffer/params/param.rbs +7 -31
  208. data/sig/generated/riffer/params.rbs +8 -38
  209. data/sig/generated/riffer/providers/amazon_bedrock.rbs +33 -29
  210. data/sig/generated/riffer/providers/anthropic.rbs +2 -12
  211. data/sig/generated/riffer/providers/azure_open_ai.rbs +0 -8
  212. data/sig/generated/riffer/providers/base.rbs +18 -34
  213. data/sig/generated/riffer/providers/finish_reason.rbs +0 -6
  214. data/sig/generated/riffer/providers/gemini/client.rbs +4 -17
  215. data/sig/generated/riffer/providers/gemini.rbs +4 -10
  216. data/sig/generated/riffer/providers/mock.rbs +6 -27
  217. data/sig/generated/riffer/providers/open_ai.rbs +6 -13
  218. data/sig/generated/riffer/providers/open_router.rbs +37 -18
  219. data/sig/generated/riffer/providers/repository.rbs +2 -14
  220. data/sig/generated/riffer/providers/token_usage.rbs +9 -12
  221. data/sig/generated/riffer/registrable.rbs +0 -45
  222. data/sig/generated/riffer/runner/fibers.rbs +0 -6
  223. data/sig/generated/riffer/runner/sequential.rbs +0 -1
  224. data/sig/generated/riffer/runner/threaded.rbs +0 -3
  225. data/sig/generated/riffer/runner.rbs +0 -3
  226. data/sig/generated/riffer/skills/activate_tool.rbs +0 -3
  227. data/sig/generated/riffer/skills/adapter.rbs +0 -8
  228. data/sig/generated/riffer/skills/backend.rbs +2 -7
  229. data/sig/generated/riffer/skills/config.rbs +6 -17
  230. data/sig/generated/riffer/skills/context.rbs +0 -29
  231. data/sig/generated/riffer/skills/filesystem_backend.rbs +0 -7
  232. data/sig/generated/riffer/skills/frontmatter.rbs +2 -16
  233. data/sig/generated/riffer/skills/markdown_adapter.rbs +0 -6
  234. data/sig/generated/riffer/skills/xml_adapter.rbs +0 -3
  235. data/sig/generated/riffer/stream_events/base.rbs +0 -3
  236. data/sig/generated/riffer/stream_events/finish_reason_done.rbs +1 -6
  237. data/sig/generated/riffer/stream_events/guardrail_modification.rbs +0 -10
  238. data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +0 -10
  239. data/sig/generated/riffer/stream_events/interrupt.rbs +2 -10
  240. data/sig/generated/riffer/stream_events/reasoning_delta.rbs +0 -3
  241. data/sig/generated/riffer/stream_events/reasoning_done.rbs +4 -6
  242. data/sig/generated/riffer/stream_events/skill_activation.rbs +0 -3
  243. data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
  244. data/sig/generated/riffer/stream_events/text_done.rbs +0 -2
  245. data/sig/generated/riffer/stream_events/token_usage_done.rbs +0 -2
  246. data/sig/generated/riffer/stream_events/tool_call_delta.rbs +1 -5
  247. data/sig/generated/riffer/stream_events/tool_call_done.rbs +0 -5
  248. data/sig/generated/riffer/stream_events/web_search_done.rbs +0 -3
  249. data/sig/generated/riffer/stream_events/web_search_status.rbs +1 -5
  250. data/sig/generated/riffer/testing.rbs +0 -38
  251. data/sig/generated/riffer/tool.rbs +0 -27
  252. data/sig/generated/riffer/tools/response.rbs +3 -29
  253. data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -5
  254. data/sig/generated/riffer/tools/runtime/inline.rbs +0 -1
  255. data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -5
  256. data/sig/generated/riffer/tools/runtime.rbs +4 -26
  257. data/sig/generated/riffer/tools/toolable.rbs +0 -37
  258. data/sig/generated/riffer/tracing/capture.rbs +6 -9
  259. data/sig/generated/riffer/tracing/no_op.rbs +0 -7
  260. data/sig/generated/riffer/tracing/otel.rbs +7 -16
  261. data/sig/generated/riffer/tracing/stream_recorder.rbs +0 -2
  262. data/sig/generated/riffer/tracing.rbs +4 -23
  263. data/sig/generated/riffer.rbs +2 -29
  264. data/sig/manual/riffer/helpers/deep_dup.rbs +5 -0
  265. data/sig/manual/riffer/helpers/validate.rbs +5 -0
  266. metadata +39 -3
  267. data/sig/generated/riffer/messages/file_part.rbs +0 -101
@@ -4,9 +4,7 @@
4
4
  require "base64"
5
5
  require "uri"
6
6
 
7
- # Represents a file attachment (image or document) — from a URL (+from_url+) or
8
- # raw base64 data (+new+).
9
- class Riffer::Messages::FilePart
7
+ class Riffer::Messages::User::FilePart
10
8
  # @rbs @url_string: String?
11
9
  # @rbs @data: String?
12
10
  # @rbs @downloaded_data: String?
@@ -28,17 +26,12 @@ class Riffer::Messages::FilePart
28
26
  SUPPORTED_MEDIA_TYPES = MEDIA_TYPES.values.uniq.freeze #: Array[String]
29
27
  SHA256_PATTERN = /\A[0-9a-f]{64}\z/i #: Regexp
30
28
 
31
- # The MIME type of the file.
32
29
  attr_reader :media_type #: String # @dynamic media_type
33
30
 
34
- # The filename, if available.
35
31
  attr_reader :filename #: String? # @dynamic filename
36
32
 
37
- # The expected SHA-256 of the file contents, if the caller supplied one.
38
33
  attr_reader :sha256 #: String? # @dynamic sha256
39
34
 
40
- # Raises Riffer::ArgumentError unless +data+ or +url+ is given and
41
- # +media_type+ is supported.
42
35
  #--
43
36
  #: (media_type: String, ?data: String?, ?filename: String?, ?url: String?, ?sha256: String?) -> void
44
37
  def initialize(media_type:, data: nil, filename: nil, url: nil, sha256: nil)
@@ -59,26 +52,16 @@ class Riffer::Messages::FilePart
59
52
  @url_string = url
60
53
  end
61
54
 
62
- # Creates a FilePart from a URL, detecting +media_type+ from the path
63
- # extension when omitted. Raises Riffer::ArgumentError if it can't be detected.
64
55
  #--
65
- #: (String, ?media_type: String?, ?filename: String?, ?sha256: String?) -> Riffer::Messages::FilePart
56
+ #: (String, ?media_type: String?, ?filename: String?, ?sha256: String?) -> Riffer::Messages::User::FilePart
66
57
  def self.from_url(url, media_type: nil, filename: nil, sha256: nil)
67
- unless media_type
68
- ext = ::File.extname(URI.parse(url).path.to_s).downcase
69
- media_type = MEDIA_TYPES.fetch(ext) { raise Riffer::ArgumentError, "Cannot detect media type from URL; provide media_type explicitly" }
70
- end
71
-
72
- new(url: url, media_type: media_type, filename: filename, sha256: sha256)
58
+ new(url: url, media_type: media_type || detect_media_type(url), filename: filename, sha256: sha256)
73
59
  end
74
60
 
75
- # Builds a FilePart from a +{url:, media_type:}+ or +{data:, media_type:}+ hash,
76
- # or returns +file+ unchanged when it is already a FilePart. Raises
77
- # Riffer::ArgumentError on an invalid hash.
78
61
  #--
79
- #: ((Hash[Symbol, untyped] | Riffer::Messages::FilePart)) -> Riffer::Messages::FilePart
62
+ #: ((Hash[Symbol, untyped] | Riffer::Messages::User::FilePart)) -> Riffer::Messages::User::FilePart
80
63
  def self.from_hash(file)
81
- return file if file.is_a?(Riffer::Messages::FilePart)
64
+ return file if file.is_a?(Riffer::Messages::User::FilePart)
82
65
 
83
66
  url = file[:url]
84
67
  data = file[:data]
@@ -87,7 +70,7 @@ class Riffer::Messages::FilePart
87
70
  sha256 = file[:sha256]
88
71
 
89
72
  if url
90
- from_url(url, media_type: media_type, filename: filename, sha256: sha256)
73
+ new(url: url, data: data, media_type: media_type || detect_media_type(url), filename: filename, sha256: sha256)
91
74
  elsif data && media_type
92
75
  new(data: data, media_type: media_type, filename: filename, sha256: sha256)
93
76
  else
@@ -95,8 +78,14 @@ class Riffer::Messages::FilePart
95
78
  end
96
79
  end
97
80
 
98
- # The base64-encoded contents - caller-supplied, or filled in by the file
99
- # resolver after a download. Nil for a URL source riffer hasn't fetched.
81
+ #--
82
+ #: (String) -> String
83
+ def self.detect_media_type(url)
84
+ ext = ::File.extname(URI.parse(url).path.to_s).downcase
85
+ MEDIA_TYPES.fetch(ext) { raise Riffer::ArgumentError, "Cannot detect media type from URL; provide media_type explicitly" }
86
+ end
87
+ private_class_method :detect_media_type
88
+
100
89
  #--
101
90
  #: () -> String?
102
91
  def data
@@ -120,60 +109,47 @@ class Riffer::Messages::FilePart
120
109
  @data_bytes = bytes
121
110
  end
122
111
 
123
- # Whether data was supplied directly, as opposed to filled in later by the
124
- # file resolver after a download
125
112
  #: () -> bool
126
113
  def inline_data?
127
114
  !@data.nil?
128
115
  end
129
116
 
130
- # Caches bytes fetched for a URL source. Deliberately absent from +to_h+:
131
- # the agent loop re-sends history on every turn, so the cache saves refreshing
132
- # the same file, while persisted history stays free of megabytes of base64
133
117
  #--
134
118
  #: (String) -> void
135
119
  def cache_downloaded_data(data)
136
120
  @downloaded_data = data
137
121
  end
138
122
 
139
- # Returns the URL if the source was a URL, nil otherwise.
140
- #
141
123
  #--
142
124
  #: () -> String?
143
125
  def url
144
126
  @url_string
145
127
  end
146
128
 
147
- # Returns true if the source was a URL.
148
- #
149
129
  #--
150
130
  #: () -> bool
151
131
  def url?
152
132
  !@url_string.nil?
153
133
  end
154
134
 
155
- # Returns true if the file is an image.
156
- #
157
135
  #--
158
136
  #: () -> bool
159
137
  def image?
160
138
  media_type.start_with?("image/")
161
139
  end
162
140
 
163
- # Returns true if the file is a document (not an image).
164
- #
165
141
  #--
166
142
  #: () -> bool
167
143
  def document?
168
144
  !image?
169
145
  end
170
146
 
171
- # Serializes the FilePart to a hash.
172
- #
173
147
  #--
174
148
  #: () -> Hash[Symbol, untyped]
175
149
  def to_h
176
150
  hash = { media_type: media_type } #: Hash[Symbol, untyped]
151
+ # Downloaded data is left out so persisted history stays free of megabytes
152
+ # of base64; the in-memory cache still spares refetching on every turn.
177
153
  hash[:data] = @data if @data
178
154
  hash[:url] = @url_string if @url_string
179
155
  hash[:filename] = filename if filename
@@ -1,13 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Represents a user message in a conversation.
5
4
  class Riffer::Messages::User < Riffer::Messages::Base
6
- # File attachments for this message.
7
- attr_reader :files #: Array[Riffer::Messages::FilePart] # @dynamic files
5
+ #--
6
+ #: ((Hash[Symbol, untyped] | Riffer::Messages::User)) -> Riffer::Messages::User
7
+ def self.from_hash(msg)
8
+ return msg if msg.is_a?(Riffer::Messages::User)
9
+
10
+ files = (msg[:files] || []).map { |f| Riffer::Messages::User::FilePart.from_hash(f) }
11
+ new(msg[:content], id: msg[:id], files: files)
12
+ end
13
+
14
+ attr_reader :files #: Array[Riffer::Messages::User::FilePart] # @dynamic files
8
15
 
9
16
  #--
10
- #: (String, ?id: String?, ?files: Array[Riffer::Messages::FilePart]) -> void
17
+ #: (String, ?id: String?, ?files: Array[Riffer::Messages::User::FilePart]) -> void
11
18
  def initialize(content, id: nil, files: [])
12
19
  super(content, id: id)
13
20
  @files = files
@@ -1,10 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Sentinel type for declaring boolean parameters — Ruby has no +Boolean+ class
5
- # (+true+/+false+ are +TrueClass+/+FalseClass+).
6
- #
7
- # required :verbose, Riffer::Params::Boolean
8
- #
4
+ # Ruby has no +Boolean+ class, so params declare booleans with this sentinel.
9
5
  module Riffer::Params::Boolean
10
6
  end
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # A single parameter definition, handling type validation and JSON Schema
5
- # generation.
6
4
  class Riffer::Params::Param
7
- # Maps Ruby types to JSON Schema type strings
8
5
  TYPE_MAPPINGS = {
9
6
  String => "string",
10
7
  Integer => "integer",
@@ -16,11 +13,8 @@ class Riffer::Params::Param
16
13
  Hash => "object",
17
14
  }.freeze #: Hash[Module, String]
18
15
 
19
- # Primitive types allowed for the <tt>of:</tt> keyword on Array params
20
16
  PRIMITIVE_TYPES = (TYPE_MAPPINGS.keys - [Array, Hash]).freeze #: Array[Module]
21
17
 
22
- # Maps JSON Schema type strings back to Ruby types (inverse of TYPE_MAPPINGS),
23
- # collapsing the three boolean spellings onto Riffer::Params::Boolean.
24
18
  JSON_TYPE_MAPPINGS = {
25
19
  "string" => String,
26
20
  "integer" => Integer,
@@ -30,32 +24,22 @@ class Riffer::Params::Param
30
24
  "object" => Hash,
31
25
  }.freeze #: Hash[String, Module]
32
26
 
33
- # The parameter name.
34
27
  attr_reader :name #: Symbol # @dynamic name
35
28
 
36
- # The Ruby type.
37
29
  attr_reader :type #: Module # @dynamic type
38
30
 
39
- # Whether the parameter is required.
40
31
  attr_reader :required #: bool # @dynamic required
41
32
 
42
- # The parameter description, if any.
43
33
  attr_reader :description #: String? # @dynamic description
44
34
 
45
- # Allowed values, if constrained.
46
35
  attr_reader :enum #: Array[untyped]? # @dynamic enum
47
36
 
48
- # The default value, if any.
49
37
  attr_reader :default #: untyped # @dynamic default
50
38
 
51
- # Element type for a typed array (+of:+).
52
39
  attr_reader :item_type #: Module? # @dynamic item_type
53
40
 
54
- # Nested Params for object / array-of-object types.
55
41
  attr_reader :nested_params #: Riffer::Params? # @dynamic nested_params
56
42
 
57
- # Reconstructs a Param from a single JSON Schema property. Raises
58
- # Riffer::ArgumentError on a type outside the Params-expressible subset.
59
43
  #--
60
44
  #: (Symbol, Hash[Symbol, untyped], required: bool) -> Riffer::Params::Param
61
45
  def self.from_json_schema(name, schema, required:)
@@ -88,9 +72,6 @@ class Riffer::Params::Param
88
72
  end
89
73
  private_class_method :resolve_nesting
90
74
 
91
- # Resolves a JSON Schema +type+ (or a <tt>[type, "null"]</tt> union) to its
92
- # Ruby type. Returns a Module — Riffer::Params::Boolean is a Module, not a
93
- # Class. Raises Riffer::ArgumentError on an unsupported type.
94
75
  #--
95
76
  #: (untyped) -> Module
96
77
  def self.json_type_to_ruby(type)
@@ -112,8 +93,6 @@ class Riffer::Params::Param
112
93
  @nested_params = nested_params
113
94
  end
114
95
 
115
- # Validates that a value matches the expected type.
116
- #
117
96
  #--
118
97
  #: (untyped) -> bool
119
98
  def valid_type?(value)
@@ -128,28 +107,20 @@ class Riffer::Params::Param
128
107
  end
129
108
  end
130
109
 
131
- # Returns the JSON Schema type name for this parameter.
132
- #
133
110
  #--
134
111
  #: () -> String
135
112
  def type_name
136
113
  TYPE_MAPPINGS[type] || type.to_s.downcase
137
114
  end
138
115
 
139
- # Converts this parameter to JSON Schema format. When +strict+, optional
140
- # params are made nullable (<tt>["type", "null"]</tt>) so strict providers
141
- # distinguish absent from present; optional params with an +enum+ use +anyOf+
142
- # instead, since providers like Anthropic reject
143
- # <tt>{"type": ["string", "null"], "enum": [...]}</tt>. Raises
144
- # Riffer::ArgumentError when +strict+ and a Hash param has no block or an
145
- # Array param has neither a block nor <tt>of:</tt>, since strict providers
146
- # reject objects without +properties+ and arrays without +items+.
147
116
  #--
148
117
  #: (?strict: bool) -> Hash[Symbol, untyped]
149
118
  def to_json_schema(strict: false)
150
119
  validate_strict_shape! if strict
120
+ # Nullable so strict providers can distinguish absent from present.
151
121
  nullable = strict && !required
152
122
 
123
+ # Providers like Anthropic reject a nullable type union combined with enum.
153
124
  if nullable && enum
154
125
  schema = { anyOf: [{ type: type_name, enum: enum }, { type: "null" }] } #: Hash[Symbol, untyped]
155
126
  schema[:description] = description if description
@@ -181,9 +152,22 @@ class Riffer::Params::Param
181
152
 
182
153
  private
183
154
 
155
+ # +dup+ would leave the copy sharing this one's nested Params, enum list and
156
+ # default, so defining a parameter or editing either value on one would reach
157
+ # the other.
158
+ #--
159
+ #: (Riffer::Params::Param) -> void
160
+ def initialize_copy(source)
161
+ super
162
+ @nested_params = source.nested_params&.dup
163
+ @enum = Riffer::Helpers::DeepDup.call(source.enum)
164
+ @default = Riffer::Helpers::DeepDup.call(source.default)
165
+ end
166
+
184
167
  #--
185
168
  #: () -> void
186
169
  def validate_strict_shape!
170
+ # Strict providers reject objects without properties and arrays without items.
187
171
  if type == Hash && nested_params.nil?
188
172
  raise Riffer::ArgumentError,
189
173
  "#{name}: a Hash param requires a block defining its properties under strict schemas"
data/lib/riffer/params.rb CHANGED
@@ -1,16 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # A DSL for defining tool parameters and structured-output schemas, used within
5
- # a Tool's +params+ block.
6
- #
7
- # params do
8
- # required :city, String, description: "The city name"
9
- # optional :units, String, default: "celsius", enum: ["celsius", "fahrenheit"]
10
- # end
11
- #
12
4
  class Riffer::Params
13
- # The defined parameters.
14
5
  attr_reader :parameters #: Array[Riffer::Params::Param] # @dynamic parameters
15
6
 
16
7
  #--
@@ -19,13 +10,8 @@ class Riffer::Params
19
10
  @parameters = []
20
11
  end
21
12
 
22
- # Reconstructs a Params from a JSON Schema object — the inverse of
23
- # +to_json_schema(strict: false)+. Raises Riffer::ArgumentError on features
24
- # outside the Params-expressible subset of JSON Schema.
25
- #
26
- # schema = params.to_json_schema(strict: false)
27
- # Riffer::Params.from_json_schema(schema) # => equivalent Riffer::Params
28
- #
13
+ # The inverse of +to_json_schema(strict: false)+. Raises Riffer::ArgumentError
14
+ # on features outside the Params-expressible subset of JSON Schema.
29
15
  #--
30
16
  #: (Hash[Symbol, untyped]) -> Riffer::Params
31
17
  def self.from_json_schema(schema)
@@ -42,8 +28,6 @@ class Riffer::Params
42
28
  params
43
29
  end
44
30
 
45
- # Defines a required parameter.
46
- #
47
31
  #--
48
32
  #: (Symbol, Module, ?description: String?, ?enum: Array[untyped]?, ?of: Module?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> void
49
33
  def required(name, type, description: nil, enum: nil, of: nil, &)
@@ -59,8 +43,6 @@ class Riffer::Params
59
43
  )
60
44
  end
61
45
 
62
- # Defines an optional parameter.
63
- #
64
46
  #--
65
47
  #: (Symbol, Module, ?description: String?, ?enum: Array[untyped]?, ?default: untyped, ?of: Module?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> void
66
48
  def optional(name, type, description: nil, enum: nil, default: nil, of: nil, &)
@@ -77,15 +59,6 @@ class Riffer::Params
77
59
  )
78
60
  end
79
61
 
80
- # Validates arguments against parameter definitions.
81
- #
82
- # A Float param accepts an Integer (JSON Schema <tt>"number"</tt> covers
83
- # integers) and its value is coerced with +to_f+, so callers always get a
84
- # Float. The same holds for the items of an <tt>of: Float</tt> array. No other
85
- # type is coerced.
86
- #
87
- # Raises Riffer::ValidationError if validation fails.
88
- #
89
62
  #--
90
63
  #: (Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
91
64
  def validate(arguments)
@@ -125,11 +98,6 @@ class Riffer::Params
125
98
  validated
126
99
  end
127
100
 
128
- # Converts all parameters to JSON Schema format. When +strict+ is true, every
129
- # property is listed in +required+ and optional ones are made nullable
130
- # instead, satisfying providers that enforce strict structured output schemas.
131
- # Raises Riffer::ArgumentError when +strict+ and a Hash param (at any depth)
132
- # has no block or an Array param has neither a block nor <tt>of:</tt>.
133
101
  #--
134
102
  #: (?strict: bool) -> Hash[Symbol, untyped]
135
103
  def to_json_schema(strict: false)
@@ -138,6 +106,8 @@ class Riffer::Params
138
106
 
139
107
  @parameters.each do |param|
140
108
  properties[param.name.to_s] = param.to_json_schema(strict: strict)
109
+ # Providers enforcing strict structured output require every property in
110
+ # +required+; strict mode makes optional ones nullable instead.
141
111
  required_params << param.name.to_s if strict || param.required
142
112
  end
143
113
 
@@ -151,6 +121,15 @@ class Riffer::Params
151
121
 
152
122
  private
153
123
 
124
+ # Without this, +dup+ shares parameters, so defining one on either copy
125
+ # would reach the other.
126
+ #--
127
+ #: (Riffer::Params) -> void
128
+ def initialize_copy(source)
129
+ super
130
+ @parameters = source.parameters.map(&:dup)
131
+ end
132
+
154
133
  #--
155
134
  #: (Module, Module?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
156
135
  def build_nested(type, of, &block)
@@ -224,7 +203,6 @@ class Riffer::Params
224
203
  end
225
204
  end
226
205
 
227
- # Returns the array with its valid items coerced by +coerce_value+.
228
206
  #--
229
207
  #: (Riffer::Params::Param, Array[untyped], Array[String]) -> Array[untyped]
230
208
  def validate_typed_array(param, value, errors)
@@ -249,13 +227,11 @@ class Riffer::Params
249
227
  end
250
228
  end
251
229
 
252
- # Coerces an already-validated value to the Ruby type its param declares.
253
- # Only Float coerces today, because JSON Schema "number" accepts integers and
254
- # callers should not get a type that depends on whether the model wrote a
255
- # decimal point. Add a branch here rather than inline at a call site.
256
230
  #--
257
231
  #: (Module, untyped) -> untyped
258
232
  def coerce_value(type, value)
233
+ # JSON Schema "number" accepts integers; callers shouldn't get a type that
234
+ # depends on whether the model wrote a decimal point.
259
235
  return value.to_f if type == Float
260
236
 
261
237
  value