activeagent 1.0.0.rc1 → 1.0.1

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 (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +102 -1
  3. data/lib/active_agent/providers/_base_provider.rb +94 -82
  4. data/lib/active_agent/providers/anthropic/_types.rb +2 -2
  5. data/lib/active_agent/providers/anthropic/options.rb +4 -6
  6. data/lib/active_agent/providers/anthropic/request.rb +157 -78
  7. data/lib/active_agent/providers/anthropic/transforms.rb +482 -0
  8. data/lib/active_agent/providers/anthropic_provider.rb +159 -59
  9. data/lib/active_agent/providers/common/messages/_types.rb +46 -3
  10. data/lib/active_agent/providers/common/messages/assistant.rb +20 -4
  11. data/lib/active_agent/providers/common/responses/base.rb +118 -70
  12. data/lib/active_agent/providers/common/usage.rb +385 -0
  13. data/lib/active_agent/providers/concerns/instrumentation.rb +263 -0
  14. data/lib/active_agent/providers/concerns/previewable.rb +39 -5
  15. data/lib/active_agent/providers/concerns/tool_choice_clearing.rb +62 -0
  16. data/lib/active_agent/providers/log_subscriber.rb +64 -246
  17. data/lib/active_agent/providers/mock_provider.rb +23 -23
  18. data/lib/active_agent/providers/ollama/chat/request.rb +214 -35
  19. data/lib/active_agent/providers/ollama/chat/transforms.rb +135 -0
  20. data/lib/active_agent/providers/ollama/embedding/request.rb +160 -47
  21. data/lib/active_agent/providers/ollama/embedding/transforms.rb +160 -0
  22. data/lib/active_agent/providers/ollama_provider.rb +0 -1
  23. data/lib/active_agent/providers/open_ai/_base.rb +3 -2
  24. data/lib/active_agent/providers/open_ai/chat/_types.rb +13 -1
  25. data/lib/active_agent/providers/open_ai/chat/request.rb +132 -186
  26. data/lib/active_agent/providers/open_ai/chat/transforms.rb +444 -0
  27. data/lib/active_agent/providers/open_ai/chat_provider.rb +95 -36
  28. data/lib/active_agent/providers/open_ai/embedding/_types.rb +13 -2
  29. data/lib/active_agent/providers/open_ai/embedding/request.rb +38 -70
  30. data/lib/active_agent/providers/open_ai/embedding/transforms.rb +88 -0
  31. data/lib/active_agent/providers/open_ai/responses/_types.rb +1 -7
  32. data/lib/active_agent/providers/open_ai/responses/request.rb +116 -135
  33. data/lib/active_agent/providers/open_ai/responses/transforms.rb +363 -0
  34. data/lib/active_agent/providers/open_ai/responses_provider.rb +115 -30
  35. data/lib/active_agent/providers/open_ai_provider.rb +0 -3
  36. data/lib/active_agent/providers/open_router/_types.rb +27 -1
  37. data/lib/active_agent/providers/open_router/options.rb +49 -1
  38. data/lib/active_agent/providers/open_router/request.rb +252 -66
  39. data/lib/active_agent/providers/open_router/requests/_types.rb +0 -1
  40. data/lib/active_agent/providers/open_router/requests/messages/_types.rb +37 -40
  41. data/lib/active_agent/providers/open_router/requests/messages/content/file.rb +19 -3
  42. data/lib/active_agent/providers/open_router/requests/messages/content/files/details.rb +15 -4
  43. data/lib/active_agent/providers/open_router/requests/plugin.rb +19 -3
  44. data/lib/active_agent/providers/open_router/requests/plugins/pdf_config.rb +30 -8
  45. data/lib/active_agent/providers/open_router/requests/prediction.rb +17 -0
  46. data/lib/active_agent/providers/open_router/requests/provider_preferences/max_price.rb +41 -7
  47. data/lib/active_agent/providers/open_router/requests/provider_preferences.rb +60 -19
  48. data/lib/active_agent/providers/open_router/requests/response_format.rb +30 -2
  49. data/lib/active_agent/providers/open_router/transforms.rb +164 -0
  50. data/lib/active_agent/providers/open_router_provider.rb +23 -0
  51. data/lib/active_agent/version.rb +1 -1
  52. metadata +17 -160
  53. data/lib/active_agent/generation_provider/open_router/types.rb +0 -505
  54. data/lib/active_agent/generation_provider/xai_provider.rb +0 -144
  55. data/lib/active_agent/providers/anthropic/requests/_types.rb +0 -190
  56. data/lib/active_agent/providers/anthropic/requests/container_params.rb +0 -19
  57. data/lib/active_agent/providers/anthropic/requests/content/base.rb +0 -21
  58. data/lib/active_agent/providers/anthropic/requests/content/sources/base.rb +0 -22
  59. data/lib/active_agent/providers/anthropic/requests/context_management_config.rb +0 -18
  60. data/lib/active_agent/providers/anthropic/requests/messages/_types.rb +0 -189
  61. data/lib/active_agent/providers/anthropic/requests/messages/assistant.rb +0 -23
  62. data/lib/active_agent/providers/anthropic/requests/messages/base.rb +0 -63
  63. data/lib/active_agent/providers/anthropic/requests/messages/content/_types.rb +0 -143
  64. data/lib/active_agent/providers/anthropic/requests/messages/content/base.rb +0 -21
  65. data/lib/active_agent/providers/anthropic/requests/messages/content/document.rb +0 -26
  66. data/lib/active_agent/providers/anthropic/requests/messages/content/image.rb +0 -23
  67. data/lib/active_agent/providers/anthropic/requests/messages/content/redacted_thinking.rb +0 -21
  68. data/lib/active_agent/providers/anthropic/requests/messages/content/search_result.rb +0 -27
  69. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/_types.rb +0 -171
  70. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/base.rb +0 -22
  71. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_base64.rb +0 -25
  72. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_file.rb +0 -23
  73. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_text.rb +0 -25
  74. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_url.rb +0 -23
  75. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_base64.rb +0 -27
  76. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_file.rb +0 -23
  77. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_url.rb +0 -23
  78. data/lib/active_agent/providers/anthropic/requests/messages/content/text.rb +0 -22
  79. data/lib/active_agent/providers/anthropic/requests/messages/content/thinking.rb +0 -23
  80. data/lib/active_agent/providers/anthropic/requests/messages/content/tool_result.rb +0 -24
  81. data/lib/active_agent/providers/anthropic/requests/messages/content/tool_use.rb +0 -28
  82. data/lib/active_agent/providers/anthropic/requests/messages/user.rb +0 -21
  83. data/lib/active_agent/providers/anthropic/requests/metadata.rb +0 -18
  84. data/lib/active_agent/providers/anthropic/requests/response_format.rb +0 -22
  85. data/lib/active_agent/providers/anthropic/requests/thinking_config/_types.rb +0 -60
  86. data/lib/active_agent/providers/anthropic/requests/thinking_config/base.rb +0 -20
  87. data/lib/active_agent/providers/anthropic/requests/thinking_config/disabled.rb +0 -16
  88. data/lib/active_agent/providers/anthropic/requests/thinking_config/enabled.rb +0 -20
  89. data/lib/active_agent/providers/anthropic/requests/tool_choice/_types.rb +0 -78
  90. data/lib/active_agent/providers/anthropic/requests/tool_choice/any.rb +0 -17
  91. data/lib/active_agent/providers/anthropic/requests/tool_choice/auto.rb +0 -17
  92. data/lib/active_agent/providers/anthropic/requests/tool_choice/base.rb +0 -20
  93. data/lib/active_agent/providers/anthropic/requests/tool_choice/none.rb +0 -16
  94. data/lib/active_agent/providers/anthropic/requests/tool_choice/tool.rb +0 -20
  95. data/lib/active_agent/providers/ollama/chat/requests/_types.rb +0 -3
  96. data/lib/active_agent/providers/ollama/chat/requests/messages/_types.rb +0 -116
  97. data/lib/active_agent/providers/ollama/chat/requests/messages/assistant.rb +0 -19
  98. data/lib/active_agent/providers/ollama/chat/requests/messages/user.rb +0 -19
  99. data/lib/active_agent/providers/ollama/embedding/requests/_types.rb +0 -83
  100. data/lib/active_agent/providers/ollama/embedding/requests/options.rb +0 -104
  101. data/lib/active_agent/providers/open_ai/chat/requests/_types.rb +0 -229
  102. data/lib/active_agent/providers/open_ai/chat/requests/audio.rb +0 -24
  103. data/lib/active_agent/providers/open_ai/chat/requests/messages/_types.rb +0 -123
  104. data/lib/active_agent/providers/open_ai/chat/requests/messages/assistant.rb +0 -42
  105. data/lib/active_agent/providers/open_ai/chat/requests/messages/base.rb +0 -78
  106. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/_types.rb +0 -133
  107. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/audio.rb +0 -35
  108. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/base.rb +0 -24
  109. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/file.rb +0 -26
  110. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/files/_types.rb +0 -60
  111. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/files/details.rb +0 -41
  112. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/image.rb +0 -37
  113. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/refusal.rb +0 -25
  114. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/text.rb +0 -25
  115. data/lib/active_agent/providers/open_ai/chat/requests/messages/developer.rb +0 -25
  116. data/lib/active_agent/providers/open_ai/chat/requests/messages/function.rb +0 -25
  117. data/lib/active_agent/providers/open_ai/chat/requests/messages/system.rb +0 -25
  118. data/lib/active_agent/providers/open_ai/chat/requests/messages/tool.rb +0 -26
  119. data/lib/active_agent/providers/open_ai/chat/requests/messages/user.rb +0 -32
  120. data/lib/active_agent/providers/open_ai/chat/requests/prediction.rb +0 -46
  121. data/lib/active_agent/providers/open_ai/chat/requests/response_format.rb +0 -53
  122. data/lib/active_agent/providers/open_ai/chat/requests/stream_options.rb +0 -24
  123. data/lib/active_agent/providers/open_ai/chat/requests/tool_choice.rb +0 -26
  124. data/lib/active_agent/providers/open_ai/chat/requests/tools/_types.rb +0 -5
  125. data/lib/active_agent/providers/open_ai/chat/requests/tools/base.rb +0 -22
  126. data/lib/active_agent/providers/open_ai/chat/requests/tools/custom_tool.rb +0 -41
  127. data/lib/active_agent/providers/open_ai/chat/requests/tools/function_tool.rb +0 -51
  128. data/lib/active_agent/providers/open_ai/chat/requests/web_search_options.rb +0 -45
  129. data/lib/active_agent/providers/open_ai/embedding/requests/_types.rb +0 -49
  130. data/lib/active_agent/providers/open_ai/responses/requests/_types.rb +0 -231
  131. data/lib/active_agent/providers/open_ai/responses/requests/conversation.rb +0 -23
  132. data/lib/active_agent/providers/open_ai/responses/requests/inputs/_types.rb +0 -264
  133. data/lib/active_agent/providers/open_ai/responses/requests/inputs/assistant_message.rb +0 -22
  134. data/lib/active_agent/providers/open_ai/responses/requests/inputs/base.rb +0 -89
  135. data/lib/active_agent/providers/open_ai/responses/requests/inputs/code_interpreter_tool_call.rb +0 -30
  136. data/lib/active_agent/providers/open_ai/responses/requests/inputs/computer_tool_call.rb +0 -28
  137. data/lib/active_agent/providers/open_ai/responses/requests/inputs/computer_tool_call_output.rb +0 -33
  138. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/_types.rb +0 -207
  139. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/base.rb +0 -22
  140. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_audio.rb +0 -26
  141. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_file.rb +0 -28
  142. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_image.rb +0 -28
  143. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_text.rb +0 -25
  144. data/lib/active_agent/providers/open_ai/responses/requests/inputs/custom_tool_call.rb +0 -28
  145. data/lib/active_agent/providers/open_ai/responses/requests/inputs/custom_tool_call_output.rb +0 -27
  146. data/lib/active_agent/providers/open_ai/responses/requests/inputs/developer_message.rb +0 -20
  147. data/lib/active_agent/providers/open_ai/responses/requests/inputs/file_search_tool_call.rb +0 -25
  148. data/lib/active_agent/providers/open_ai/responses/requests/inputs/function_call_output.rb +0 -32
  149. data/lib/active_agent/providers/open_ai/responses/requests/inputs/function_tool_call.rb +0 -28
  150. data/lib/active_agent/providers/open_ai/responses/requests/inputs/image_gen_tool_call.rb +0 -27
  151. data/lib/active_agent/providers/open_ai/responses/requests/inputs/input_message.rb +0 -31
  152. data/lib/active_agent/providers/open_ai/responses/requests/inputs/item_reference.rb +0 -23
  153. data/lib/active_agent/providers/open_ai/responses/requests/inputs/local_shell_tool_call.rb +0 -26
  154. data/lib/active_agent/providers/open_ai/responses/requests/inputs/local_shell_tool_call_output.rb +0 -33
  155. data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_approval_request.rb +0 -30
  156. data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_approval_response.rb +0 -28
  157. data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_list_tools.rb +0 -29
  158. data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_tool_call.rb +0 -35
  159. data/lib/active_agent/providers/open_ai/responses/requests/inputs/output_message.rb +0 -35
  160. data/lib/active_agent/providers/open_ai/responses/requests/inputs/reasoning.rb +0 -33
  161. data/lib/active_agent/providers/open_ai/responses/requests/inputs/system_message.rb +0 -20
  162. data/lib/active_agent/providers/open_ai/responses/requests/inputs/tool_call_base.rb +0 -27
  163. data/lib/active_agent/providers/open_ai/responses/requests/inputs/tool_message.rb +0 -23
  164. data/lib/active_agent/providers/open_ai/responses/requests/inputs/user_message.rb +0 -20
  165. data/lib/active_agent/providers/open_ai/responses/requests/inputs/web_search_tool_call.rb +0 -24
  166. data/lib/active_agent/providers/open_ai/responses/requests/prompt_reference.rb +0 -23
  167. data/lib/active_agent/providers/open_ai/responses/requests/reasoning.rb +0 -23
  168. data/lib/active_agent/providers/open_ai/responses/requests/stream_options.rb +0 -20
  169. data/lib/active_agent/providers/open_ai/responses/requests/text/_types.rb +0 -89
  170. data/lib/active_agent/providers/open_ai/responses/requests/text/base.rb +0 -22
  171. data/lib/active_agent/providers/open_ai/responses/requests/text/json_object.rb +0 -20
  172. data/lib/active_agent/providers/open_ai/responses/requests/text/json_schema.rb +0 -48
  173. data/lib/active_agent/providers/open_ai/responses/requests/text/plain.rb +0 -20
  174. data/lib/active_agent/providers/open_ai/responses/requests/text.rb +0 -41
  175. data/lib/active_agent/providers/open_ai/responses/requests/tool_choice.rb +0 -26
  176. data/lib/active_agent/providers/open_ai/responses/requests/tools/_types.rb +0 -112
  177. data/lib/active_agent/providers/open_ai/responses/requests/tools/base.rb +0 -25
  178. data/lib/active_agent/providers/open_ai/responses/requests/tools/code_interpreter_tool.rb +0 -23
  179. data/lib/active_agent/providers/open_ai/responses/requests/tools/computer_tool.rb +0 -27
  180. data/lib/active_agent/providers/open_ai/responses/requests/tools/custom_tool.rb +0 -28
  181. data/lib/active_agent/providers/open_ai/responses/requests/tools/file_search_tool.rb +0 -27
  182. data/lib/active_agent/providers/open_ai/responses/requests/tools/function_tool.rb +0 -29
  183. data/lib/active_agent/providers/open_ai/responses/requests/tools/image_generation_tool.rb +0 -37
  184. data/lib/active_agent/providers/open_ai/responses/requests/tools/local_shell_tool.rb +0 -21
  185. data/lib/active_agent/providers/open_ai/responses/requests/tools/mcp_tool.rb +0 -41
  186. data/lib/active_agent/providers/open_ai/responses/requests/tools/web_search_preview_tool.rb +0 -24
  187. data/lib/active_agent/providers/open_ai/responses/requests/tools/web_search_tool.rb +0 -25
  188. data/lib/active_agent/providers/open_ai/schema.yml +0 -65937
  189. data/lib/active_agent/providers/open_router/requests/message.rb +0 -1
  190. data/lib/active_agent/providers/open_router/requests/messages/assistant.rb +0 -20
  191. data/lib/active_agent/providers/open_router/requests/messages/user.rb +0 -30
@@ -8,11 +8,48 @@ require_relative "requests/provider_preferences"
8
8
  module ActiveAgent
9
9
  module Providers
10
10
  module OpenRouter
11
+ # Configuration options for OpenRouter provider
12
+ #
13
+ # Extends OpenAI::Options with OpenRouter-specific settings including
14
+ # HTTP-Referer and X-Title headers for app identification and ranking.
15
+ #
16
+ # @example Basic configuration
17
+ # options = Options.new(
18
+ # api_key: 'sk-or-v1-...',
19
+ # app_name: 'MyApp',
20
+ # site_url: 'https://myapp.com'
21
+ # )
22
+ #
23
+ # @example Rails auto-configuration
24
+ # # Automatically resolves app_name from Rails.application
25
+ # # and site_url from routes.default_url_options
26
+ # options = Options.new(api_key: ENV['OPENROUTER_API_KEY'])
27
+ #
28
+ # @see https://openrouter.ai/docs/api-keys OpenRouter API Keys
29
+ # @see https://openrouter.ai/docs/rankings OpenRouter App Rankings
11
30
  class Options < ActiveAgent::Providers::OpenAI::Options
31
+ # @!attribute base_url
32
+ # @return [String] API endpoint (default: "https://openrouter.ai/api/v1")
12
33
  attribute :base_url, :string, as: "https://openrouter.ai/api/v1"
34
+
35
+ # @!attribute app_name
36
+ # @return [String] application name for X-Title header (default: "ActiveAgent" or Rails app name)
13
37
  attribute :app_name, :string, fallback: "ActiveAgent"
38
+
39
+ # @!attribute site_url
40
+ # @return [String] site URL for HTTP-Referer header (default: "https://activeagents.ai/" or Rails URL)
14
41
  attribute :site_url, :string, fallback: "https://activeagents.ai/"
15
42
 
43
+ # Creates new OpenRouter options with auto-resolution
44
+ #
45
+ # Automatically resolves app_name from Rails application name and
46
+ # site_url from Rails routes/ActionMailer default_url_options.
47
+ #
48
+ # @param kwargs [Hash] configuration options
49
+ # @option kwargs [String] :api_key OpenRouter API key
50
+ # @option kwargs [String] :app_name application name for rankings
51
+ # @option kwargs [String] :site_url site URL for rankings
52
+ # @return [Options]
16
53
  def initialize(kwargs = {})
17
54
  kwargs = kwargs.deep_symbolize_keys if kwargs.respond_to?(:deep_symbolize_keys)
18
55
 
@@ -22,11 +59,22 @@ module ActiveAgent
22
59
  )))
