openai 0.70.0 → 0.72.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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/README.md +51 -1
  4. data/lib/openai/client.rb +62 -8
  5. data/lib/openai/internal/provider.rb +31 -0
  6. data/lib/openai/internal/transport/base_client.rb +65 -21
  7. data/lib/openai/models/admin/organization/audit_log_list_params.rb +84 -0
  8. data/lib/openai/models/admin/organization/audit_log_list_response.rb +84 -0
  9. data/lib/openai/models/admin/organization/organization_spend_limit.rb +151 -0
  10. data/lib/openai/models/admin/organization/organization_spend_limit_deleted.rb +33 -0
  11. data/lib/openai/models/admin/organization/projects/project_service_account.rb +4 -3
  12. data/lib/openai/models/admin/organization/projects/project_spend_limit.rb +155 -0
  13. data/lib/openai/models/admin/organization/projects/project_spend_limit_deleted.rb +35 -0
  14. data/lib/openai/models/admin/organization/projects/service_account_create_params.rb +9 -1
  15. data/lib/openai/models/admin/organization/projects/service_account_create_response.rb +26 -6
  16. data/lib/openai/models/admin/organization/projects/service_accounts/api_key_create_params.rb +54 -0
  17. data/lib/openai/models/admin/organization/projects/service_accounts/api_key_create_response.rb +57 -0
  18. data/lib/openai/models/admin/organization/projects/spend_limit_delete_params.rb +26 -0
  19. data/lib/openai/models/admin/organization/projects/spend_limit_retrieve_params.rb +26 -0
  20. data/lib/openai/models/admin/organization/projects/spend_limit_update_params.rb +77 -0
  21. data/lib/openai/models/admin/organization/spend_limit_delete_params.rb +18 -0
  22. data/lib/openai/models/admin/organization/spend_limit_retrieve_params.rb +18 -0
  23. data/lib/openai/models/admin/organization/spend_limit_update_params.rb +67 -0
  24. data/lib/openai/models/admin/organization/usage_audio_speeches_response.rb +88 -13
  25. data/lib/openai/models/admin/organization/usage_audio_transcriptions_response.rb +88 -13
  26. data/lib/openai/models/admin/organization/usage_code_interpreter_sessions_response.rb +88 -13
  27. data/lib/openai/models/admin/organization/usage_completions_response.rb +88 -13
  28. data/lib/openai/models/admin/organization/usage_costs_response.rb +88 -13
  29. data/lib/openai/models/admin/organization/usage_embeddings_response.rb +88 -13
  30. data/lib/openai/models/admin/organization/usage_file_search_calls_response.rb +88 -13
  31. data/lib/openai/models/admin/organization/usage_images_response.rb +88 -13
  32. data/lib/openai/models/admin/organization/usage_moderations_response.rb +88 -13
  33. data/lib/openai/models/admin/organization/usage_vector_stores_response.rb +88 -13
  34. data/lib/openai/models/admin/organization/usage_web_search_calls_response.rb +88 -13
  35. data/lib/openai/models/beta/beta_response.rb +12 -6
  36. data/lib/openai/models/beta/beta_response_error.rb +1 -0
  37. data/lib/openai/models/beta/beta_response_input_item.rb +16 -32
  38. data/lib/openai/models/beta/beta_response_text_config.rb +4 -2
  39. data/lib/openai/models/beta/beta_responses_client_event.rb +12 -6
  40. data/lib/openai/models/beta/response_create_params.rb +12 -6
  41. data/lib/openai/models/beta/responses/input_token_count_params.rb +12 -4
  42. data/lib/openai/models/chat/completion_create_params.rb +8 -6
  43. data/lib/openai/models/reasoning.rb +8 -2
  44. data/lib/openai/models/responses/input_token_count_params.rb +4 -2
  45. data/lib/openai/models/responses/response.rb +4 -4
  46. data/lib/openai/models/responses/response_create_params.rb +4 -4
  47. data/lib/openai/models/responses/response_error.rb +1 -0
  48. data/lib/openai/models/responses/response_text_config.rb +4 -2
  49. data/lib/openai/models/responses/responses_client_event.rb +4 -4
  50. data/lib/openai/provider.rb +18 -0
  51. data/lib/openai/providers/bedrock.rb +489 -0
  52. data/lib/openai/resources/admin/organization/projects/service_accounts/api_keys.rb +60 -0
  53. data/lib/openai/resources/admin/organization/projects/service_accounts.rb +10 -3
  54. data/lib/openai/resources/admin/organization/projects/spend_limit.rb +95 -0
  55. data/lib/openai/resources/admin/organization/projects.rb +4 -0
  56. data/lib/openai/resources/admin/organization/spend_limit.rb +86 -0
  57. data/lib/openai/resources/admin/organization.rb +4 -0
  58. data/lib/openai/resources/beta/responses.rb +4 -4
  59. data/lib/openai/resources/chat/completions.rb +4 -4
  60. data/lib/openai/resources/responses.rb +4 -4
  61. data/lib/openai/version.rb +1 -1
  62. data/lib/openai.rb +18 -0
  63. data/rbi/openai/client.rbi +17 -0
  64. data/rbi/openai/internal/provider.rbi +51 -0
  65. data/rbi/openai/internal/transport/base_client.rbi +32 -0
  66. data/rbi/openai/models/admin/organization/audit_log_list_params.rbi +420 -0
  67. data/rbi/openai/models/admin/organization/audit_log_list_response.rbi +420 -0
  68. data/rbi/openai/models/admin/organization/organization_spend_limit.rbi +278 -0
  69. data/rbi/openai/models/admin/organization/organization_spend_limit_deleted.rbi +48 -0
  70. data/rbi/openai/models/admin/organization/projects/project_service_account.rbi +8 -3
  71. data/rbi/openai/models/admin/organization/projects/project_spend_limit.rbi +280 -0
  72. data/rbi/openai/models/admin/organization/projects/project_spend_limit_deleted.rbi +49 -0
  73. data/rbi/openai/models/admin/organization/projects/service_account_create_params.rbi +8 -0
  74. data/rbi/openai/models/admin/organization/projects/service_account_create_response.rbi +52 -8
  75. data/rbi/openai/models/admin/organization/projects/service_accounts/api_key_create_params.rbi +84 -0
  76. data/rbi/openai/models/admin/organization/projects/service_accounts/api_key_create_response.rbi +80 -0
  77. data/rbi/openai/models/admin/organization/projects/spend_limit_delete_params.rbi +44 -0
  78. data/rbi/openai/models/admin/organization/projects/spend_limit_retrieve_params.rbi +44 -0
  79. data/rbi/openai/models/admin/organization/projects/spend_limit_update_params.rbi +149 -0
  80. data/rbi/openai/models/admin/organization/spend_limit_delete_params.rbi +34 -0
  81. data/rbi/openai/models/admin/organization/spend_limit_retrieve_params.rbi +34 -0
  82. data/rbi/openai/models/admin/organization/spend_limit_update_params.rbi +141 -0
  83. data/rbi/openai/models/admin/organization/usage_audio_speeches_response.rbi +121 -16
  84. data/rbi/openai/models/admin/organization/usage_audio_transcriptions_response.rbi +121 -16
  85. data/rbi/openai/models/admin/organization/usage_code_interpreter_sessions_response.rbi +121 -16
  86. data/rbi/openai/models/admin/organization/usage_completions_response.rbi +121 -16
  87. data/rbi/openai/models/admin/organization/usage_costs_response.rbi +121 -16
  88. data/rbi/openai/models/admin/organization/usage_embeddings_response.rbi +121 -16
  89. data/rbi/openai/models/admin/organization/usage_file_search_calls_response.rbi +121 -16
  90. data/rbi/openai/models/admin/organization/usage_images_response.rbi +121 -16
  91. data/rbi/openai/models/admin/organization/usage_moderations_response.rbi +121 -16
  92. data/rbi/openai/models/admin/organization/usage_vector_stores_response.rbi +121 -16
  93. data/rbi/openai/models/admin/organization/usage_web_search_calls_response.rbi +121 -16
  94. data/rbi/openai/models/beta/beta_response.rbi +18 -15
  95. data/rbi/openai/models/beta/beta_response_error.rbi +5 -0
  96. data/rbi/openai/models/beta/beta_response_input_item.rbi +39 -46
  97. data/rbi/openai/models/beta/beta_response_text_config.rbi +6 -3
  98. data/rbi/openai/models/beta/beta_responses_client_event.rbi +18 -15
  99. data/rbi/openai/models/beta/response_create_params.rbi +18 -15
  100. data/rbi/openai/models/beta/responses/input_token_count_params.rbi +18 -6
  101. data/rbi/openai/models/chat/completion_create_params.rbi +12 -15
  102. data/rbi/openai/models/reasoning.rbi +12 -3
  103. data/rbi/openai/models/responses/input_token_count_params.rbi +6 -3
  104. data/rbi/openai/models/responses/response.rbi +6 -12
  105. data/rbi/openai/models/responses/response_create_params.rbi +6 -12
  106. data/rbi/openai/models/responses/response_error.rbi +5 -0
  107. data/rbi/openai/models/responses/response_text_config.rbi +6 -3
  108. data/rbi/openai/models/responses/responses_client_event.rbi +6 -12
  109. data/rbi/openai/provider.rbi +9 -0
  110. data/rbi/openai/providers.rbi +33 -0
  111. data/rbi/openai/resources/admin/organization/projects/service_accounts/api_keys.rbi +45 -0
  112. data/rbi/openai/resources/admin/organization/projects/service_accounts.rbi +12 -2
  113. data/rbi/openai/resources/admin/organization/projects/spend_limit.rbi +78 -0
  114. data/rbi/openai/resources/admin/organization/projects.rbi +7 -0
  115. data/rbi/openai/resources/admin/organization/spend_limit.rbi +57 -0
  116. data/rbi/openai/resources/admin/organization.rbi +3 -0
  117. data/rbi/openai/resources/beta/responses.rbi +4 -4
  118. data/rbi/openai/resources/chat/completions.rbi +8 -6
  119. data/rbi/openai/resources/responses.rbi +4 -4
  120. data/sig/openai/client.rbs +7 -0
  121. data/sig/openai/internal/provider.rbs +29 -0
  122. data/sig/openai/internal/transport/base_client.rbs +17 -1
  123. data/sig/openai/models/admin/organization/audit_log_list_params.rbs +168 -0
  124. data/sig/openai/models/admin/organization/audit_log_list_response.rbs +168 -0
  125. data/sig/openai/models/admin/organization/organization_spend_limit.rbs +94 -0
  126. data/sig/openai/models/admin/organization/organization_spend_limit_deleted.rbs +28 -0
  127. data/sig/openai/models/admin/organization/projects/project_service_account.rbs +2 -1
  128. data/sig/openai/models/admin/organization/projects/project_spend_limit.rbs +96 -0
  129. data/sig/openai/models/admin/organization/projects/project_spend_limit_deleted.rbs +30 -0
  130. data/sig/openai/models/admin/organization/projects/service_account_create_params.rbs +9 -1
  131. data/sig/openai/models/admin/organization/projects/service_account_create_response.rbs +16 -5
  132. data/sig/openai/models/admin/organization/projects/service_accounts/api_key_create_params.rbs +53 -0
  133. data/sig/openai/models/admin/organization/projects/service_accounts/api_key_create_response.rbs +48 -0
  134. data/sig/openai/models/admin/organization/projects/spend_limit_delete_params.rbs +29 -0
  135. data/sig/openai/models/admin/organization/projects/spend_limit_retrieve_params.rbs +29 -0
  136. data/sig/openai/models/admin/organization/projects/spend_limit_update_params.rbs +67 -0
  137. data/sig/openai/models/admin/organization/spend_limit_delete_params.rbs +19 -0
  138. data/sig/openai/models/admin/organization/spend_limit_retrieve_params.rbs +19 -0
  139. data/sig/openai/models/admin/organization/spend_limit_update_params.rbs +60 -0
  140. data/sig/openai/models/admin/organization/usage_audio_speeches_response.rbs +63 -0
  141. data/sig/openai/models/admin/organization/usage_audio_transcriptions_response.rbs +63 -0
  142. data/sig/openai/models/admin/organization/usage_code_interpreter_sessions_response.rbs +63 -0
  143. data/sig/openai/models/admin/organization/usage_completions_response.rbs +63 -0
  144. data/sig/openai/models/admin/organization/usage_costs_response.rbs +63 -0
  145. data/sig/openai/models/admin/organization/usage_embeddings_response.rbs +63 -0
  146. data/sig/openai/models/admin/organization/usage_file_search_calls_response.rbs +63 -0
  147. data/sig/openai/models/admin/organization/usage_images_response.rbs +63 -0
  148. data/sig/openai/models/admin/organization/usage_moderations_response.rbs +63 -0
  149. data/sig/openai/models/admin/organization/usage_vector_stores_response.rbs +63 -0
  150. data/sig/openai/models/admin/organization/usage_web_search_calls_response.rbs +63 -0
  151. data/sig/openai/models/beta/beta_response.rbs +8 -12
  152. data/sig/openai/models/beta/beta_response_error.rbs +2 -0
  153. data/sig/openai/models/beta/beta_response_input_item.rbs +18 -24
  154. data/sig/openai/models/beta/beta_responses_client_event.rbs +8 -12
  155. data/sig/openai/models/beta/response_create_params.rbs +8 -12
  156. data/sig/openai/models/chat/completion_create_params.rbs +8 -12
  157. data/sig/openai/models/responses/response.rbs +8 -12
  158. data/sig/openai/models/responses/response_create_params.rbs +8 -12
  159. data/sig/openai/models/responses/response_error.rbs +2 -0
  160. data/sig/openai/models/responses/responses_client_event.rbs +8 -12
  161. data/sig/openai/provider.rbs +5 -0
  162. data/sig/openai/providers.rbs +15 -0
  163. data/sig/openai/resources/admin/organization/projects/service_accounts/api_keys.rbs +23 -0
  164. data/sig/openai/resources/admin/organization/projects/service_accounts.rbs +3 -0
  165. data/sig/openai/resources/admin/organization/projects/spend_limit.rbs +31 -0
  166. data/sig/openai/resources/admin/organization/projects.rbs +2 -0
  167. data/sig/openai/resources/admin/organization/spend_limit.rbs +26 -0
  168. data/sig/openai/resources/admin/organization.rbs +2 -0
  169. data/sig/openai/resources/beta/responses.rbs +4 -4
  170. data/sig/openai/resources/chat/completions.rbs +4 -4
  171. data/sig/openai/resources/responses.rbs +4 -4
  172. metadata +56 -2
