openai 0.86.0 → 0.87.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/README.md +10 -1
  4. data/examples/audio/record_and_play.rb +19 -0
  5. data/examples/responses/websocket_workflows.rb +159 -0
  6. data/lib/openai/helpers/local_audio/errors.rb +27 -0
  7. data/lib/openai/helpers/local_audio/process.rb +158 -0
  8. data/lib/openai/helpers/local_audio.rb +157 -0
  9. data/lib/openai/helpers/realtime/connection.rb +20 -2
  10. data/lib/openai/helpers/realtime/errors.rb +18 -0
  11. data/lib/openai/helpers/realtime/recovery.rb +489 -0
  12. data/lib/openai/helpers/realtime/resources/realtime_extension.rb +26 -1
  13. data/lib/openai/helpers/realtime.rb +1 -0
  14. data/lib/openai/models/beta/beta_response_input_item.rb +84 -1
  15. data/lib/openai/models/beta/beta_response_item.rb +72 -1
  16. data/lib/openai/models/beta/beta_response_output_item.rb +84 -1
  17. data/lib/openai/models/beta/beta_responses_client_event.rb +3 -0
  18. data/lib/openai/models/beta/beta_tool.rb +69 -37
  19. data/lib/openai/models/beta/response_create_params.rb +3 -0
  20. data/lib/openai/models/conversations/conversation_item.rb +82 -1
  21. data/lib/openai/models/image_edit_completed_event.rb +34 -8
  22. data/lib/openai/models/image_edit_params.rb +64 -44
  23. data/lib/openai/models/image_edit_partial_image_event.rb +34 -8
  24. data/lib/openai/models/image_gen_completed_event.rb +34 -8
  25. data/lib/openai/models/image_gen_partial_image_event.rb +34 -8
  26. data/lib/openai/models/image_generate_params.rb +51 -35
  27. data/lib/openai/models/image_model.rb +4 -0
  28. data/lib/openai/models/images_response.rb +37 -13
  29. data/lib/openai/models/responses/response_create_params.rb +3 -0
  30. data/lib/openai/models/responses/response_input_item.rb +84 -1
  31. data/lib/openai/models/responses/response_item.rb +72 -1
  32. data/lib/openai/models/responses/response_output_item.rb +84 -1
  33. data/lib/openai/models/responses/responses_client_event.rb +3 -0
  34. data/lib/openai/models/responses/tool.rb +69 -37
  35. data/lib/openai/models/webhooks/live_call_incoming_webhook_event.rb +3 -3
  36. data/lib/openai/models/webhooks/realtime_call_incoming_webhook_event.rb +5 -3
  37. data/lib/openai/resources/images.rb +12 -14
  38. data/lib/openai/version.rb +1 -1
  39. data/local-audio.md +146 -0
  40. data/rbi/openai/helpers/local_audio/interface.rbi +110 -0
  41. data/rbi/openai/helpers/realtime/connection.rbi +18 -2
  42. data/rbi/openai/helpers/realtime/extensions.rbi +8 -0
  43. data/rbi/openai/helpers/realtime/recovery.rbi +27 -0
  44. data/rbi/openai/models/beta/beta_response_input_item.rbi +86 -1
  45. data/rbi/openai/models/beta/beta_response_item.rbi +78 -1
  46. data/rbi/openai/models/beta/beta_response_output_item.rbi +84 -1
  47. data/rbi/openai/models/beta/beta_responses_client_event.rbi +6 -0
  48. data/rbi/openai/models/beta/beta_tool.rbi +90 -51
  49. data/rbi/openai/models/beta/response_create_params.rbi +6 -0
  50. data/rbi/openai/models/conversations/conversation_item.rbi +88 -1
  51. data/rbi/openai/models/image_edit_completed_event.rbi +20 -9
  52. data/rbi/openai/models/image_edit_params.rbi +89 -60
  53. data/rbi/openai/models/image_edit_partial_image_event.rbi +20 -9
  54. data/rbi/openai/models/image_gen_completed_event.rbi +20 -9
  55. data/rbi/openai/models/image_gen_partial_image_event.rbi +20 -9
  56. data/rbi/openai/models/image_generate_params.rbi +71 -48
  57. data/rbi/openai/models/image_model.rbi +4 -0
  58. data/rbi/openai/models/images_response.rbi +27 -16
  59. data/rbi/openai/models/responses/response_create_params.rbi +6 -0
  60. data/rbi/openai/models/responses/response_input_item.rbi +88 -1
  61. data/rbi/openai/models/responses/response_item.rbi +80 -1
  62. data/rbi/openai/models/responses/response_output_item.rbi +84 -1
  63. data/rbi/openai/models/responses/responses_client_event.rbi +6 -0
  64. data/rbi/openai/models/responses/tool.rbi +90 -51
  65. data/rbi/openai/models/webhooks/live_call_incoming_webhook_event.rbi +4 -4
  66. data/rbi/openai/models/webhooks/realtime_call_incoming_webhook_event.rbi +8 -4
  67. data/rbi/openai/resources/beta/responses.rbi +6 -2
  68. data/rbi/openai/resources/images.rbi +106 -76
  69. data/rbi/openai/resources/responses.rbi +6 -0
  70. data/realtime.md +101 -0
  71. data/responses-websocket.md +182 -0
  72. data/sig/openai/helpers/local_audio/interface.rbs +62 -0
  73. data/sig/openai/helpers/realtime/connection.rbs +9 -1
  74. data/sig/openai/helpers/realtime/extensions.rbs +4 -0
  75. data/sig/openai/helpers/realtime/recovery.rbs +19 -0
  76. data/sig/openai/models/beta/beta_response_input_item.rbs +39 -2
  77. data/sig/openai/models/beta/beta_response_item.rbs +39 -2
  78. data/sig/openai/models/beta/beta_response_output_item.rbs +39 -2
  79. data/sig/openai/models/beta/beta_tool.rbs +11 -1
  80. data/sig/openai/models/conversations/conversation_item.rbs +39 -2
  81. data/sig/openai/models/image_edit_completed_event.rbs +11 -4
  82. data/sig/openai/models/image_edit_params.rbs +3 -1
  83. data/sig/openai/models/image_edit_partial_image_event.rbs +11 -4
  84. data/sig/openai/models/image_gen_completed_event.rbs +11 -4
  85. data/sig/openai/models/image_gen_partial_image_event.rbs +11 -4
  86. data/sig/openai/models/image_generate_params.rbs +4 -1
  87. data/sig/openai/models/image_model.rbs +8 -0
  88. data/sig/openai/models/images_response.rbs +10 -4
  89. data/sig/openai/models/responses/response_input_item.rbs +39 -2
  90. data/sig/openai/models/responses/response_item.rbs +39 -2
  91. data/sig/openai/models/responses/response_output_item.rbs +39 -2
  92. data/sig/openai/models/responses/tool.rbs +11 -1
  93. metadata +15 -1