23
60
  end
24
61
 
62
+ # Serializes options for API requests
63
+ #
64
+ # Excludes app_name and site_url as they're sent via headers.
65
+ #
66
+ # @return [Hash] serialized options
25
67
  def serialize
26
68
  super.except(:app_name, :site_url)
27
69
  end
28
70
 
29
- # We fallback to ActiveAgent but allow empty strings to unset
71
+ # Returns extra headers for OpenRouter API
72
+ #
73
+ # Maps app_name and site_url to OpenRouter's required headers:
74
+ # - HTTP-Referer: site_url
75
+ # - X-Title: app_name
76
+ #
77
+ # @return [Hash] headers hash
30
78
  def extra_headers
31
79
  deep_compact(
32
80
  "http-referer" => site_url.presence,
@@ -1,81 +1,267 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../open_ai/_types"
3
+ require "delegate"
4
+ require "json"
5
+ require_relative "transforms"
4
6
  require_relative "requests/_types"
5
7
 
6
8
  module ActiveAgent
7
9
  module Providers
8
10
  module OpenRouter
9
- class Request < OpenAI::Chat::Request
10
- # Prompting Options
11
- attribute :model, :string, fallback: "openrouter/auto"
12
- attribute :response_format, Requests::ResponseFormatType.new
13
- attribute :max_tokens, :integer
14
- attribute :stop # Can be string or array
15
-
16
- # Messages array (required)
17
- attribute :messages, Requests::Messages::MessagesType.new
18
-
19
- # LLM Parameters
20
- attribute :seed, :integer
21
- attribute :top_p, :float
22
- attribute :top_k, :integer
23
- attribute :frequency_penalty, :float
24
- attribute :presence_penalty, :float
25
- attribute :repetition_penalty, :float
26
- attribute :top_logprobs, :integer
27
- attribute :min_p, :float
28
- attribute :top_a, :float
29
- attribute :logit_bias # Hash of token_id => bias value
30
-
31
- # Tool calling (inherited from OpenAI but explicitly documented)
32
- # attribute :tools, :json
33
- # attribute :tool_choice, :json
34
-
35
- # Predicted outputs
36
- attribute :prediction, Requests::PredictionType.new
37
-
38
- # OpenRouter-specific parameters
39
- attribute :transforms, default: -> { [] } # Array of strings
40
- attribute :models, default: -> { [] } # Array of model strings for fallback
41
- attribute :route, :string, default: "fallback"
42
- attribute :provider, Requests::ProviderPreferencesType.new, default: {}
43
- attribute :user, :string # Stable identifier for end-users
44
- attribute :plugins, Requests::PluginsType.new # Array of plugin configurations (e.g., file-parser for PDFs)
45
-
46
- # Validations for parameters with specific ranges
47
- validates :max_tokens, numericality: { greater_than_or_equal_to: 1 }, allow_nil: true
48
- validates :top_p, numericality: { greater_than: 0, less_than_or_equal_to: 1 }, allow_nil: true
49
- validates :top_k, numericality: { greater_than_or_equal_to: 1 }, allow_nil: true
50
- validates :frequency_penalty, numericality: { greater_than_or_equal_to: -2, less_than_or_equal_to: 2 }, allow_nil: true
51
- validates :presence_penalty, numericality: { greater_than_or_equal_to: -2, less_than_or_equal_to: 2 }, allow_nil: true
52
- validates :repetition_penalty, numericality: { greater_than: 0, less_than_or_equal_to: 2 }, allow_nil: true
53
- validates :min_p, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 }, allow_nil: true
54
- validates :top_a, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 }, allow_nil: true
55
- validates :route, inclusion: { in: [ "fallback" ] }, allow_nil: true
56
-
57
- # Backwards Compatibility
58
- delegate_attributes :data_collection, :enable_fallbacks, :sort, :ignore, :only, :quantizations, :max_price, to: :provider
59
- alias_attribute :fallback_models, :models
60
-
61
- # Common Format Compatability
11
+ # Wraps OpenAI gem's CompletionCreateParams with OpenRouter-specific extensions
12
+ #
13
+ # Delegates to OpenAI::Models::Chat::CompletionCreateParams for OpenAI-compatible
14
+ # parameters while adding support for OpenRouter-specific features like plugins,
15
+ # provider preferences, model fallbacks, and extended sampling parameters.
16
+ #
17
+ # OpenRouter-specific parameters:
18
+ # - plugins: Array of plugin configurations (e.g., file-parser for PDFs)
19
+ # - provider: ProviderPreferences object with require_parameters, data_collection, etc.
20
+ # - transforms: Array of transformation strings
21
+ # - models: Array of model strings for fallback routing
22
+ # - route: Routing strategy (default: "fallback")
23
+ # - top_k, min_p, top_a, repetition_penalty: Extended sampling parameters
24
+ #
25
+ # @example Basic usage
26
+ # request = Request.new(
27
+ # model: "openai/gpt-4",
28
+ # messages: [{role: "user", content: "Hello"}]
29
+ # )
30
+ #
31
+ # @example With OpenRouter-specific features
32
+ # request = Request.new(
33
+ # model: "openai/gpt-4",
34
+ # messages: [{role: "user", content: "Hello"}],
35
+ # models: ["anthropic/claude-3", "openai/gpt-4"],
36
+ # provider: {require_parameters: true}
37
+ # )
38
+ class Request < SimpleDelegator
39
+ # Default parameter values
40
+ DEFAULTS = {
41
+ frequency_penalty: 0,
42
+ logprobs: false,
43
+ n: 1,
44
+ presence_penalty: 0,
45
+ temperature: 1,
46
+ top_p: 1,
47
+ route: "fallback",
48
+ models: [],
49
+ transforms: []
50
+ }.freeze
51
+
52
+ # @return [Boolean, nil]
53
+ attr_reader :stream
54
+
55
+ # @return [Hash] OpenRouter-specific parameters
56
+ attr_reader :openrouter_params
57
+
58
+ # Creates a new OpenRouter request
59
+ #
60
+ # @param params [Hash] request parameters
61
+ # @option params [String] :model model identifier (default: "openrouter/auto")
62
+ # @option params [Array, String, Hash] :messages required conversation messages
63
+ # @option params [Array] :plugins plugin configurations
64
+ # @option params [Hash] :provider provider preferences
65
+ # @option params [Array<String>] :transforms transformation strings
66
+ # @option params [Array<String>] :models fallback model list
67
+ # @option params [String] :route routing strategy
68
+ # @option params [Integer] :top_k sampling parameter
69
+ # @option params [Float] :min_p minimum probability sampling
70
+ # @option params [Float] :top_a top-a sampling
71
+ # @option params [Float] :repetition_penalty repetition penalty
72
+ # @raise [ArgumentError] when parameters are invalid
73
+ def initialize(**params)
74
+ # Step 1: Extract stream flag
75
+ @stream = params[:stream]
76
+
77
+ # Step 2: Apply defaults
78
+ params = apply_defaults(params)
79
+
80
+ # Step 3: Normalize parameters and split into OpenAI vs OpenRouter-specific
81
+ # This handles response_format special logic for structured output
82
+ openai_params, @openrouter_params = Transforms.normalize_params(params)
83
+
84
+ # Step 4: Create gem model with OpenAI-compatible params
85
+ gem_model = ::OpenAI::Models::Chat::CompletionCreateParams.new(**openai_params)
86
+
87
+ # Step 5: Delegate to the gem model
88
+ super(gem_model)
89
+ rescue ArgumentError => e
90
+ raise ArgumentError, "Invalid OpenRouter request parameters: #{e.message}"
91
+ end
92
+
93
+ # Serializes request for API submission
94
+ #
95
+ # Merges OpenAI-compatible parameters with OpenRouter-specific extensions.
96
+ #
97
+ # @return [Hash] cleaned request hash
98
+ def serialize
99
+ # Get OpenAI params from gem model
100
+ openai_hash = Transforms.gem_to_hash(__getobj__)
101
+
102
+ # Merge with OpenRouter-specific params
103
+ Transforms.cleanup_serialized_request(openai_hash, @openrouter_params, DEFAULTS, __getobj__)
104
+ end
105
+
106
+ # @return [Array<Hash>, nil]
107
+ def messages
108
+ __getobj__.instance_variable_get(:@data)[:messages]
109
+ end
110
+
111
+ # Sets messages with normalization
112
+ #
113
+ # Merges new messages with existing ones for compatibility.
114
+ #
115
+ # @param value [Array, String, Hash]
116
+ # @return [void]
62
117
  def messages=(value)