@@ -0,0 +1,489 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenAI
4
+ module Providers
5
+ # @api private
6
+ module Bedrock
7
+ SERVICE = "bedrock-mantle"
8
+ CANONICAL_HOST = /\Abedrock-mantle\.([a-z0-9-]+)\.api\.aws\z/i
9
+ SIGNING_HEADERS = %w[authorization x-amz-content-sha256 x-amz-date x-amz-security-token].freeze
10
+ BEARER_AUTH_MARKER = :openai_bedrock_bearer
11
+ SIGV4_AUTH_MARKER = :openai_bedrock_sigv4
12
+ MISSING_REGION_MESSAGE =
13
+ "Bedrock requires an AWS region. Pass `region` to `bedrock(...)`, or set `AWS_REGION` " \
14
+ "or `AWS_DEFAULT_REGION`."
15
+ MISSING_CREDENTIALS_MESSAGE =
16
+ "Could not find credentials for Bedrock. Pass a bearer credential or AWS credentials " \
17
+ "to `bedrock(...)`, set `AWS_BEARER_TOKEN_BEDROCK`, or configure the default AWS credential chain."
18
+ CREDENTIAL_RESOLUTION_MESSAGE =
19
+ "Failed to resolve AWS credentials for Bedrock. Verify your AWS profile, environment " \
20
+ "variables, or runtime identity configuration and try again."
21
+ NON_REPLAYABLE_BODY_MESSAGE =
22
+ "Bedrock SigV4 authentication requires a replayable request body. Buffer the body " \
23
+ "before sending or use bearer authentication."
24
+ MISSING_DEPENDENCY_MESSAGE =
25
+ "Bedrock AWS authentication requires optional AWS dependencies. Add `gem \"aws-sdk-core\"` " \
26
+ "to your Gemfile, run `bundle install`, and try again."
27
+ PARTIAL_STATIC_CREDENTIALS_MESSAGE =
28
+ "Static AWS credentials require both `access_key_id` and `secret_access_key`. " \
29
+ "A `session_token` may only be used with both."
30
+ AMBIGUOUS_AWS_AUTH_MESSAGE =
31
+ "Bedrock authentication is ambiguous. Configure exactly one explicit AWS mode: " \
32
+ "static credentials, profile, or credential provider."
33
+ AMBIGUOUS_AUTH_MESSAGE =
34
+ "Bedrock authentication is ambiguous. Configure exactly one explicit mode: bearer " \
35
+ "credential, static AWS credentials, profile, or credential provider."
36
+
37
+ class Definition
38
+ attr_reader :name
39
+
40
+ def initialize(
41
+ region:,
42
+ base_url:,
43
+ bearer_provider:,
44
+ profile:,
45
+ access_key_id:,
46
+ secret_access_key:,
47
+ session_token:,
48
+ credentials_provider:,
49
+ default_chain:
50
+ )
51
+ @name = "bedrock"
52
+ @region = region&.freeze
53
+ @base_url = base_url&.freeze
54
+ @bearer_provider = bearer_provider
55
+ @profile = profile&.freeze
56
+ @access_key_id = access_key_id&.freeze
57
+ @secret_access_key = secret_access_key&.freeze
58
+ @session_token = session_token&.freeze
59
+ @credentials_provider = credentials_provider
60
+ @default_chain = default_chain
61
+ end
62
+
63
+ def configure
64
+ if @bearer_provider
65
+ base_url = Bedrock.resolve_base_url(@base_url, @region)
66
+ auth = BearerAuth.new(token_provider: @bearer_provider, base_url: base_url)
67
+ else
68
+ Bedrock.load_aws!
69
+ region = @region || Bedrock.profile_region(@profile)
70
+ raise ArgumentError, MISSING_REGION_MESSAGE if region.nil?
71
+
72
+ base_url = Bedrock.resolve_base_url(@base_url, region)
73
+ credentials =
74
+ if @access_key_id
75
+ Aws::Credentials.new(@access_key_id, @secret_access_key, @session_token)
76
+ elsif @credentials_provider
77
+ CustomCredentialsProvider.new(@credentials_provider)
78
+ elsif @profile
79
+ ProfileCredentialsProvider.new(@profile, region: region)
80
+ else
81
+ DefaultCredentialsProvider.new
82
+ end
83
+ auth = SigV4Auth.new(
84
+ region: region,
85
+ base_url: base_url,
86
+ credentials_provider: credentials,
87
+ default_chain: @default_chain
88
+ )
89
+ end
90
+
91
+ OpenAI::Internal::Provider::Runtime.new(
92
+ name: name,
93
+ base_url: base_url,
94
+ prepare_request: auth.method(:prepare_request)
95
+ )
96
+ end
97
+ end
98
+
99
+ class BearerAuth
100
+ def initialize(token_provider:, base_url:)
101
+ @token_provider = token_provider
102
+ @base_url = URI(base_url)
103
+ end
104
+
105
+ def prepare_request(request)
106
+ Bedrock.validate_origin!(request.fetch(:url), @base_url, action: "authenticate")
107
+ headers = Bedrock.provider_headers(request, marker: BEARER_AUTH_MARKER)
108
+ token = resolve_token
109
+
110
+ request.merge(
111
+ headers: headers.merge("authorization" => "Bearer #{token}"),
112
+ provider_auth: BEARER_AUTH_MARKER
113
+ )
114
+ end
115
+
116
+ private def resolve_token
117
+ token = @token_provider.call
118
+ unless token.is_a?(String) && !token.strip.empty?
119
+ raise OpenAI::Errors::Error,
120
+ "The Bedrock bearer credential provider must return a non-empty string."
121
+ end
122
+ token
123
+ rescue OpenAI::Errors::Error
124
+ raise
125
+ rescue StandardError => e
126
+ raise OpenAI::Errors::Error.new("Failed to resolve a bearer credential for Bedrock."), cause: e
127
+ end
128
+ end
129
+
130
+ class SigV4Auth
131
+ def initialize(region:, base_url:, credentials_provider:, default_chain:)
132
+ @region = region
133
+ @base_url = URI(base_url)
134
+ @default_chain = default_chain
135
+ @signer = Aws::Sigv4::Signer.new(
136
+ service: SERVICE,
137
+ region: region,
138
+ credentials_provider: credentials_provider
139
+ )
140
+ end
141
+
142
+ def prepare_request(request)
143
+ url = request.fetch(:url)
144
+ Bedrock.validate_origin!(url, @base_url, action: "sign")
145
+ Bedrock.validate_endpoint_region!(url, @region)
146
+ body = request[:body]
147
+ unless body.nil? || body.is_a?(String)
148
+ raise OpenAI::Errors::Error, NON_REPLAYABLE_BODY_MESSAGE
149
+ end
150
+
151
+ headers = Bedrock.provider_headers(request, marker: SIGV4_AUTH_MARKER)
152
+ SIGNING_HEADERS.each { headers.delete(_1) }
153
+ signature = sign_request(
154
+ http_method: request.fetch(:method).to_s.upcase,
155
+ url: url.to_s,
156
+ headers: headers,
157
+ body: body
158
+ )
159
+
160
+ request.merge(
161
+ headers: OpenAI::Internal::Util.normalized_headers(headers, signature.headers),
162
+ follow_redirects: false,
163
+ provider_auth: SIGV4_AUTH_MARKER
164
+ )
165
+ end
166
+
167
+ private def sign_request(**options)
168
+ @signer.sign_request(**options)
169
+ rescue OpenAI::Errors::Error
170
+ raise
171
+ rescue StandardError => e
172
+ message =
173
+ if @default_chain
174
+ MISSING_CREDENTIALS_MESSAGE
175
+ else
176
+ CREDENTIAL_RESOLUTION_MESSAGE
177
+ end
178
+ raise OpenAI::Errors::Error.new(message), cause: e
179
+ end
180
+ end
181
+
182
+ class CustomCredentialsProvider
183
+ def initialize(provider) = @provider = provider
184
+
185
+ def credentials
186
+ value = @provider.respond_to?(:call) ? @provider.call : @provider.credentials
187
+ value = value.credentials if !value.respond_to?(:access_key_id) && value.respond_to?(:credentials)
188
+ Bedrock.validate_credentials!(value)
189
+ rescue OpenAI::Errors::Error
190
+ raise
191
+ rescue StandardError => e
192
+ raise OpenAI::Errors::Error.new(CREDENTIAL_RESOLUTION_MESSAGE), cause: e
193
+ end
194
+ end
195
+
196
+ class DefaultCredentialsProvider
197
+ def initialize
198
+ @mutex = Mutex.new
199
+ @provider = nil
200
+ end
201
+
202
+ def credentials
203
+ provider = @mutex.synchronize { @provider ||= Aws::CredentialProviderChain.new.resolve }
204
+ raise OpenAI::Errors::Error, MISSING_CREDENTIALS_MESSAGE unless provider
205
+ Bedrock.validate_credentials!(provider.credentials)
206
+ end
207
+ end
208
+
209
+ class ProfileCredentialsProvider
210
+ def initialize(profile, region:)
211
+ @profile = profile
212
+ @region = region
213
+ @mutex = Mutex.new
214
+ @provider = nil
215
+ end
216
+
217
+ def credentials
218
+ provider = @mutex.synchronize { @provider ||= resolve }
219
+ Bedrock.validate_credentials!(provider.credentials)
220
+ rescue OpenAI::Errors::Error
221
+ raise
222
+ rescue StandardError => e
223
+ raise OpenAI::Errors::Error.new(CREDENTIAL_RESOLUTION_MESSAGE), cause: e
224
+ end
225
+
226
+ private def resolve
227
+ config = Aws.shared_config
228
+ providers = []
229
+ if config.config_enabled?
230
+ providers << config.assume_role_web_identity_credentials_from_config(
231
+ profile: @profile,
232
+ region: @region
233
+ )
234
+ providers << config.sso_credentials_from_config(profile: @profile)
235
+ providers << config.assume_role_credentials_from_config(profile: @profile, region: @region)
236
+ end
237
+
238
+ begin
239
+ providers << Aws::SharedCredentials.new(profile_name: @profile)
240
+ rescue Aws::Errors::NoSuchProfileError
241
+ nil
242
+ end
243
+
244
+ if config.config_enabled?
245
+ if config.respond_to?(:login_credentials_from_config)
246
+ providers << config.login_credentials_from_config(profile: @profile, region: @region)
247
+ end
248
+ process = config.credential_process(profile: @profile)
249
+ providers << Aws::ProcessCredentials.new([process]) if process
250
+ end
251
+
252
+ providers.compact.find(&:set?) || raise(OpenAI::Errors::Error, CREDENTIAL_RESOLUTION_MESSAGE)
253
+ end
254
+ end
255
+
256
+ class << self
257
+ def load_aws!
258
+ require("aws-sdk-core")
259
+ require("aws-sigv4")
260
+ rescue LoadError => e
261
+ raise OpenAI::Errors::Error.new(MISSING_DEPENDENCY_MESSAGE), cause: e
262
+ end
263
+
264
+ def resolve_base_url(base_url, region)
265
+ return base_url if base_url
266
+ raise ArgumentError, MISSING_REGION_MESSAGE if region.nil?
267
+ "https://bedrock-mantle.#{region}.api.aws/v1"
268
+ end
269
+
270
+ def normalize_base_url(base_url)
271
+ uri = URI(base_url)
272
+ unless uri.is_a?(URI::HTTP) && uri.host
273
+ raise ArgumentError, "The Bedrock `base_url` must be an absolute HTTP or HTTPS URL."
274
+ end
275
+ uri.path = uri.path.sub(%r{/responses(?:/.*)?\z}, "")
276
+ uri.path = "" if uri.path == "/"
277
+ uri.to_s.sub(%r{/\z}, "")
278
+ rescue URI::InvalidURIError => e
279
+ message = "The Bedrock `base_url` must be an absolute HTTP or HTTPS URL."
280
+ raise ArgumentError.new(message), cause: e
281
+ end
282
+
283
+ def profile_region(profile)
284
+ configured_profile = profile || ENV["AWS_PROFILE"] || ENV["AWS_DEFAULT_PROFILE"] || "default"
285
+ region = Aws.shared_config.region(profile: configured_profile)
286
+ normalize_optional_string(region)
287
+ rescue StandardError => e
288
+ raise OpenAI::Errors::Error.new(CREDENTIAL_RESOLUTION_MESSAGE), cause: e
289
+ end
290
+
291
+ def provider_headers(request, marker:)
292
+ headers = request.fetch(:headers).dup
293
+ if request[:provider_auth] == marker
294
+ headers.delete("authorization")
295
+ elsif headers.key?("authorization")
296
+ raise OpenAI::Errors::Error,
297
+ "Bedrock provider authentication cannot be combined with a custom `Authorization` header."
298
+ end
299
+ headers
300
+ end
301
+
302
+ def validate_origin!(url, base_url, action:)
303
+ return if OpenAI::Internal::Util.uri_origin(url) == OpenAI::Internal::Util.uri_origin(base_url)
304
+ message =
305
+ "Refusing to #{action} a Bedrock request for an origin other than the configured " \
306
+ "provider URL."
307
+ raise OpenAI::Errors::Error,
308
+ message
309
+ end
310
+
311
+ def validate_endpoint_region!(url, region)
312
+ endpoint_region = CANONICAL_HOST.match(url.host)&.[](1)
313
+ return if endpoint_region.nil? || endpoint_region == region
314
+ message =
315
+ "The Bedrock endpoint region `#{endpoint_region}` does not match the SigV4 " \
316
+ "region `#{region}`."
317
+ raise OpenAI::Errors::Error,
318
+ message
319
+ end
320
+
321
+ def validate_credentials!(credentials)
322
+ access_key_id = credentials&.access_key_id
323
+ secret_access_key = credentials&.secret_access_key
324
+ session_token = credentials&.session_token if credentials.respond_to?(:session_token)
325
+ valid =
326
+ access_key_id.is_a?(String) && !access_key_id.strip.empty? &&
327
+ secret_access_key.is_a?(String) && !secret_access_key.strip.empty? &&
328
+ (session_token.nil? || (session_token.is_a?(String) && !session_token.strip.empty?))
329
+ return credentials if valid
330
+ raise OpenAI::Errors::Error, CREDENTIAL_RESOLUTION_MESSAGE
331
+ end
332
+
333
+ def normalize_optional_string(value)
334
+ return nil if value.nil?
335
+ return nil unless value.is_a?(String)
336
+ normalized = value.strip
337
+ normalized unless normalized.empty?
338
+ end
339
+ end
340
+ end
341
+
342
+ class << self
343
+ # Configure the standard OpenAI client for Amazon Bedrock Mantle.
344
+ #
345
+ # Authentication precedence is an explicit bearer or AWS mode, then
346
+ # AWS_BEARER_TOKEN_BEDROCK, then the standard AWS credential chain.
347
+ #
348
+ # @param region [String, nil] AWS signing region. Defaults to `AWS_REGION`,
349
+ # `AWS_DEFAULT_REGION`, or the selected profile's configured region.
350
+ #
351
+ # @param base_url [String, nil] Bedrock API root. Defaults to
352
+ # `AWS_BEDROCK_BASE_URL` or the regional Bedrock Mantle endpoint.
353
+ #
354
+ # @param api_key [String, nil] Explicit Bedrock bearer credential. Passing
355
+ # `nil` explicitly skips the `AWS_BEARER_TOKEN_BEDROCK` fallback.
356
+ #
357
+ # @param token_provider [#call, nil] Callable returning a fresh bearer
358
+ # credential before each request attempt.
359
+ #
360
+ # @param access_key_id [String, nil] Explicit AWS access key ID. Must be
361
+ # paired with `secret_access_key`.
362
+ #
363
+ # @param secret_access_key [String, nil] Explicit AWS secret access key.
364
+ #
365
+ # @param session_token [String, nil] Optional session token for explicit
366
+ # temporary AWS credentials.
367
+ #
368
+ # @param profile [String, nil] Explicit AWS shared-config profile.
369
+ #
370
+ # @param credentials_provider [#credentials, #call, nil] Refreshable AWS
371
+ # credentials provider. A callable must return an AWS credential identity.
372
+ #
373
+ # @return [OpenAI::Provider]
374
+ def bedrock(
375
+ region: nil,
376
+ base_url: OpenAI::Internal::OMIT,
377
+ api_key: OpenAI::Internal::OMIT,
378
+ token_provider: nil,
379
+ access_key_id: nil,
380
+ secret_access_key: nil,
381
+ session_token: nil,
382
+ profile: nil,
383
+ credentials_provider: nil
384
+ )
385
+ normalized_region = Bedrock.normalize_optional_string(region)
386
+ if !region.nil? && normalized_region.nil?
387
+ raise ArgumentError, "The Bedrock AWS `region` must not be empty."
388
+ end
389
+ normalized_region ||= Bedrock.normalize_optional_string(ENV["AWS_REGION"]) ||
390
+ Bedrock.normalize_optional_string(ENV["AWS_DEFAULT_REGION"])
391
+
392
+ normalized_profile = Bedrock.normalize_optional_string(profile)
393
+ if !profile.nil? && normalized_profile.nil?
394
+ raise ArgumentError, "The Bedrock AWS `profile` must not be empty."
395
+ end
396
+
397
+ configured_base_url =
398
+ if base_url.equal?(OpenAI::Internal::OMIT)
399
+ Bedrock.normalize_optional_string(ENV["AWS_BEDROCK_BASE_URL"])
400
+ elsif base_url.nil?
401
+ nil
402
+ else
403
+ normalized = Bedrock.normalize_optional_string(base_url)
404
+ raise(ArgumentError, "The Bedrock `base_url` must not be empty.") unless normalized
405
+ normalized
406
+ end
407
+ configured_base_url = Bedrock.normalize_base_url(configured_base_url) if configured_base_url
408
+
409
+ has_access_key = !access_key_id.nil?
410
+ has_secret_key = !secret_access_key.nil?
411
+ if has_access_key != has_secret_key || (!session_token.nil? && !has_access_key)
412
+ raise ArgumentError, Bedrock::PARTIAL_STATIC_CREDENTIALS_MESSAGE
413
+ end
414
+ normalized_access_key_id = Bedrock.normalize_optional_string(access_key_id)
415
+ normalized_secret_access_key = Bedrock.normalize_optional_string(secret_access_key)
416
+ normalized_session_token = Bedrock.normalize_optional_string(session_token)
417
+ if has_access_key && [normalized_access_key_id, normalized_secret_access_key].any?(&:nil?)
418
+ raise ArgumentError,
419
+ "Static AWS credentials require non-empty `access_key_id` and `secret_access_key` values."
420
+ end
421
+ if !session_token.nil? && normalized_session_token.nil?
422
+ raise ArgumentError, "A static AWS `session_token` must not be empty when provided."
423
+ end
424
+
425
+ explicit_api_key = !api_key.equal?(OpenAI::Internal::OMIT) && !api_key.nil?
426
+ normalized_api_key = Bedrock.normalize_optional_string(api_key)
427
+ if explicit_api_key && normalized_api_key.nil?
428
+ raise ArgumentError, "The Bedrock bearer credential must not be empty."
429
+ end
430
+ if explicit_api_key && !token_provider.nil?
431
+ raise ArgumentError,
432
+ "The `api_key` and `token_provider` options are mutually exclusive. Configure only one."
433
+ end
434
+ if !token_provider.nil? && !token_provider.respond_to?(:call)
435
+ raise ArgumentError, "The Bedrock `token_provider` must respond to `call`."
436
+ end
437
+ if !credentials_provider.nil? &&
438
+ !credentials_provider.respond_to?(:call) &&
439
+ !credentials_provider.respond_to?(:credentials)
440
+ raise ArgumentError,
441
+ "The Bedrock `credentials_provider` must respond to `call` or `credentials`."
442
+ end
443
+
444
+ explicit_bearer = explicit_api_key || !token_provider.nil?
445
+ aws_modes = [has_access_key, !normalized_profile.nil?, !credentials_provider.nil?].count(true)
446
+ if aws_modes > 1
447
+ raise ArgumentError, Bedrock::AMBIGUOUS_AWS_AUTH_MESSAGE
448
+ end
449
+ if explicit_bearer && aws_modes.positive?
450
+ raise ArgumentError, Bedrock::AMBIGUOUS_AUTH_MESSAGE
451
+ end
452
+
453
+ skip_environment_bearer = !api_key.equal?(OpenAI::Internal::OMIT) && api_key.nil?
454
+ environment_bearer =
455
+ !explicit_bearer && aws_modes.zero? && !skip_environment_bearer &&
456
+ !Bedrock.normalize_optional_string(ENV["AWS_BEARER_TOKEN_BEDROCK"]).nil?
457
+ bearer_provider =
458
+ if explicit_api_key
459
+ normalized_api_key.freeze
460
+ -> { normalized_api_key }
461
+ elsif !token_provider.nil?
462
+ token_provider
463
+ elsif environment_bearer
464
+ lambda do
465
+ ENV["AWS_BEARER_TOKEN_BEDROCK"] ||
466
+ raise(OpenAI::Errors::Error, Bedrock::MISSING_CREDENTIALS_MESSAGE)
467
+ end
468
+ end
469
+
470
+ if bearer_provider && configured_base_url.nil? && normalized_region.nil?
471
+ raise ArgumentError, Bedrock::MISSING_REGION_MESSAGE
472
+ end
473
+
474
+ definition = Bedrock::Definition.new(
475
+ region: normalized_region,
476
+ base_url: configured_base_url,
477
+ bearer_provider: bearer_provider,
478
+ profile: normalized_profile,
479
+ access_key_id: normalized_access_key_id,
480
+ secret_access_key: normalized_secret_access_key,
481
+ session_token: normalized_session_token,
482
+ credentials_provider: credentials_provider,
483
+ default_chain: aws_modes.zero?
484
+ )
485
+ OpenAI::Internal::Provider.create(definition)
486
+ end
487
+ end
488
+ end
489
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenAI
4
+ module Resources
5
+ class Admin
6
+ class Organization
7
+ class Projects
8
+ class ServiceAccounts
9
+ class APIKeys
10
+ # Creates an API key for a service account in the project.
11
+ #
12
+ # @overload create(service_account_id, project_id:, name: nil, scopes: nil, request_options: {})
13
+ #
14
+ # @param service_account_id [String] Path param: The ID of the service account.
15
+ #
16
+ # @param project_id [String] Path param: The ID of the project.
17
+ #
18
+ # @param name [String] Body param: API key name.
19
+ #
20
+ # @param scopes [Array<String>] Body param: API key scopes.
21
+ #
22
+ # @param request_options [OpenAI::RequestOptions, Hash{Symbol=>Object}, nil]
23
+ #
24
+ # @return [OpenAI::Models::Admin::Organization::Projects::ServiceAccounts::APIKeyCreateResponse]
25
+ #
26
+ # @see OpenAI::Models::Admin::Organization::Projects::ServiceAccounts::APIKeyCreateParams
27
+ def create(service_account_id, params)
28
+ parsed, options =
29
+ OpenAI::Admin::Organization::Projects::ServiceAccounts::APIKeyCreateParams.dump_request(params)
30
+ project_id =
31
+ parsed.delete(:project_id) do
32
+ raise ArgumentError.new("missing required path argument #{_1}")
33
+ end
34
+ @client.request(
35
+ method: :post,
36
+ path: [
37
+ "organization/projects/%1$s/service_accounts/%2$s/api_keys",
38
+ project_id,
39
+ service_account_id
40
+ ],
41
+ body: parsed,
42
+ model: OpenAI::Models::Admin::Organization::Projects::ServiceAccounts::APIKeyCreateResponse,
43
+ security: {admin_api_key_auth: true},
44
+ options: options
45
+ )
46
+ end
47
+
48
+ # @api private
49
+ #
50
+ # @param client [OpenAI::Client]
51
+ def initialize(client:)
52
+ @client = client
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -6,15 +6,20 @@ module OpenAI
6
6
  class Organization