@@ -0,0 +1,489 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenAI
4
+ module Realtime
5
+ # Owns successive block-scoped sockets on one Async reactor. Calls from other
6
+ # fibers or threads use mailboxes; the physical socket never leaves its owner.
7
+ # No incoming events are prefetched: a receive request drives each read.
8
+ #
9
+ # @api private
10
+ class Recovery
11
+ def self.validate_options!(max_reconnect_attempts:, max_queue_bytes:, on_reconnected:)
12
+ {max_reconnect_attempts: max_reconnect_attempts, max_queue_bytes: max_queue_bytes}.each do |name, value|
13
+ unless value.is_a?(Integer) && value >= 0
14
+ raise ArgumentError, "`#{name}` must be a nonnegative Integer."
15
+ end
16
+ end
17
+
18
+ unless on_reconnected.nil? || on_reconnected.respond_to?(:call)
19
+ raise ArgumentError, "`on_reconnected` must respond to `call`."
20
+ end
21
+ end
22
+
23
+ def initialize(manager:, max_reconnect_attempts:, max_queue_bytes:, on_reconnected:)
24
+ @manager = manager
25
+ @max_attempts = max_reconnect_attempts
26
+ @max_queue_bytes = max_queue_bytes
27
+ @on_reconnected = on_reconnected
28
+ @mutex = Mutex.new
29
+ @commands = Queue.new
30
+ @reads = Queue.new
31
+ @stop_requests = Queue.new
32
+ @ready = Queue.new
33
+ @waiters = {}
34
+ @pending = []
35
+ @pending_bytes = 0
36
+ @state = :connecting
37
+ @generation = 0
38
+ @attempts = 0
39
+ @flushing = false
40
+ end
41
+
42
+ def inspect = "#<#{self.class.name}>"
43
+
44
+ def open
45
+ load_async
46
+ ::Kernel.Sync() do |root|
47
+ stopper = root.async do
48
+ @stop_requests.pop
49
+ @lifecycle&.stop
50
+ end
51
+
52
+ @lifecycle = root.async { run }
53
+ begin
54
+ kind, value = @ready.pop
55
+ raise value, cause: nil if kind == :error
56
+
57
+ result = yield(value)
58
+ raise @error, cause: nil if @error
59
+
60
+ result
61
+ ensure
62
+ $! ? abort : close
63
+ @lifecycle.wait
64
+ stopper.stop
65
+ end
66
+ end
67
+ end
68
+
69
+ def closed? = @mutex.synchronize { @state == :closed }
70
+
71
+ def reconnecting?
72
+ @mutex.synchronize { @state == :recovering || @state == :restoring }
73
+ end
74
+
75
+ def pending_messages = @mutex.synchronize { @pending.dup }
76
+
77
+ def take_pending_messages
78
+ @mutex.synchronize do
79
+ raise state_error("Cannot remove queued events while flushing.") if @flushing
80
+
81
+ messages = @pending
82
+ @pending = []
83
+ @pending_bytes = 0
84
+ messages
85
+ end
86
+ end
87
+
88
+ def read
89
+ return @physical.receive_raw if Fiber.current.equal?(@owner_fiber)
90
+
91
+ reply = @mutex.synchronize do
92
+ raise @error, cause: nil if @error
93
+ return nil if @state == :closed
94
+
95
+ register(:read).tap { @reads << _1 }
96
+ end
97
+
98
+ await_reply(reply)
99
+ end
100
+
101
+ def write(text)
102
+ return write_once(text) if Fiber.current.equal?(@owner_fiber)
103
+
104
+ reply = @mutex.synchronize do
105
+ raise write_error(text), cause: nil if @error || [:closed, :ending].include?(@state)
106
+
107
+ if @state != :open || @flushing
108
+ if @max_queue_bytes.zero? || @pending_bytes + [text.bytesize, 1].max > @max_queue_bytes
109
+ raise OpenAI::Errors::RealtimeQueueFullError
110
+ end
111
+
112
+ @pending << text.dup.freeze
113
+ @pending_bytes += [text.bytesize, 1].max
114
+ return nil
115
+ end
116
+
117
+ encoded = text.dup.freeze
118
+ register(:write, encoded).tap { @commands << [:write, encoded, _1, @generation] }
119
+ end
120
+
121
+ await_reply(reply)
122
+ nil
123
+ end
124
+
125
+ def flush_pending
126
+ if Fiber.current.equal?(@owner_fiber)
127
+ raise state_error("Queue flush interrupted; remaining events were not sent.") unless flush
128
+ return nil
129
+ end
130
+
131
+ reply = @mutex.synchronize do
132
+ raise @error, cause: nil if @error
133
+ raise state_error("Cannot flush until the replacement connection is ready.") unless @state == :open
134
+
135
+ register(:flush).tap { @commands << [:flush, nil, _1, @generation] }
136
+ end
137
+
138
+ await_reply(reply)
139
+ nil
140
+ end
141
+
142
+ # Cancel outstanding work and make a bounded attempt to send the close frame.
143
+ def close(code: 1000, reason: "")
144
+ stop({code: code, reason: reason})
145
+ end
146
+
147
+ def abort = stop(nil)
148
+
149
+ private def stop(close_options)
150
+ changed = @mutex.synchronize do
151
+ next false if @state == :closed
152
+ if @inflight
153
+ fail_locked(
154
+ "Realtime connection closed during a send; delivery is uncertain.",
155
+ uncertain_message: @inflight
156
+ )
157
+ else
158
+ @close_options = close_options
159
+ finish_locked
160
+ end
161
+
162
+ true
163
+ end
164
+
165
+ @stop_requests << true if changed
166
+ nil
167
+ end
168
+
169
+ private def register(kind, text = nil)
170
+ Queue.new.tap { @waiters[_1] = [kind, text] }
171
+ end
172
+
173
+ private def load_async
174
+ require "async"
175
+
176
+ rescue LoadError => e
177
+ raise(
178
+ LoadError.new(
179
+ "Realtime reconnect requires Async. Add `gem \"async\"` (or `gem \"async-websocket\"`) to your Gemfile."
180
+ ),
181
+ cause: e
182
+ )
183
+ end
184
+
185
+ private def await_reply(reply)
186
+ kind, value = reply.pop
187
+ raise value, cause: nil if kind == :error
188
+
189
+ value
190
+ ensure
191
+ # Cancellation must not leave a read consuming an event for an abandoned
192
+ # caller, or a write happening later without an observable outcome.
193
+ abort if @mutex.synchronize { @waiters.key?(reply) }
194
+ end
195
+
196
+ private def respond(reply, value = nil, error: nil)
197
+ return unless reply
198
+
199
+ @mutex.synchronize { respond_locked(reply, value, error: error) }
200
+ end
201
+
202
+ private def respond_locked(reply, value = nil, error: nil)
203
+ if (waiter = @waiters.delete(reply))
204
+ failure = if error && waiter.first == :write
205
+ write_error(waiter.last, attempted: reply.equal?(@write_reply))
206
+ else
207
+ error
208
+ end
209
+
210
+ reply << [failure ? :error : :ok, failure || value]
211
+ end
212
+ end
213
+
214
+ private def run
215
+ loop do
216
+ break if closed?
217
+
218
+ begin
219
+ @manager.open { |physical| serve(physical) }
220
+ break
221
+ rescue StandardError, LoadError => e
222
+ break if closed?
223
+ if @callback_failed || e.is_a?(OpenAI::Errors::RealtimeReconnectError) || e.is_a?(LoadError)
224
+ finish(e)
225
+ break
226
+ end
227
+
228
+ unless recoverable?(e) && @attempts < @max_attempts
229
+ @mutex.synchronize { fail_locked("Realtime reconnect stopped; the connection could not be recovered.") }
230
+ break
231
+ end
232
+
233
+ @mutex.synchronize { @state = :recovering unless @state == :closed }
234
+ @attempts += 1
235
+ sleep(retry_delay)
236
+ end
237
+ end
238
+
239
+ ensure
240
+ finish
241
+ @ready << [:error, @error || state_error("Realtime connection closed before opening.")] unless @connection
242
+ end
243
+
244
+ private def serve(physical)
245
+ @owner_fiber = Fiber.current
246
+ @physical = physical
247
+ @mutex.synchronize do
248
+ return if @state == :closed
249
+
250
+ @generation += 1
251
+ @state = @connection ? :restoring : :open
252
+ end
253
+
254
+ if @connection
255
+ begin
256
+ @on_reconnected&.call(@connection)
257
+ rescue StandardError, LoadError
258
+ @callback_failed = true
259
+ raise
260
+ end
261
+ else
262
+ @connection = OpenAI::Realtime::Connection.new(socket: self, url: physical.url, recovery: self)
263
+ @ready << [:ok, @connection]
264
+ end
265
+
266
+ return if closed?
267
+
268
+ @mutex.synchronize { @state = :open unless @state == :closed }
269
+ reader = ::Async::Task.current.async { read_loop }
270
+ until closed?
271
+ kind, data, reply, generation = @commands.pop
272
+ case kind
273
+ when :lost
274
+ raise data
275
+ when :end
276
+ break
277
+ else
278
+ writable = @mutex.synchronize { @state == :open && generation == @generation }
279
+ unless writable
280
+ respond(reply, error: state_error("Connection interrupted before sending; event was not sent."))
281
+ next
282
+ end
283
+
284
+ if kind == :write
285
+ write_once(data, reply: reply)
286
+ elsif flush
287
+ respond(reply)
288
+ else
289
+ respond(reply, error: state_error("Queue flush interrupted; remaining events were not sent."))
290
+ end
291
+ end
292
+ end
293
+
294
+ ensure
295
+ begin
296
+ reader&.stop
297
+ ensure
298
+ begin
299
+ if @close_options
300
+ ::Async::Task.current.with_timeout(1) { physical.close(**@close_options) }
301
+ end
302
+
303
+ rescue StandardError
304
+ # Shutdown must not mask the operation's result or prevent cleanup.
305
+ nil
306
+ ensure
307
+ physical.abort
308
+ @physical = nil
309
+ end
310
+ end
311
+ end
312
+
313
+ private def read_loop
314
+ loop do
315
+ @read_reply ||= @reads.pop
316
+ value = @physical.receive_raw
317
+ if value.nil?
318
+ @mutex.synchronize do
319
+ if @inflight || !@pending.empty?
320
+ fail_locked(
321
+ "Realtime connection closed with pending sends; in-flight delivery is uncertain.",
322
+ uncertain_message: @inflight
323
+ )
324
+ elsif @state != :closed
325
+ @state = :ending
326
+ end
327
+ end
328
+
329
+ if closed?
330
+ @stop_requests << true
331
+ return
332
+ end
333
+ end
334
+
335
+ respond(@read_reply, value)
336
+ @read_reply = nil
337
+ if value.nil?
338
+ @commands << [:end]
339
+ break
340
+ end
341
+ end
342
+
343
+ rescue StandardError => e
344
+ @mutex.synchronize do
345
+ if @inflight
346
+ fail_locked(
347
+ "Realtime connection interrupted during a send; delivery is uncertain.",
348
+ uncertain_message: @inflight
349
+ )
350
+ elsif @state != :closed
351
+ @state = :recovering
352
+ end
353
+ end
354
+
355
+ if closed?
356
+ @stop_requests << true
357
+ else
358
+ @commands << [:lost, e]
359
+ end
360
+ end
361
+
362
+ private def write_once(text, queued: false, reply: nil)
363
+ @mutex.synchronize do
364
+ raise write_error(text), cause: nil if @state == :closed
365
+ @inflight = text.dup.freeze
366
+ @write_reply = reply
367
+ end
368
+
369
+ begin
370
+ @physical.send_raw(text)
371
+ sent = true
372
+ rescue StandardError
373
+ error = @mutex.synchronize do
374
+ global_error = fail_locked(
375
+ "Realtime send failed; delivery is uncertain and the event was not retried.",
376
+ uncertain_message: text
377
+ )
378
+ queued ? global_error : write_error(text, attempted: true)
379
+ end
380
+
381
+ raise error, cause: nil
382
+ ensure
383
+ @mutex.synchronize do
384
+ respond_locked(reply) if sent && reply
385
+ @inflight = nil
386
+ @write_reply = nil
387
+ if queued && @pending.first.equal?(text)
388
+ @pending.shift
389
+ @pending_bytes -= [text.bytesize, 1].max
390
+ end
391
+ end
392
+ end
393
+ end
394
+
395
+ private def flush
396
+ @mutex.synchronize { @flushing = true }
397
+ loop do
398
+ text = @mutex.synchronize do
399
+ return false unless [:open, :restoring].include?(@state)
400
+ @pending.first
401
+ end
402
+
403
+ break unless text
404
+
405
+ write_once(text, queued: true)
406
+ end
407
+
408
+ true
409
+ ensure
410
+ @mutex.synchronize { @flushing = false }
411
+ end
412
+
413
+ private def finish(error = nil)
414
+ @mutex.synchronize { finish_locked(error) }
415
+ end
416
+
417
+ private def finish_locked(error = nil)
418
+ return if @state == :closed
419
+
420
+ @error = error
421
+ @state = :closed
422
+ @waiters.each do |reply, (kind, text)|
423
+ failure = if kind == :write
424
+ write_error(text, attempted: reply.equal?(@write_reply))
425
+ else
426
+ error || (kind == :read ? nil : state_error("Realtime connection closed before completing the operation."))
427
+ end
428
+
429
+ reply << [failure ? :error : :ok, failure]
430
+ end
431
+
432
+ @waiters.clear
433
+ @commands.clear
434
+ @reads.clear
435
+ @read_reply = nil
436
+ end
437
+
438
+ private def write_error(text, attempted: false)
439
+ encoded = text.dup.freeze
440
+ OpenAI::Errors::RealtimeReconnectError.new(
441
+ message: attempted ? "Realtime send interrupted; delivery is uncertain." : "Realtime send interrupted; event was not sent.",
442
+ unsent_messages: attempted ? [] : [encoded],
443
+ uncertain_message: attempted ? encoded : nil
444
+ )
445
+ end
446
+
447
+ private def fail_locked(message, uncertain_message: nil)
448
+ return @error if @state == :closed && @error
449
+
450
+ if @flushing && uncertain_message && @pending.first == uncertain_message
451
+ @pending.shift
452
+ @pending_bytes -= [uncertain_message.bytesize, 1].max
453
+ end
454
+
455
+ error = OpenAI::Errors::RealtimeReconnectError.new(
456
+ message: message,
457
+ unsent_messages: @pending,
458
+ uncertain_message: uncertain_message&.dup&.freeze
459
+ )
460
+ finish_locked(error)
461
+ error
462
+ end
463
+
464
+ private def state_error(message)
465
+ OpenAI::Errors::RealtimeReconnectError.new(message: message)
466
+ end
467
+
468
+ private def retry_delay = [0.5 * (2 ** [@attempts - 1, 4].min), 8.0].min * (0.75 + (rand * 0.25))
469
+
470
+ private def recoverable?(error)
471
+ return false unless error.is_a?(OpenAI::Errors::RealtimeConnectionError)
472
+ return [408, 429, 500, 502, 503, 504].include?(error.http_status) if error.http_status
473
+
474
+ cause = error.cause
475
+ if defined?(::Protocol::WebSocket::ClosedError) && cause.is_a?(::Protocol::WebSocket::ClosedError)
476
+ return [1001, 1005, 1006, 1011, 1012, 1013].include?(cause.code)
477
+ end
478
+
479
+ cause.is_a?(EOFError) ||
480
+ cause.is_a?(Errno::ECONNRESET) ||
481
+ cause.is_a?(Errno::ECONNREFUSED) ||
482
+ cause.is_a?(Errno::ETIMEDOUT) ||
483
+ cause.is_a?(Errno::EPIPE) ||
484
+ cause.is_a?(Timeout::Error) ||
485
+ cause.is_a?(::Async::TimeoutError)
486
+ end
487
+ end
488
+ end
489
+ end
@@ -14,9 +14,25 @@ module OpenAI
14
14
  request_options: nil,
