openai 0.108.0 → 0.109.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbaffc5b480678a26851b73b6b49f9f63419801d8bbec633d6ed4f3765fe4717
4
- data.tar.gz: 127b19570f47781df5644f2217d9405e1ea0e82279b319a7683c59b567cbbd72
3
+ metadata.gz: 54cd64155cb1af2bb57a7c61bc15355979a7c0d9e50951ce7e893911fcfe473b
4
+ data.tar.gz: 29101b3385b4be69545f1bd5516c90329c34d1e6f0bf303f28b5ea379b36fde8
5
5
  SHA512:
6
- metadata.gz: ffe2a7748064e0cb03c1eb2482670a4f581323b02a0275fd9654e1f97ecaf85f9128e064f22b5075af7d615d0e9d4fee73144d57f07ceaa9dcde25a419f7b066
7
- data.tar.gz: c531be809fb0b7dd69d4cb089eea59680a01dbcc258265e47ef6b16052ffd78eeba8f2ccf094ffaa694cdd2dca602b9ebceffa65e362de1427f51e04219a6192
6
+ metadata.gz: b3d15cf0ee35ec3f58df82ea2f7249b62e63c02c1ee5351d0d6e95f50d1929c707a458225ee71e8568f816149a7c8346788286b22ba9ec38faeb42d3e511746c
7
+ data.tar.gz: b0e8720cecf2f4f600f7ac05226a8cbbba2eae974a81e0312b9d8ac88bef0bc7e4883a591ec2ac0fa2e9b30de13a79607a23b4a904f03f945fc10a79bb4a3913
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.109.0](https://github.com/openai/openai-ruby/compare/v0.108.0...v0.109.0) (2026-10-21)
4
+
5
+
6
+ ### Features
7
+
8
+ * add Vertex AI support for the Responses API ([#952](https://github.com/openai/openai-ruby/issues/952)) ([98f5359](https://github.com/openai/openai-ruby/commit/98f5359e3ad2133abc5e914610fe7bbd2a058f8d))
9
+
3
10
  ## [0.108.0](https://github.com/openai/openai-ruby/compare/v0.107.0...v0.108.0) (2026-10-21)
4
11
 
5
12
 
data/README.md CHANGED
@@ -15,7 +15,7 @@ To use this gem, install via Bundler by adding the following to your application
15
15
  <!-- x-release-please-start-version -->
16
16
 
17
17
  ```ruby
18
- gem "openai", "~> 0.108.0"
18
+ gem "openai", "~> 0.109.0"
19
19
  ```
20
20
 
21
21
  <!-- x-release-please-end -->
@@ -556,6 +556,12 @@ client = OpenAI::Client.new(
556
556
 
557
557
  See [bedrock.md](bedrock.md) for authentication precedence, static and refreshable credentials, endpoint configuration, and SigV4 request constraints.
558
558
 
559
+ ## Google Vertex AI
560
+
561
+ Use `OpenAI::Providers.vertex` with the Responses API. See [vertex.md](vertex.md)
562
+ for setup with Google Application Default Credentials, a credentials object, or
563
+ an access-token callback.
564
+
559
565
  ## Workload Identity Authentication
560
566
 
561
567
  For secure, automated environments like cloud-managed Kubernetes, Azure, and GCP, you can use workload identity authentication with short-lived tokens from cloud identity providers instead of long-lived API keys.
@@ -101,6 +101,17 @@ module OpenAI
101
101
 
102
102
  # @api private
103
103
  private def send_request(request, redirect_count:, retry_count:, send_retry_header:)
104
+ if (wrapper = @provider_runtime&.wrap_request)
105
+ return wrapper.call(request, request_replayable?(request), retry_count) do |attempt, attempt_retry_count|
106
+ super(
107
+ attempt,
108
+ redirect_count: redirect_count,
109
+ retry_count: attempt_retry_count,
110
+ send_retry_header: send_retry_header
111
+ )
112
+ end
113
+ end
114
+
104
115
  if @workload_identity_auth && x509_transport?(@requester)
105
116
  canonical_headers = @requester.validate_api_request!(
106
117
  url: request.fetch(:url),
@@ -11,6 +11,7 @@ module OpenAI
11
11
  :name,
12
12
  :base_url,
13
13
  :prepare_request,
14
+ :wrap_request,
14
15
  :authentication_headers,
15
16
  keyword_init: true
16
17
  )
@@ -0,0 +1,229 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timeout"
4
+
5
+ module OpenAI
6
+ module Providers
7
+ # @api private
8
+ module Vertex
9
+ SCOPE = "https://www.googleapis.com/auth/cloud-platform"
10
+ AUTH_MARKER = :openai_vertex_bearer
11
+
12
+ class Definition
13
+ def initialize(base_url:, token_provider:, credentials:)
14
+ @base_url = base_url.freeze
15
+ @token_provider = token_provider
16
+ @credentials = credentials
17
+ @credentials_mutex = Mutex.new
18
+ end
19
+
20
+ def name = "vertex"
21
+
22
+ def configure
23
+ token_provider = @token_provider
24
+ credentials = @credentials
25
+ unless token_provider || credentials
26
+ begin
27
+ require "googleauth"
28
+
29
+ rescue LoadError => e
30
+ raise LoadError.new("Vertex ADC requires optional gem \"googleauth\"; add it to your Gemfile."), cause: e
31
+ end
32
+
33
+ begin
34
+ credentials = Google::Auth.get_application_default(SCOPE)
35
+ rescue StandardError => e
36
+ raise OpenAI::Errors::Error.new("Vertex could not load Google Application Default Credentials."), cause: e
37
+ end
38
+ end
39
+
40
+ if credentials
41
+ # googleauth apply! refreshes expired tokens. Serialize just this
42
+ # cache/refresh step across all clients using this provider.
43
+ mutex = @credentials_mutex
44
+ rejected_token = nil
45
+ token_provider = lambda do
46
+ headers = {}
47
+ mutex.synchronize do
48
+ credentials.apply!(headers)
49
+ token = headers.fetch(:authorization) { headers.fetch("authorization") }.delete_prefix("Bearer ")
50
+ if token == rejected_token && credentials.respond_to?(:fetch_access_token!)
51
+ credentials.fetch_access_token!
52
+ credentials.apply!(headers)
53
+ token = headers.fetch(:authorization) { headers.fetch("authorization") }.delete_prefix("Bearer ")
54
+ rejected_token = nil
55
+ end
56
+
57
+ token
58
+ end
59
+ end
60
+ end
61
+
62
+ # Share the replay allowance and retry count, but reset the deadline
63
+ # for each ordinary transport retry. A 401 replay keeps its deadline.
64
+ # Refresh itself happens in prepare_request, under its timeout and mutex.
65
+ if credentials.respond_to?(:fetch_access_token!)
66
+ wrapper = lambda do |request, replayable, retry_count, &send_request|
67
+ context = request[:vertex_auth_context] || {replayed: false}
68
+ context[:retry_count] = retry_count
69
+ context[:deadline] = request[:timeout]&.then { OpenAI::Internal::Util.monotonic_secs + _1 }
70
+ request = request.merge(vertex_auth_context: context)
71
+ begin
72
+ send_request.call(request, context[:retry_count])
73
+ rescue OpenAI::Errors::AuthenticationError
74
+ remaining = context[:deadline] && (context[:deadline] - OpenAI::Internal::Util.monotonic_secs)
75
+ if remaining && remaining <= 0
76
+ raise OpenAI::Errors::APITimeoutError.new(url: request.fetch(:url))
77
+ end
78
+
79
+ begin
80
+ Timeout.timeout(remaining) { mutex.synchronize { rejected_token = context[:token] } }
81
+ rescue Timeout::Error
82
+ raise OpenAI::Errors::APITimeoutError.new(url: request.fetch(:url)), cause: nil
83
+ end
84
+
85
+ raise if context[:replayed] || !replayable || context[:retry_count] >= request.fetch(:max_retries)
86
+
87
+ context[:replayed] = true
88
+ context[:retry_count] += 1
89
+ retry
90
+ end
91
+ end
92
+ end
93
+
94
+ base = URI(@base_url)
95
+ OpenAI::Internal::Provider::Runtime.new(
96
+ name: name,
97
+ base_url: @base_url,
98
+ authentication_headers: %w[authorization],
99
+ wrap_request: wrapper,
100
+ prepare_request: lambda do |request|
101
+ url = request.fetch(:url)
102
+ unless OpenAI::Internal::Util.uri_origin(url) == OpenAI::Internal::Util.uri_origin(base)
103
+ raise OpenAI::Errors::Error, "Vertex refuses credentials for an origin other than the configured URL."
104
+ end
105
+
106
+ headers = request.fetch(:headers).dup
107
+ if request[:provider_auth] == AUTH_MARKER
108
+ headers.delete("authorization")
109
+ elsif headers.key?("authorization")
110
+ raise(
111
+ OpenAI::Errors::Error,
112
+ "Vertex authentication cannot be combined with a custom Authorization header."
113
+ )
114
+ end
115
+
116
+ if headers.key?("api-key")
117
+ raise OpenAI::Errors::Error, "Vertex authentication cannot be combined with a custom api-key header."
118
+ end
119
+ # The outer Google API version differs for stored Responses.
120
+ # Do not change other gateway layouts.
121
+ prefix = "#{base.path.delete_suffix("/")}/responses/"
122
+ if [:get, :delete].include?(request[:method]) &&
123
+ url.path.start_with?(prefix) &&
124
+ base.path.start_with?("/v1/projects/")
125
+ url = url.dup
126
+ url.path = url.path.sub(%r{\A/v1/}, "/v1beta1/")
127
+ end
128
+
129
+ headers.delete("openai-organization")
130
+ headers.delete("openai-project")
131
+ timeout = request[:timeout]
132
+ context = request[:vertex_auth_context]
133
+ timeout = context[:deadline] - OpenAI::Internal::Util.monotonic_secs if context&.dig(:deadline)
134
+ if timeout && timeout <= 0
135
+ raise OpenAI::Errors::APITimeoutError.new(url: url, message: "Vertex credential lookup timed out")
136
+ end
137
+
138
+ started = OpenAI::Internal::Util.monotonic_secs
139
+ token = begin
140
+ Timeout.timeout(timeout) { token_provider.call }
141
+ rescue Timeout::Error => e
142
+ raise(
143
+ OpenAI::Errors::APITimeoutError.new(url: url, message: "Vertex credential lookup timed out"),
144
+ cause: e
145
+ )
146
+ rescue StandardError => e
147
+ raise OpenAI::Errors::Error.new("Vertex failed to resolve Google credentials."), cause: e
148
+ end
149
+
150
+ remaining = timeout && (timeout - (OpenAI::Internal::Util.monotonic_secs - started))
151
+ if remaining && remaining <= 0
152
+ raise OpenAI::Errors::APITimeoutError.new(url: url, message: "Vertex credential lookup timed out")
153
+ end
154
+
155
+ if credentials.respond_to?(:quota_project_id) && (quota = credentials.quota_project_id)
156
+ headers["x-goog-user-project"] = quota
157
+ end
158
+
159
+ unless token.is_a?(String) && !token.strip.empty?
160
+ raise OpenAI::Errors::Error, "Vertex token_provider must return a non-empty access token string."
161
+ end
162
+
163
+ context[:token] = token if context
164
+
165
+ request.merge(
166
+ url: url,
167
+ timeout: remaining,
168
+ headers: headers.merge("authorization" => "Bearer #{token}"),
169
+ provider_auth: AUTH_MARKER,
170
+ follow_redirects: false
171
+ )
172
+ end
173
+ )
174
+ end
175
+ end
176
+ end
177
+
178
+ class << self
179
+ # Use Google Application Default Credentials with the regular Responses
180
+ # client. Supply token_provider: -> { ... } for application-managed credentials.
181
+ # It is called per attempt and should cache/refresh tokens as needed.
182
+ #
183
+ # Base URL is the complete trusted provider URL before /responses. When
184
+ # omitted, project and location are required. OpenAI environment defaults
185
+ # and top-level auth/base_url options are ignored/rejected by Client.
186
+ def vertex(project: nil, location: nil, base_url: nil, token_provider: nil, credentials: nil)
187
+ if credentials && token_provider
188
+ raise ArgumentError, "Vertex credentials and token_provider are mutually exclusive."
189
+ end
190
+
191
+ if credentials && !credentials.respond_to?(:apply!)
192
+ raise ArgumentError, "Vertex credentials must provide Google's apply! method."
193
+ end
194
+
195
+ unless token_provider.nil? || token_provider.respond_to?(:call)
196
+ raise ArgumentError, "Vertex token_provider must respond to call."
197
+ end
198
+
199
+ if base_url.nil?
200
+ project_segment = /\A[a-zA-Z0-9][a-zA-Z0-9_.:-]*\z/
201
+ location_segment = /\A[a-zA-Z0-9][a-zA-Z0-9_-]*\z/
202
+ unless project.is_a?(String) &&
203
+ location.is_a?(String) &&
204
+ project_segment.match?(project) &&
205
+ location_segment.match?(location)
206
+ raise ArgumentError, "Vertex project and location are required and must be single path segments."
207
+ end
208
+
209
+ host = location == "global" ? "aiplatform.googleapis.com" : "#{location}-aiplatform.googleapis.com"
210
+ base_url = "https://#{host}/v1/projects/#{project}/locations/#{location}/publishers/openai/v1"
211
+ end
212
+
213
+ begin
214
+ uri = URI(base_url.to_s)
215
+ unless %w[http https].include?(uri.scheme) && uri.host && !uri.userinfo && !uri.query && !uri.fragment
216
+ raise URI::InvalidURIError
217
+ end
218
+
219
+ rescue URI::InvalidURIError
220
+ raise ArgumentError, "Vertex base_url must be an absolute HTTP(S) URL without credentials, query or fragment."
221
+ end
222
+
223
+ OpenAI::Internal::Provider.create(
224
+ Vertex::Definition.new(base_url: uri.to_s, token_provider: token_provider, credentials: credentials)
225
+ )
226
+ end
227
+ end
228
+ end
229
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenAI
4
- VERSION = "0.108.0"
4
+ VERSION = "0.109.0"
5
5
  end
data/lib/openai.rb CHANGED
@@ -69,6 +69,7 @@ require_relative "openai/provider"
69
69
  require_relative "openai/internal/provider"
70
70
  require_relative "openai/providers/azure"
71
71
  require_relative "openai/providers/bedrock"
72
+ require_relative "openai/providers/vertex"
72
73
  require_relative "openai/internal/transport/request_body_merge"
73
74
  require_relative "openai/internal/transport/base_client"
74
75
  require_relative "openai/helpers/client_options"
@@ -25,6 +25,9 @@ module OpenAI
25
25
 
26
26
  attr_accessor :prepare_request
27
27
 
28
+ sig { returns(T.nilable(Proc)) }
29
+ attr_accessor :wrap_request
30
+
28
31
  sig { returns(T::Array[String]) }
29
32
  attr_accessor :authentication_headers
30
33
  end
@@ -14,6 +14,19 @@ module OpenAI
14
14
  def azure(endpoint: nil, api_key: nil, token_provider: nil)
15
15
  end
16
16
 
17
+ sig do
18
+ params(
19
+ project: T.nilable(String),
20
+ location: T.nilable(String),
21
+ base_url: T.nilable(String),
22
+ token_provider: T.nilable(T.proc.returns(String)),
23
+ credentials: T.untyped
24
+ )
25
+ .returns(OpenAI::Provider)
26
+ end
27
+ def vertex(project: nil, location: nil, base_url: nil, token_provider: nil, credentials: nil)
28
+ end
29
+
17
30
  sig do
18
31
  params(
19
32
  endpoint: T.nilable(T.any(String, Symbol)),
@@ -8,6 +8,7 @@ module OpenAI
8
8
  OpenAI::Internal::Transport::BaseClient::request_input
9
9
  ) -> OpenAI::Internal::Transport::BaseClient::request_input)?
10
10
  attr_accessor authentication_headers: Array[String]
11
+ attr_accessor wrap_request: Proc?
11
12
 
12
13
  def initialize: (
13
14
  name: String,
@@ -15,7 +16,8 @@ module OpenAI
15
16
  prepare_request: (^(
16
17
  OpenAI::Internal::Transport::BaseClient::request_input
17
18
  ) -> OpenAI::Internal::Transport::BaseClient::request_input)?,
18
- authentication_headers: Array[String]
19
+ authentication_headers: Array[String],
20
+ ?wrap_request: Proc?
19
21
  ) -> void
20
22
  end
21
23
 
@@ -6,6 +6,14 @@ module OpenAI
6
6
  ?token_provider: (^-> String)?
7
7
  ) -> OpenAI::Provider
8
8
 
9
+ def self.vertex: (
10
+ ?project: String?,
11
+ ?location: String?,
12
+ ?base_url: String?,
13
+ ?token_provider: (^-> String)?,
14
+ ?credentials: untyped
15
+ ) -> OpenAI::Provider
16
+
9
17
  def self.bedrock: (
10
18
  ?endpoint: (:mantle | :runtime | "mantle" | "runtime")?,
11
19
  ?region: String?,
data/vertex.md ADDED
@@ -0,0 +1,29 @@
1
+ # Google Vertex AI
2
+
3
+ Configure the standard `OpenAI::Client` to use the Responses API through Vertex AI. For Google Application Default Credentials (ADC), add the optional `googleauth` gem to your application's Gemfile and run `bundle install`:
4
+
5
+ ```ruby
6
+ gem "googleauth", "~> 1"
7
+ ```
8
+
9
+ Configure ADC in your environment, then create a client:
10
+
11
+ ```ruby
12
+ require "openai"
13
+
14
+ client = OpenAI::Client.new(
15
+ provider: OpenAI::Providers.vertex(
16
+ project: "your-project-id",
17
+ location: "your-vertex-location"
18
+ )
19
+ )
20
+
21
+ response = client.responses.create(model: "your-enabled-model", input: "Say hello!")
22
+ puts(response.output_text)
23
+ ```
24
+
25
+ Use a model enabled for your project and location. Vertex AI determines which Responses features are available.
26
+
27
+ To manage credentials in your application, pass either `credentials:` with a Google credentials object or `token_provider:` with a callback that returns a current access token. The callback runs once per request attempt, including retries; cache and refresh tokens as needed. The `googleauth` gem is not required when using `token_provider:`.
28
+
29
+ To use a test server or trusted gateway, set `base_url:` to the full API root before `/responses`. The configured origin receives your bearer token. HTTP test roots are supported; use HTTPS in production.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.108.0
4
+ version: 0.109.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OpenAI
@@ -49,6 +49,7 @@ extra_rdoc_files:
49
49
  - local-audio.md
50
50
  - realtime.md
51
51
  - responses-websocket.md
52
+ - vertex.md
52
53
  files:
53
54
  - ".ignore"
54
55
  - CHANGELOG.md
@@ -1648,6 +1649,7 @@ files:
1648
1649
  - lib/openai/provider.rb
1649
1650
  - lib/openai/providers/azure.rb
1650
1651
  - lib/openai/providers/bedrock.rb
1652
+ - lib/openai/providers/vertex.rb
1651
1653
  - lib/openai/request_options.rb
1652
1654
  - lib/openai/resources/admin.rb
1653
1655
  - lib/openai/resources/admin/organization.rb
@@ -5093,6 +5095,7 @@ files:
5093
5095
  - sig/openai/response_carrier.rbs
5094
5096
  - sig/openai/streaming.rbs
5095
5097
  - sig/openai/version.rbs
5098
+ - vertex.md
5096
5099
  homepage: https://gemdocs.org/gems/openai
5097
5100
  licenses:
5098
5101
  - Apache-2.0