63
- case value
64
- when Array
65
- super((messages || []) | value)
66
- else
67
- super((messages || []) | [ value ])
68
- end
118
+ normalized_value = Transforms.normalize_messages(value)
119
+ current_messages = messages || []
120
+
121
+ # Merge behavior for OpenRouter compatibility
122
+ merged = current_messages | Array(normalized_value)
123
+ __getobj__.instance_variable_get(:@data)[:messages] = merged
124
+ end
125
+
126
+ # Alias for messages (common format compatibility)
127
+ #
128
+ # @return [Array<Hash>, nil]
129
+ def message
130
+ messages
131
+ end
132
+
133
+ # @param value [Array, String, Hash]
134
+ # @return [void]
135
+ def message=(value)
136
+ self.messages = value
137
+ end
138
+
139
+ # Sets instructions as developer messages
140
+ #
141
+ # Prepends developer messages to the messages array.
142
+ #
143
+ # @param values [Array<String>, String]
144
+ # @return [void]
145
+ def instructions=(*values)
146
+ instructions_messages = OpenAI::Chat::Transforms.normalize_instructions(values.flatten)
147
+ current_messages = messages || []
148
+ self.messages = instructions_messages + current_messages
69
149
  end
70
150
 
71
- # Common Format Compatability
72
- def response_format=(value)
73
- # If we are doing structured output, we need to ensure that we route to models that support it.
74
- if %i[json_object json_schema].include?(value[:type].to_sym)
75
- self.provider.require_parameters = true
151
+ # Gets tool_choice bypassing gem validation
152
+ #
153
+ # OpenRouter supports "any" which isn't valid in OpenAI gem types.
154
+ #
155
+ # @return [String, Hash, nil]
156
+ def tool_choice
157
+ __getobj__.instance_variable_get(:@data)[:tool_choice]
158
+ end
159
+
160
+ # Sets tool_choice bypassing gem validation
161
+ #
162
+ # OpenRouter supports "any" which isn't valid in OpenAI gem types,
163
+ # so we bypass the gem's type validation by setting @data directly.
164
+ #
165
+ # @param value [String, Hash, nil]
166
+ # @return [void]
167
+ def tool_choice=(value)
168
+ __getobj__.instance_variable_get(:@data)[:tool_choice] = value
169
+ end
170
+
171
+ # Accessor for OpenRouter-specific provider preferences
172
+ #
173
+ # @return [Hash, nil]
174
+ def provider
175
+ @openrouter_params[:provider]
176
+ end
177
+
178
+ # Sets provider preferences
179
+ #
180
+ # @param value [Hash]
181
+ # @return [void]
182
+ def provider=(value)
183
+ @openrouter_params[:provider] = value
184
+ end
185
+
186
+ # Accessor for OpenRouter plugins
187
+ #
188
+ # @return [Array, nil]
189
+ def plugins
190
+ @openrouter_params[:plugins]
191
+ end
192
+
193
+ # Sets plugins
194
+ #
195
+ # @param value [Array]
196
+ # @return [void]
197
+ def plugins=(value)
198
+ @openrouter_params[:plugins] = value
199
+ end
200
+
201
+ # Accessor for OpenRouter transforms
202
+ #
203
+ # @return [Array]
204
+ def transforms
205
+ @openrouter_params[:transforms] || []
206
+ end
207
+
208
+ # Sets transforms
209
+ #
210
+ # @param value [Array]
211
+ # @return [void]
212
+ def transforms=(value)
213
+ @openrouter_params[:transforms] = value
214
+ end
215
+
216
+ # Accessor for fallback models
217
+ #
218
+ # @return [Array]
219
+ def models
220
+ @openrouter_params[:models] || []
221
+ end
222
+
223
+ # Sets fallback models
224
+ #
225
+ # @param value [Array]
226
+ # @return [void]
227
+ def models=(value)
228
+ @openrouter_params[:models] = value
229
+ end
230
+
231
+ # Alias for backwards compatibility
232
+ alias_method :fallback_models, :models
233
+ alias_method :fallback_models=, :models=
234
+
235
+ # Accessor for routing strategy
236
+ #
237
+ # @return [String]
238
+ def route
239
+ @openrouter_params[:route] || DEFAULTS[:route]
240
+ end
241
+
242
+ # Sets routing strategy
243
+ #
244
+ # @param value [String]
245
+ # @return [void]
246
+ def route=(value)
247
+ @openrouter_params[:route] = value
248
+ end
249
+
250
+ private
251
+
252
+ # @api private
253
+ # @param params [Hash]
254
+ # @return [Hash]
255
+ def apply_defaults(params)
256
+ # Set default model if not provided
257
+ params[:model] ||= "openrouter/auto"
258
+
259
+ # Apply other defaults
260
+ DEFAULTS.each do |key, value|
261
+ params[key] = value unless params.key?(key)
76
262
  end
