activeagent 1.0.0.rc1 → 1.0.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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -1
  3. data/lib/active_agent/providers/_base_provider.rb +92 -82
  4. data/lib/active_agent/providers/anthropic/_types.rb +2 -2
  5. data/lib/active_agent/providers/anthropic/request.rb +135 -81
  6. data/lib/active_agent/providers/anthropic/transforms.rb +353 -0
  7. data/lib/active_agent/providers/anthropic_provider.rb +96 -53
  8. data/lib/active_agent/providers/common/messages/_types.rb +37 -1
  9. data/lib/active_agent/providers/common/responses/base.rb +118 -70
  10. data/lib/active_agent/providers/common/usage.rb +385 -0
  11. data/lib/active_agent/providers/concerns/instrumentation.rb +263 -0
  12. data/lib/active_agent/providers/log_subscriber.rb +64 -246
  13. data/lib/active_agent/providers/mock_provider.rb +23 -23
  14. data/lib/active_agent/providers/ollama/chat/request.rb +214 -35
  15. data/lib/active_agent/providers/ollama/chat/transforms.rb +135 -0
  16. data/lib/active_agent/providers/ollama/embedding/request.rb +160 -47
  17. data/lib/active_agent/providers/ollama/embedding/transforms.rb +160 -0
  18. data/lib/active_agent/providers/ollama_provider.rb +0 -1
  19. data/lib/active_agent/providers/open_ai/_base.rb +3 -2
  20. data/lib/active_agent/providers/open_ai/chat/_types.rb +13 -1
  21. data/lib/active_agent/providers/open_ai/chat/request.rb +132 -186
  22. data/lib/active_agent/providers/open_ai/chat/transforms.rb +364 -0
  23. data/lib/active_agent/providers/open_ai/chat_provider.rb +57 -36
  24. data/lib/active_agent/providers/open_ai/embedding/_types.rb +13 -2
  25. data/lib/active_agent/providers/open_ai/embedding/request.rb +38 -70
  26. data/lib/active_agent/providers/open_ai/embedding/transforms.rb +88 -0
  27. data/lib/active_agent/providers/open_ai/responses/_types.rb +1 -7
  28. data/lib/active_agent/providers/open_ai/responses/request.rb +100 -134
  29. data/lib/active_agent/providers/open_ai/responses/transforms.rb +228 -0
  30. data/lib/active_agent/providers/open_ai/responses_provider.rb +77 -30
  31. data/lib/active_agent/providers/open_ai_provider.rb +0 -3
  32. data/lib/active_agent/providers/open_router/_types.rb +27 -1
  33. data/lib/active_agent/providers/open_router/options.rb +49 -1
  34. data/lib/active_agent/providers/open_router/request.rb +232 -66
  35. data/lib/active_agent/providers/open_router/requests/_types.rb +0 -1
  36. data/lib/active_agent/providers/open_router/requests/messages/_types.rb +37 -40
  37. data/lib/active_agent/providers/open_router/requests/messages/content/file.rb +19 -3
  38. data/lib/active_agent/providers/open_router/requests/messages/content/files/details.rb +15 -4
  39. data/lib/active_agent/providers/open_router/requests/plugin.rb +19 -3
  40. data/lib/active_agent/providers/open_router/requests/plugins/pdf_config.rb +30 -8
  41. data/lib/active_agent/providers/open_router/requests/prediction.rb +17 -0
  42. data/lib/active_agent/providers/open_router/requests/provider_preferences/max_price.rb +41 -7
  43. data/lib/active_agent/providers/open_router/requests/provider_preferences.rb +60 -19
  44. data/lib/active_agent/providers/open_router/requests/response_format.rb +30 -2
  45. data/lib/active_agent/providers/open_router/transforms.rb +134 -0
  46. data/lib/active_agent/providers/open_router_provider.rb +9 -0
  47. data/lib/active_agent/version.rb +1 -1
  48. metadata +15 -159
  49. data/lib/active_agent/generation_provider/open_router/types.rb +0 -505
  50. data/lib/active_agent/generation_provider/xai_provider.rb +0 -144
  51. data/lib/active_agent/providers/anthropic/requests/_types.rb +0 -190
  52. data/lib/active_agent/providers/anthropic/requests/container_params.rb +0 -19
  53. data/lib/active_agent/providers/anthropic/requests/content/base.rb +0 -21
  54. data/lib/active_agent/providers/anthropic/requests/content/sources/base.rb +0 -22
  55. data/lib/active_agent/providers/anthropic/requests/context_management_config.rb +0 -18
  56. data/lib/active_agent/providers/anthropic/requests/messages/_types.rb +0 -189
  57. data/lib/active_agent/providers/anthropic/requests/messages/assistant.rb +0 -23
  58. data/lib/active_agent/providers/anthropic/requests/messages/base.rb +0 -63
  59. data/lib/active_agent/providers/anthropic/requests/messages/content/_types.rb +0 -143
  60. data/lib/active_agent/providers/anthropic/requests/messages/content/base.rb +0 -21
  61. data/lib/active_agent/providers/anthropic/requests/messages/content/document.rb +0 -26
  62. data/lib/active_agent/providers/anthropic/requests/messages/content/image.rb +0 -23
  63. data/lib/active_agent/providers/anthropic/requests/messages/content/redacted_thinking.rb +0 -21
  64. data/lib/active_agent/providers/anthropic/requests/messages/content/search_result.rb +0 -27
  65. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/_types.rb +0 -171
  66. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/base.rb +0 -22
  67. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_base64.rb +0 -25
  68. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_file.rb +0 -23
  69. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_text.rb +0 -25
  70. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/document_url.rb +0 -23
  71. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_base64.rb +0 -27
  72. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_file.rb +0 -23
  73. data/lib/active_agent/providers/anthropic/requests/messages/content/sources/image_url.rb +0 -23
  74. data/lib/active_agent/providers/anthropic/requests/messages/content/text.rb +0 -22
  75. data/lib/active_agent/providers/anthropic/requests/messages/content/thinking.rb +0 -23
  76. data/lib/active_agent/providers/anthropic/requests/messages/content/tool_result.rb +0 -24
  77. data/lib/active_agent/providers/anthropic/requests/messages/content/tool_use.rb +0 -28
  78. data/lib/active_agent/providers/anthropic/requests/messages/user.rb +0 -21
  79. data/lib/active_agent/providers/anthropic/requests/metadata.rb +0 -18
  80. data/lib/active_agent/providers/anthropic/requests/response_format.rb +0 -22
  81. data/lib/active_agent/providers/anthropic/requests/thinking_config/_types.rb +0 -60
  82. data/lib/active_agent/providers/anthropic/requests/thinking_config/base.rb +0 -20
  83. data/lib/active_agent/providers/anthropic/requests/thinking_config/disabled.rb +0 -16
  84. data/lib/active_agent/providers/anthropic/requests/thinking_config/enabled.rb +0 -20
  85. data/lib/active_agent/providers/anthropic/requests/tool_choice/_types.rb +0 -78
  86. data/lib/active_agent/providers/anthropic/requests/tool_choice/any.rb +0 -17
  87. data/lib/active_agent/providers/anthropic/requests/tool_choice/auto.rb +0 -17
  88. data/lib/active_agent/providers/anthropic/requests/tool_choice/base.rb +0 -20
  89. data/lib/active_agent/providers/anthropic/requests/tool_choice/none.rb +0 -16
  90. data/lib/active_agent/providers/anthropic/requests/tool_choice/tool.rb +0 -20
  91. data/lib/active_agent/providers/ollama/chat/requests/_types.rb +0 -3
  92. data/lib/active_agent/providers/ollama/chat/requests/messages/_types.rb +0 -116
  93. data/lib/active_agent/providers/ollama/chat/requests/messages/assistant.rb +0 -19
  94. data/lib/active_agent/providers/ollama/chat/requests/messages/user.rb +0 -19
  95. data/lib/active_agent/providers/ollama/embedding/requests/_types.rb +0 -83
  96. data/lib/active_agent/providers/ollama/embedding/requests/options.rb +0 -104
  97. data/lib/active_agent/providers/open_ai/chat/requests/_types.rb +0 -229
  98. data/lib/active_agent/providers/open_ai/chat/requests/audio.rb +0 -24
  99. data/lib/active_agent/providers/open_ai/chat/requests/messages/_types.rb +0 -123
  100. data/lib/active_agent/providers/open_ai/chat/requests/messages/assistant.rb +0 -42
  101. data/lib/active_agent/providers/open_ai/chat/requests/messages/base.rb +0 -78
  102. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/_types.rb +0 -133
  103. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/audio.rb +0 -35
  104. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/base.rb +0 -24
  105. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/file.rb +0 -26
  106. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/files/_types.rb +0 -60
  107. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/files/details.rb +0 -41
  108. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/image.rb +0 -37
  109. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/refusal.rb +0 -25
  110. data/lib/active_agent/providers/open_ai/chat/requests/messages/content/text.rb +0 -25
  111. data/lib/active_agent/providers/open_ai/chat/requests/messages/developer.rb +0 -25
  112. data/lib/active_agent/providers/open_ai/chat/requests/messages/function.rb +0 -25
  113. data/lib/active_agent/providers/open_ai/chat/requests/messages/system.rb +0 -25
  114. data/lib/active_agent/providers/open_ai/chat/requests/messages/tool.rb +0 -26
  115. data/lib/active_agent/providers/open_ai/chat/requests/messages/user.rb +0 -32
  116. data/lib/active_agent/providers/open_ai/chat/requests/prediction.rb +0 -46
  117. data/lib/active_agent/providers/open_ai/chat/requests/response_format.rb +0 -53
  118. data/lib/active_agent/providers/open_ai/chat/requests/stream_options.rb +0 -24
  119. data/lib/active_agent/providers/open_ai/chat/requests/tool_choice.rb +0 -26
  120. data/lib/active_agent/providers/open_ai/chat/requests/tools/_types.rb +0 -5
  121. data/lib/active_agent/providers/open_ai/chat/requests/tools/base.rb +0 -22
  122. data/lib/active_agent/providers/open_ai/chat/requests/tools/custom_tool.rb +0 -41
  123. data/lib/active_agent/providers/open_ai/chat/requests/tools/function_tool.rb +0 -51
  124. data/lib/active_agent/providers/open_ai/chat/requests/web_search_options.rb +0 -45
  125. data/lib/active_agent/providers/open_ai/embedding/requests/_types.rb +0 -49
  126. data/lib/active_agent/providers/open_ai/responses/requests/_types.rb +0 -231
  127. data/lib/active_agent/providers/open_ai/responses/requests/conversation.rb +0 -23
  128. data/lib/active_agent/providers/open_ai/responses/requests/inputs/_types.rb +0 -264
  129. data/lib/active_agent/providers/open_ai/responses/requests/inputs/assistant_message.rb +0 -22
  130. data/lib/active_agent/providers/open_ai/responses/requests/inputs/base.rb +0 -89
  131. data/lib/active_agent/providers/open_ai/responses/requests/inputs/code_interpreter_tool_call.rb +0 -30
  132. data/lib/active_agent/providers/open_ai/responses/requests/inputs/computer_tool_call.rb +0 -28
  133. data/lib/active_agent/providers/open_ai/responses/requests/inputs/computer_tool_call_output.rb +0 -33
  134. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/_types.rb +0 -207
  135. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/base.rb +0 -22
  136. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_audio.rb +0 -26
  137. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_file.rb +0 -28
  138. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_image.rb +0 -28
  139. data/lib/active_agent/providers/open_ai/responses/requests/inputs/content/input_text.rb +0 -25
  140. data/lib/active_agent/providers/open_ai/responses/requests/inputs/custom_tool_call.rb +0 -28
  141. data/lib/active_agent/providers/open_ai/responses/requests/inputs/custom_tool_call_output.rb +0 -27
  142. data/lib/active_agent/providers/open_ai/responses/requests/inputs/developer_message.rb +0 -20
  143. data/lib/active_agent/providers/open_ai/responses/requests/inputs/file_search_tool_call.rb +0 -25
  144. data/lib/active_agent/providers/open_ai/responses/requests/inputs/function_call_output.rb +0 -32
  145. data/lib/active_agent/providers/open_ai/responses/requests/inputs/function_tool_call.rb +0 -28
  146. data/lib/active_agent/providers/open_ai/responses/requests/inputs/image_gen_tool_call.rb +0 -27
  147. data/lib/active_agent/providers/open_ai/responses/requests/inputs/input_message.rb +0 -31
  148. data/lib/active_agent/providers/open_ai/responses/requests/inputs/item_reference.rb +0 -23
  149. data/lib/active_agent/providers/open_ai/responses/requests/inputs/local_shell_tool_call.rb +0 -26
  150. data/lib/active_agent/providers/open_ai/responses/requests/inputs/local_shell_tool_call_output.rb +0 -33
  151. data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_approval_request.rb +0 -30
  152. data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_approval_response.rb +0 -28
  153. data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_list_tools.rb +0 -29
  154. data/lib/active_agent/providers/open_ai/responses/requests/inputs/mcp_tool_call.rb +0 -35
  155. data/lib/active_agent/providers/open_ai/responses/requests/inputs/output_message.rb +0 -35
  156. data/lib/active_agent/providers/open_ai/responses/requests/inputs/reasoning.rb +0 -33
  157. data/lib/active_agent/providers/open_ai/responses/requests/inputs/system_message.rb +0 -20
  158. data/lib/active_agent/providers/open_ai/responses/requests/inputs/tool_call_base.rb +0 -27
  159. data/lib/active_agent/providers/open_ai/responses/requests/inputs/tool_message.rb +0 -23
  160. data/lib/active_agent/providers/open_ai/responses/requests/inputs/user_message.rb +0 -20
  161. data/lib/active_agent/providers/open_ai/responses/requests/inputs/web_search_tool_call.rb +0 -24
  162. data/lib/active_agent/providers/open_ai/responses/requests/prompt_reference.rb +0 -23
  163. data/lib/active_agent/providers/open_ai/responses/requests/reasoning.rb +0 -23
  164. data/lib/active_agent/providers/open_ai/responses/requests/stream_options.rb +0 -20
  165. data/lib/active_agent/providers/open_ai/responses/requests/text/_types.rb +0 -89
  166. data/lib/active_agent/providers/open_ai/responses/requests/text/base.rb +0 -22
  167. data/lib/active_agent/providers/open_ai/responses/requests/text/json_object.rb +0 -20
  168. data/lib/active_agent/providers/open_ai/responses/requests/text/json_schema.rb +0 -48
  169. data/lib/active_agent/providers/open_ai/responses/requests/text/plain.rb +0 -20
  170. data/lib/active_agent/providers/open_ai/responses/requests/text.rb +0 -41
  171. data/lib/active_agent/providers/open_ai/responses/requests/tool_choice.rb +0 -26
  172. data/lib/active_agent/providers/open_ai/responses/requests/tools/_types.rb +0 -112
  173. data/lib/active_agent/providers/open_ai/responses/requests/tools/base.rb +0 -25
  174. data/lib/active_agent/providers/open_ai/responses/requests/tools/code_interpreter_tool.rb +0 -23
  175. data/lib/active_agent/providers/open_ai/responses/requests/tools/computer_tool.rb +0 -27
  176. data/lib/active_agent/providers/open_ai/responses/requests/tools/custom_tool.rb +0 -28
  177. data/lib/active_agent/providers/open_ai/responses/requests/tools/file_search_tool.rb +0 -27
  178. data/lib/active_agent/providers/open_ai/responses/requests/tools/function_tool.rb +0 -29
  179. data/lib/active_agent/providers/open_ai/responses/requests/tools/image_generation_tool.rb +0 -37
  180. data/lib/active_agent/providers/open_ai/responses/requests/tools/local_shell_tool.rb +0 -21
  181. data/lib/active_agent/providers/open_ai/responses/requests/tools/mcp_tool.rb +0 -41
  182. data/lib/active_agent/providers/open_ai/responses/requests/tools/web_search_preview_tool.rb +0 -24
  183. data/lib/active_agent/providers/open_ai/responses/requests/tools/web_search_tool.rb +0 -25
  184. data/lib/active_agent/providers/open_ai/schema.yml +0 -65937
  185. data/lib/active_agent/providers/open_router/requests/message.rb +0 -1
  186. data/lib/active_agent/providers/open_router/requests/messages/assistant.rb +0 -20
  187. data/lib/active_agent/providers/open_router/requests/messages/user.rb +0 -30