7
7
  class Projects
8
8
  class ServiceAccounts
9
- # Creates a new service account in the project. This also returns an unredacted
10
- # API key for the service account.
9
+ # @return [OpenAI::Resources::Admin::Organization::Projects::ServiceAccounts::APIKeys]
10
+ attr_reader :api_keys
11
+
12
+ # Creates a new service account in the project. By default, this also returns an
13
+ # unredacted API key for the service account.
11
14
  #
12
- # @overload create(project_id, name:, request_options: {})
15
+ # @overload create(project_id, name:, create_service_account_only: nil, request_options: {})
13
16
  #
14
17
  # @param project_id [String] The ID of the project.
15
18
  #
16
19
  # @param name [String] The name of the service account being created.
17
20
  #
21
+ # @param create_service_account_only [Boolean, nil] Create the service account without default roles or an API key.
22
+ #
18
23
  # @param request_options [OpenAI::RequestOptions, Hash{Symbol=>Object}, nil]
19
24
  #
20
25
  # @return [OpenAI::Models::Admin::Organization::Projects::ServiceAccountCreateResponse]
@@ -163,6 +168,8 @@ module OpenAI
163
168
  # @param client [OpenAI::Client]
164
169
  def initialize(client:)
165
170
  @client = client
171
+ @api_keys =
172
+ OpenAI::Resources::Admin::Organization::Projects::ServiceAccounts::APIKeys.new(client: client)
166
173
  end