77
263
 
78
- super(value)
264
+ params
79
265
  end
80
266
  end
81
267
  end
@@ -4,7 +4,6 @@ require_relative "messages/_types"
4
4
  require_relative "provider_preferences/_types"
5
5
  require_relative "plugins/_types"
6
6
 
7
- require_relative "message"
8
7
  require_relative "prediction"
9
8
  require_relative "provider_preferences"
10
9
  require_relative "response_format"
@@ -1,55 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_agent/providers/open_ai/chat/requests/messages/_types"
4
-
5
- require_relative "assistant"
6
- require_relative "user"
3
+ require_relative "../../transforms"
7
4
 
8
5
  module ActiveAgent
9
6
  module Providers
10
7
  module OpenRouter
11
8
  module Requests
12
9
  module Messages
13
- # Type for Messages array - uses OpenRouter's MessageType
14
- class MessagesType < OpenAI::Chat::Requests::Messages::MessagesType
15
- def initialize
16
- super
17
- @message_type = MessageType.new
10
+ # ActiveModel type for casting and normalizing messages
11
+ #
12
+ # Delegates to OpenRouter transforms which use OpenAI's message normalization.
13
+ class MessagesType < ActiveModel::Type::Value
14
+ # Casts value to normalized messages array
15
+ #
16
+ # @param value [Array, String, Hash, nil]
17
+ # @return [Array, nil]
18
+ def cast(value)
19
+ return nil if value.nil?
20
+ Transforms.normalize_messages(value)
21
+ end
22
+
23
+ # Serializes messages to hash array
24
+ #
25
+ # @param value [Array, nil]
26
+ # @return [Array, nil]
27
+ def serialize(value)
28
+ return nil if value.nil?
29
+
30
+ # If already serialized as hashes, return as-is
31
+ return value if value.is_a?(Array) && value.all? { |m| m.is_a?(Hash) }
32
+
33
+ # Otherwise convert gem objects to hashes
34
+ value.map { |msg| Transforms.gem_to_hash(msg) }
35
+ end
36
+
37
+ # @param value [Object]
38
+ # @return [Array, nil]
39
+ def deserialize(value)
40
+ cast(value)
18
41
  end