@@ -1,81 +1,247 @@
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
69
131
  end
70
132
 
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
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
149
+ end
150
+
151
+ # Accessor for OpenRouter-specific provider preferences
152
+ #
153
+ # @return [Hash, nil]
154
+ def provider
155
+ @openrouter_params[:provider]
156
+ end
157
+
158
+ # Sets provider preferences
159
+ #
160
+ # @param value [Hash]
161
+ # @return [void]
162
+ def provider=(value)
163
+ @openrouter_params[:provider] = value
164
+ end
165
+
166
+ # Accessor for OpenRouter plugins
167
+ #
168
+ # @return [Array, nil]
169
+ def plugins
170
+ @openrouter_params[:plugins]
171
+ end
172
+
173
+ # Sets plugins
174
+ #
175
+ # @param value [Array]
176
+ # @return [void]
177
+ def plugins=(value)
178
+ @openrouter_params[:plugins] = value
179
+ end
180
+
181
+ # Accessor for OpenRouter transforms
182
+ #
183
+ # @return [Array]
184
+ def transforms
185
+ @openrouter_params[:transforms] || []
186
+ end
187
+
188
+ # Sets transforms
189
+ #
190
+ # @param value [Array]
191
+ # @return [void]
192
+ def transforms=(value)
193
+ @openrouter_params[:transforms] = value
194
+ end
195
+
196
+ # Accessor for fallback models
197
+ #
198
+ # @return [Array]
199
+ def models
200
+ @openrouter_params[:models] || []
201
+ end
202
+
203
+ # Sets fallback models
204
+ #
205
+ # @param value [Array]
206
+ # @return [void]
207
+ def models=(value)
208
+ @openrouter_params[:models] = value
209
+ end
210
+
211
+ # Alias for backwards compatibility
212
+ alias_method :fallback_models, :models
213
+ alias_method :fallback_models=, :models=
214
+
215
+ # Accessor for routing strategy
216
+ #
217
+ # @return [String]
218
+ def route
219
+ @openrouter_params[:route] || DEFAULTS[:route]
220
+ end
221
+
222
+ # Sets routing strategy
223
+ #
224
+ # @param value [String]
225
+ # @return [void]
226
+ def route=(value)
227
+ @openrouter_params[:route] = value
228
+ end
229
+
230
+ private
231
+
232
+ # @api private
233
+ # @param params [Hash]
234
+ # @return [Hash]
235
+ def apply_defaults(params)
236
+ # Set default model if not provided
237
+ params[:model] ||= "openrouter/auto"
238
+
239
+ # Apply other defaults
240
+ DEFAULTS.each do |key, value|
241
+ params[key] = value unless params.key?(key)
76
242
  end