167
174
  end
168
175
  end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenAI
4
+ module Resources
5
+ class Admin
6
+ class Organization
7
+ class Projects
8
+ class SpendLimit
9
+ # Get a project's hard spend limit.
10
+ #
11
+ # @overload retrieve(project_id, request_options: {})
12
+ #
13
+ # @param project_id [String] The ID of the project whose hard spend limit is being managed.
14
+ #
15
+ # @param request_options [OpenAI::RequestOptions, Hash{Symbol=>Object}, nil]
16
+ #
17
+ # @return [OpenAI::Models::Admin::Organization::Projects::ProjectSpendLimit]
18
+ #
19
+ # @see OpenAI::Models::Admin::Organization::Projects::SpendLimitRetrieveParams
20
+ def retrieve(project_id, params = {})
21
+ @client.request(
22
+ method: :get,
23
+ path: ["organization/projects/%1$s/spend_limit", project_id],
24
+ model: OpenAI::Admin::Organization::Projects::ProjectSpendLimit,
25
+ security: {admin_api_key_auth: true},
26
+ options: params[:request_options]
27
+ )
28
+ end
29
+
30
+ # Some parameter documentations has been truncated, see
31
+ # {OpenAI::Models::Admin::Organization::Projects::SpendLimitUpdateParams} for more
32
+ # details.
33
+ #
34
+ # Create or replace a project's hard spend limit.
35
+ #
36
+ # @overload update(project_id, currency:, interval:, threshold_amount:, request_options: {})
37
+ #
38
+ # @param project_id [String] The ID of the project whose hard spend limit is being managed.
39
+ #
40
+ # @param currency [Symbol, OpenAI::Models::Admin::Organization::Projects::SpendLimitUpdateParams::Currency] The currency for the threshold amount. Currently, only `USD` is supported.
41
+ #
42
+ # @param interval [Symbol, OpenAI::Models::Admin::Organization::Projects::SpendLimitUpdateParams::Interval] The time interval for evaluating spend against the threshold. Currently, only `m
43
+ #
44
+ # @param threshold_amount [Integer] The hard spend limit amount, in cents.
45
+ #
46
+ # @param request_options [OpenAI::RequestOptions, Hash{Symbol=>Object}, nil]
47
+ #
48
+ # @return [OpenAI::Models::Admin::Organization::Projects::ProjectSpendLimit]
49
+ #
50
+ # @see OpenAI::Models::Admin::Organization::Projects::SpendLimitUpdateParams
51
+ def update(project_id, params)
52
+ parsed, options = OpenAI::Admin::Organization::Projects::SpendLimitUpdateParams.dump_request(params)
53
+ @client.request(
54
+ method: :post,
55
+ path: ["organization/projects/%1$s/spend_limit", project_id],
56
+ body: parsed,
57
+ model: OpenAI::Admin::Organization::Projects::ProjectSpendLimit,
58
+ security: {admin_api_key_auth: true},
59
+ options: options
60
+ )
61
+ end
62
+
63
+ # Delete a project's hard spend limit.
64
+ #
65
+ # @overload delete(project_id, request_options: {})
66
+ #
67
+ # @param project_id [String] The ID of the project whose hard spend limit is being managed.
68
+ #
69
+ # @param request_options [OpenAI::RequestOptions, Hash{Symbol=>Object}, nil]
70
+ #
71
+ # @return [OpenAI::Models::Admin::Organization::Projects::ProjectSpendLimitDeleted]
72
+ #
73
+ # @see OpenAI::Models::Admin::Organization::Projects::SpendLimitDeleteParams
74
+ def delete(project_id, params = {})
75
+ @client.request(
76
+ method: :delete,
77
+ path: ["organization/projects/%1$s/spend_limit", project_id],
78
+ model: OpenAI::Admin::Organization::Projects::ProjectSpendLimitDeleted,
79
+ security: {admin_api_key_auth: true},
80
+ options: params[:request_options]
81
+ )
82
+ end
83
+
84
+ # @api private
85
+ #
86
+ # @param client [OpenAI::Client]
87
+ def initialize(client:)
88
+ @client = client
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end