19
42
  end
20
43
 
21
- class MessageType < OpenAI::Chat::Requests::Messages::MessageType
44
+ # Kept for backwards compatibility but delegates to MessagesType
45
+ class MessageType < MessagesType
22
46
  def cast(value)
23
- case value
24
- when OpenAI::Chat::Requests::Messages::Base
25
- value
26
- when String
27
- User.new(content: value)
28
- when Hash
29
- hash = value.deep_symbolize_keys
30
- role = hash[:role]&.to_sym
31
-
32
- case role
33
- when :developer
34
- OpenAI::Chat::Requests::Messages::Developer.new(**hash)
35
- when :system
36
- OpenAI::Chat::Requests::Messages::System.new(**hash)
37
- when :user, nil
38
- User.new(**hash)
39
- when :assistant
40
- Assistant.new(**hash)
41
- when :tool
42
- OpenAI::Chat::Requests::Messages::Tool.new(**hash)
43
- when :function
44
- OpenAI::Chat::Requests::Messages::Function.new(**hash)
45
- else
46
- raise ArgumentError, "Unknown message role: #{role.inspect}"
47
- end
48
- when nil
49
- nil
50
- else
51
- raise ArgumentError, "Cannot cast #{value.class} to Message (expected Base, String, Hash, or nil)"
52
- end
47
+ # Single message - wrap in array then unwrap
48
+ result = super(value.is_a?(Array) ? value : [ value ])
49
+ result&.first
53
50
  end