77
243
 
78
- super(value)
244
+ params
79
245
  end
80
246
  end
81
247
  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
@@ -4,8 +4,25 @@ module ActiveAgent
4
4
  module Providers
5
5
  module OpenRouter
6
6
  module Requests
7
+ # Prediction configuration for prefilling responses
8
+ #
9
+ # Allows prefilling the start of the model's response. When provided,
10
+ # the model continues from this predicted content.
11
+ #
12
+ # @example Content prediction
13
+ # prediction = Prediction.new(
14
+ # type: 'content',
15
+ # content: 'Once upon a time'
16
+ # )
17
+ #
18
+ # @see https://platform.openai.com/docs/api-reference/chat/create#chat-create-prediction
7
19
  class Prediction < Common::BaseModel
20
+ # @!attribute type
21
+ # @return [String] prediction type (currently only 'content' is supported)
8
22
  attribute :type, :string
23
+
24
+ # @!attribute content
25
+ # @return [String] predicted content to prefill the response
9
26
  attribute :content, :string
10
27
 
11
28
  validates :type, inclusion: { in: %w[content] }, allow_nil: true
@@ -5,14 +5,48 @@ module ActiveAgent
5
5
  module OpenRouter
6
6
  module Requests
7
7
  # Maximum price configuration for provider routing