15
15
  transport: nil,
16
16
  transport_options: {},
17
+ reconnect: false,
18
+ max_reconnect_attempts: 5,
19
+ max_queue_bytes: 0,
20
+ on_reconnected: nil,
17
21
  &block
18
22
  )
19
23
  raise ArgumentError, "A block is required to open a Realtime WebSocket." unless block
24
+ unless reconnect == true || reconnect == false
25
+ raise ArgumentError, "`reconnect` must be true or false."
26
+ end
27
+
28
+ OpenAI::Realtime::Recovery.validate_options!(
29
+ max_reconnect_attempts: max_reconnect_attempts,
30
+ max_queue_bytes: max_queue_bytes,
31
+ on_reconnected: on_reconnected
32
+ )
33
+ if !reconnect && (max_queue_bytes.positive? || on_reconnected)
34
+ raise ArgumentError, "Queueing and recovery callbacks require `reconnect: true`."
35
+ end
20
36
 
21
37
  manager = OpenAI::Realtime::ConnectionManager.new(
22
38
  client: @client,
@@ -26,7 +42,16 @@ module OpenAI
26
42
  request_options: request_options,
27
43
  transport_options: transport_options
28
44
  )
29
- manager.open(&block)
45
+ return manager.open(&block) unless reconnect
46
+
47
+ OpenAI::Realtime::Recovery
48
+ .new(
49
+ manager: manager,
50
+ max_reconnect_attempts: max_reconnect_attempts,
51
+ max_queue_bytes: max_queue_bytes,
52
+ on_reconnected: on_reconnected
53
+ )
54
+ .open(&block)
30
55
  end