54
51
  end
55
52
  end
@@ -9,12 +9,28 @@ module ActiveAgent
9
9
  module Requests
10
10
  module Messages
11
11
  module Content
12
- # File content part for OpenRouter.
12
+ # File content part for OpenRouter messages
13
13
  #
14
- # Uses OpenRouter's Files::DetailsType which preserves the data URI prefix
15
- # instead of stripping it like OpenAI does.
14
+ # Represents a file attachment in a message. Unlike OpenAI which strips
15
+ # the data URI prefix, OpenRouter preserves it in the file_data field.
16
+ #
17
+ # @example PDF file attachment
18
+ # file = File.new(
19
+ # file: {
20
+ # file_data: 'data:application/pdf;base64,JVBERi0...',
21
+ # filename: 'document.pdf'
22
+ # }
23
+ # )
24
+ #
25
+ # @see Files::Details
26
+ # @see https://openrouter.ai/docs/file-uploads OpenRouter File Uploads
16
27
  class File < OpenAI::Chat::Requests::Messages::Content::Base
28
+ # @!attribute type
29
+ # @return [String] always "file"
17
30
  attribute :type, :string, as: "file"
31
+
32
+ # @!attribute file
33
+ # @return [Files::Details] file details with data URI
18
34
  attribute :file, Files::DetailsType.new