8
- # Specifies USD price per million tokens for different operations
9
- # See: https://openrouter.ai/docs/provider-routing
8
+ #
9
+ # Specifies maximum acceptable prices (in USD per million tokens or per
10
+ # operation) for filtering providers. OpenRouter will only route to
11
+ # providers within these price constraints.
12
+ #
13
+ # @example Setting prompt and completion limits
14
+ # max_price = MaxPrice.new(
15
+ # prompt: 0.01, # $0.01 per million input tokens
16
+ # completion: 0.03 # $0.03 per million output tokens
17
+ # )
18
+ #
19
+ # @example Setting all constraints
20
+ # max_price = MaxPrice.new(
21
+ # prompt: 0.01,
22
+ # completion: 0.03,
23
+ # image: 0.001,
24
+ # audio: 0.002,
25
+ # request: 0.0001
26
+ # )
27
+ #
28
+ # @see https://openrouter.ai/docs/provider-routing OpenRouter Provider Routing
29
+ # @see ProviderPreferences
10
30
  class MaxPrice < Common::BaseModel
11
- attribute :prompt, :float # Price per million prompt tokens (input)
12
- attribute :completion, :float # Price per million completion tokens (output)
13
- attribute :image, :float # Price per image
14
- attribute :audio, :float # Price per audio unit
15
- attribute :request, :float # Price per request
31
+ # @!attribute prompt
32
+ # @return [Float, nil] maximum price per million prompt tokens (input)
33
+ attribute :prompt, :float
34
+
35
+ # @!attribute completion
36
+ # @return [Float, nil] maximum price per million completion tokens (output)
37
+ attribute :completion, :float
38
+
39
+ # @!attribute image
40
+ # @return [Float, nil] maximum price per image operation
41
+ attribute :image, :float
42
+
43
+ # @!attribute audio
44
+ # @return [Float, nil] maximum price per audio operation
45
+ attribute :audio, :float
46
+
47
+ # @!attribute request
48
+ # @return [Float, nil] maximum price per request
49
+ attribute :request, :float
16
50
 
17
51
  validates :prompt, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
18
52
  validates :completion, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true