31
56
 
32
57
  # Attach a server-side control WebSocket to an existing WebRTC or SIP call.
@@ -10,6 +10,7 @@ require_relative "realtime/connection_resources"
10
10
  require_relative "realtime/connection"
11
11
  require_relative "realtime/sideband_connection"
12
12
  require_relative "realtime/connection_manager"
13
+ require_relative "realtime/recovery"
13
14
  require_relative "realtime/transports/async_websocket"
14
15
  require_relative "realtime/client_extension"
15
16
  require_relative "realtime/resources/realtime_extension"
@@ -1224,7 +1224,30 @@ module OpenAI
1224
1224
  # @return [OpenAI::Models::Beta::BetaResponseInputItem::ImageGenerationCall::Agent, nil]
1225
1225
  optional :agent, -> { OpenAI::Beta::BetaResponseInputItem::ImageGenerationCall::Agent }, nil?: true
1226
1226
 
1227
- # @!method initialize(id:, result:, status:, agent: nil, type: :image_generation_call)
1227
+ # @!attribute quality
1228
+ # The quality of the image generated by the image generation tool call. One of
1229
+ # `low`, `medium`, `high`, `xhigh`, `max`, or `auto`.
1230
+ #
1231
+ # @return [Symbol, OpenAI::Models::Beta::BetaResponseInputItem::ImageGenerationCall::Quality, nil]
1232
+ optional(
1233
+ :quality,
1234
+ enum: -> { OpenAI::Beta::BetaResponseInputItem::ImageGenerationCall::Quality },
1235
+ nil?: true
1236
+ )
1237
+
1238
+ # @!attribute size
1239
+ # The image dimensions as a `WIDTHxHEIGHT` string, for example `1536x864`.
1240
+ #
1241
+ # @return [String, Symbol, OpenAI::Models::Beta::BetaResponseInputItem::ImageGenerationCall::Size, nil]
1242
+ optional(
1243
+ :size,
1244
+ union: -> {
1245
+ OpenAI::Beta::BetaResponseInputItem::ImageGenerationCall::Size
1246
+ },
1247
+ nil?: true
1248
+ )
1249
+
1250
+ # @!method initialize(id:, result:, status:, agent: nil, quality: nil, size: nil, type: :image_generation_call)
1228
1251
  # Some parameter documentations has been truncated, see