19
35
 
20
36
  validates :file, presence: true
@@ -9,12 +9,23 @@ module ActiveAgent
9
9
  module Messages
10
10
  module Content
11
11
  module Files
12
- # Represents the nested file object within File content part for OpenRouter.
12
+ # File details for OpenRouter file attachments
13
13
  #
14
- # Unlike OpenAI which strips the data URI prefix, OpenRouter keeps it intact
15
- # (e.g., data:application/pdf;base64,) in the file_data field.
14
+ # Represents the nested file object within a file content part.
15
+ # Unlike OpenAI which strips the data URI prefix (e.g., data:application/pdf;base64,),
16
+ # OpenRouter requires it to be present in the file_data field.
17
+ #
18
+ # @example With data URI
19
+ # details = Details.new(
20
+ # file_data: 'data:application/pdf;base64,JVBERi0xLjQK...',
21
+ # filename: 'report.pdf'
22
+ # )
23
+ #
24
+ # @see Content::File
16
25
  class Details < OpenAI::Chat::Requests::Messages::Content::Files::Details
17
- # Override the setter to NOT strip the data URI prefix
26
+ # @!attribute file_data
27
+ # @return [String] file data with data URI prefix intact
28
+ # Format: "data:<mime-type>;base64,<base64-data>"
18
29
  attribute :file_data, :string
19
30
  end
20
31
  end
@@ -4,16 +4,32 @@ module ActiveAgent
4
4
  module Providers
5
5
  module OpenRouter
6
6
  module Requests
7
- # Represents a plugin configuration for OpenRouter requests.
8
- # Currently supports the file-parser plugin for PDF processing.
7
+ # Plugin configuration for OpenRouter requests
9
8
  #
10
- # @example
9
+ # OpenRouter supports plugins that enhance model capabilities.
10
+ # Currently supports the file-parser plugin for processing PDF documents.
11
+ #
12
+ # @example File parser plugin with PDF text extraction
11
13
  # plugin = Plugin.new(
12
14
  # id: 'file-parser',
13
15
  # pdf: { engine: 'pdf-text' }
14
16
  # )
17
+ #
18
+ # @example File parser plugin with OCR
19
+ # plugin = Plugin.new(
20
+ # id: 'file-parser',
21
+ # pdf: { engine: 'mistral-ocr' }
22
+ # )
23
+ #
24
+ # @see https://openrouter.ai/docs/plugins OpenRouter Plugins
25
+ # @see Plugins::PdfConfig
15
26
  class Plugin < Common::BaseModel
27
+ # @!attribute id
28
+ # @return [String] plugin identifier (currently only 'file-parser' is supported)
16
29
  attribute :id, :string
30
+
31
+ # @!attribute pdf
32
+ # @return [Plugins::PdfConfig, nil] PDF processing configuration
17
33
  attribute :pdf, Plugins::PdfConfigType.new
18
34
 
19
35
  validates :id, presence: true
@@ -5,19 +5,41 @@ module ActiveAgent
5
5
  module OpenRouter
6
6
  module Requests
7
7
  module Plugins
8
- # Configuration for PDF processing in the file-parser plugin.
8
+ # PDF processing configuration for file-parser plugin
9
9
  #
10
- # OpenRouter provides several PDF processing engines:
11
- # - "mistral-ocr": Best for scanned documents or PDFs with images ($2 per 1,000 pages)
12
- # - "pdf-text": Best for well-structured PDFs with clear text content (Free)
13
- # - "native": Only available for models that support file input natively (charged as input tokens)
10
+ # OpenRouter provides multiple PDF processing engines with different
11
+ # capabilities and costs:
14
12
  #
15
- # If you don't explicitly specify an engine, OpenRouter will default first to the model's
16
- # native file processing capabilities, and if that's not available, will use the "mistral-ocr" engine.
13
+ # - **mistral-ocr**: Best for scanned documents or PDFs with images
14
+ # - Cost: $2 per 1,000 pages
15
+ # - Use when: Document is image-heavy or has poor text extraction
17
16
  #
18
- # @example
17
+ # - **pdf-text**: Best for well-structured PDFs with clear text content
18
+ # - Cost: Free
19
+ # - Use when: Document has clean, extractable text
20
+ #
21
+ # - **native**: Use model's native file processing capabilities
22
+ # - Cost: Charged as input tokens
23
+ # - Use when: Model supports native file input
24
+ #
25
+ # If no engine is specified, OpenRouter defaults to the model's native
26
+ # file processing if available, otherwise uses mistral-ocr.
27
+ #
28
+ # @example Text extraction (free)
19
29
  # pdf_config = PdfConfig.new(engine: 'pdf-text')
30
+ #
31
+ # @example OCR for scanned documents
32
+ # pdf_config = PdfConfig.new(engine: 'mistral-ocr')
33
+ #
34
+ # @example Use model's native processing
35
+ # pdf_config = PdfConfig.new(engine: 'native')
36
+ #
37
+ # @see https://openrouter.ai/docs/plugins#file-parser OpenRouter File Parser Plugin
38
+ # @see Plugin
20
39
  class PdfConfig < Common::BaseModel
40
+ # @!attribute engine
41
+ # @return [String, nil] PDF processing engine
42
+ # Options: 'mistral-ocr', 'pdf-text', 'native'
21
43
  attribute :engine, :string
22
44
 
23
45
  validates :engine, inclusion: { in: %w[mistral-ocr pdf-text native] }, allow_nil: true