1229
1252
  # {OpenAI::Models::Beta::BetaResponseInputItem::ImageGenerationCall} for more
1230
1253
  # details.
@@ -1239,6 +1262,10 @@ module OpenAI
1239
1262
  #
1240
1263
  # @param agent [OpenAI::Models::Beta::BetaResponseInputItem::ImageGenerationCall::Agent, nil] The agent that produced this item.
1241
1264
  #
1265
+ # @param quality [Symbol, OpenAI::Models::Beta::BetaResponseInputItem::ImageGenerationCall::Quality, nil] The quality of the image generated by the image generation tool call. One of `lo
1266
+ #
1267
+ # @param size [String, Symbol, OpenAI::Models::Beta::BetaResponseInputItem::ImageGenerationCall::Size, nil] The image dimensions as a `WIDTHxHEIGHT` string, for example `1536x864`.
1268
+ #
1242
1269
  # @param type [Symbol, :image_generation_call] The type of the image generation call. Always `image_generation_call`.
1243
1270
 
1244
1271
  # The status of the image generation call.
@@ -1269,6 +1296,62 @@ module OpenAI
1269
1296
  #
1270
1297
  # @param agent_name [String] The canonical name of the agent that produced this item.
1271
1298
  end
1299
+
1300
+ # The quality of the image generated by the image generation tool call. One of
1301
+ # `low`, `medium`, `high`, `xhigh`, `max`, or `auto`.
1302
+ #
1303
+ # @see OpenAI::Models::Beta::BetaResponseInputItem::ImageGenerationCall#quality
1304
+ module Quality
1305
+ extend OpenAI::Internal::Type::Enum
1306
+
1307
+ LOW = :low
1308
+ MEDIUM = :medium
1309
+ HIGH = :high
1310
+ XHIGH = :xhigh
1311
+ MAX = :max
1312
+ AUTO = :auto
1313
+
1314
+ # @!method self.values
1315
+ # @return [Array<Symbol>]
1316
+ end
1317
+
1318
+ # The image dimensions as a `WIDTHxHEIGHT` string, for example `1536x864`.
1319
+ #
1320
+ # @see OpenAI::Models::Beta::BetaResponseInputItem::ImageGenerationCall#size
1321
+ module Size
1322
+ extend OpenAI::Internal::Type::Union
1323
+
1324
+ variant String
1325
+
1326
+ variant(
1327
+ const: -> { OpenAI::Models::Beta::BetaResponseInputItem::ImageGenerationCall::Size::SIZE_1024X1024 }
1328
+ )
1329
+
1330
+ variant(
1331
+ const: -> { OpenAI::Models::Beta::BetaResponseInputItem::ImageGenerationCall::Size::SIZE_1024X1536 }
1332
+ )
1333
+
1334
+ variant(
1335
+ const: -> { OpenAI::Models::Beta::BetaResponseInputItem::ImageGenerationCall::Size::SIZE_1536X1024 }
1336
+ )
1337
+
1338
+ # @!method self.variants
1339
+ # @return [Array(String, Symbol)]
1340
+
1341
+ define_sorbet_constant!(:Variants) do
1342
+ T.type_alias {
1343
+ T.any(String, OpenAI::Beta::BetaResponseInputItem::ImageGenerationCall::Size::TaggedSymbol)
1344
+ }
1345
+ end
1346
+
1347
+ # @!group
1348
+
1349
+ SIZE_1024X1024 = :"1024x1024"
1350
+ SIZE_1024X1536 = :"1024x1536"
1351
+ SIZE_1536X1024 = :"1536x1024"
1352
+
1353
+ # @!endgroup
1354
+ end
1272
1355
  end
1273
1356
 
1274
1357
  class LocalShellCall < OpenAI::Internal::Type::BaseModel