openai 0.78.0 → 0.79.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 (175) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -0
  3. data/README.md +241 -8
  4. data/SECURITY.md +6 -22
  5. data/azure.md +110 -0
  6. data/bedrock.md +132 -0
  7. data/examples/mtls_custom_http_client.rb +61 -0
  8. data/lib/openai/auth/workload_identity.rb +13 -2
  9. data/lib/openai/auth/workload_identity_auth.rb +2 -1
  10. data/lib/openai/client.rb +29 -6
  11. data/lib/openai/errors.rb +11 -1
  12. data/lib/openai/helpers/streaming/chat_completion_stream.rb +3 -3
  13. data/lib/openai/helpers/streaming/response_stream.rb +5 -6
  14. data/lib/openai/helpers/structured_output/base_model.rb +20 -19
  15. data/lib/openai/helpers/structured_output/json_schema_converter.rb +12 -5
  16. data/lib/openai/http_client.rb +99 -7
  17. data/lib/openai/internal/conversation_cursor_page.rb +5 -6
  18. data/lib/openai/internal/cursor_page.rb +3 -3
  19. data/lib/openai/internal/logging.rb +419 -0
  20. data/lib/openai/internal/next_cursor_page.rb +5 -6
  21. data/lib/openai/internal/page.rb +2 -2
  22. data/lib/openai/internal/provider.rb +7 -1
  23. data/lib/openai/internal/read_io_adapter.rb +42 -18
  24. data/lib/openai/internal/stream.rb +2 -2
  25. data/lib/openai/internal/transport/base_client.rb +137 -37
  26. data/lib/openai/internal/type/array_of.rb +6 -4
  27. data/lib/openai/internal/type/base_model.rb +39 -1
  28. data/lib/openai/internal/type/base_page.rb +19 -2
  29. data/lib/openai/internal/type/base_stream.rb +39 -7
  30. data/lib/openai/internal/type/hash_of.rb +3 -3
  31. data/lib/openai/internal/util.rb +52 -27
  32. data/lib/openai/models/admin/organization/audit_log_list_params.rb +3 -1
  33. data/lib/openai/models/admin/organization/audit_log_list_response.rb +33 -11
  34. data/lib/openai/models/admin/organization/groups/role_list_response.rb +5 -1
  35. data/lib/openai/models/admin/organization/groups/role_retrieve_response.rb +5 -1
  36. data/lib/openai/models/admin/organization/projects/groups/role_list_response.rb +6 -1
  37. data/lib/openai/models/admin/organization/projects/groups/role_retrieve_response.rb +6 -1
  38. data/lib/openai/models/admin/organization/projects/service_account_create_response.rb +3 -1
  39. data/lib/openai/models/admin/organization/projects/users/role_list_response.rb +6 -1
  40. data/lib/openai/models/admin/organization/projects/users/role_retrieve_response.rb +6 -1
  41. data/lib/openai/models/admin/organization/users/role_list_response.rb +5 -1
  42. data/lib/openai/models/admin/organization/users/role_retrieve_response.rb +5 -1
  43. data/lib/openai/models/all_models.rb +3 -0
  44. data/lib/openai/models/audio/transcription_create_params.rb +7 -1
  45. data/lib/openai/models/audio/translation_create_params.rb +7 -1
  46. data/lib/openai/models/beta/beta_response.rb +9 -0
  47. data/lib/openai/models/beta/beta_response_stream_event.rb +1 -1
  48. data/lib/openai/models/beta/beta_responses_client_event.rb +28 -3
  49. data/lib/openai/models/beta/beta_responses_server_event.rb +1037 -61
  50. data/lib/openai/models/beta/response_compact_params.rb +9 -0
  51. data/lib/openai/models/beta/response_create_params.rb +9 -0
  52. data/lib/openai/models/containers/file_create_params.rb +4 -0
  53. data/lib/openai/models/content_provenance_check_create_params.rb +4 -0
  54. data/lib/openai/models/file_create_params.rb +4 -0
  55. data/lib/openai/models/image_create_variation_params.rb +4 -0
  56. data/lib/openai/models/image_edit_params.rb +8 -0
  57. data/lib/openai/models/responses/response_compact_params.rb +9 -0
  58. data/lib/openai/models/responses/response_stream_event.rb +1 -1
  59. data/lib/openai/models/responses/responses_client_event.rb +13 -1
  60. data/lib/openai/models/responses/responses_server_event.rb +1027 -54
  61. data/lib/openai/models/responses_model.rb +3 -0
  62. data/lib/openai/models/skill_create_params.rb +4 -0
  63. data/lib/openai/models/skills/version_create_params.rb +4 -0
  64. data/lib/openai/models/uploads/part_create_params.rb +4 -0
  65. data/lib/openai/models/vector_store_search_response.rb +3 -1
  66. data/lib/openai/models/video_create_character_params.rb +4 -0
  67. data/lib/openai/models/video_create_params.rb +4 -0
  68. data/lib/openai/models/video_edit_params.rb +4 -0
  69. data/lib/openai/models/video_extend_params.rb +4 -0
  70. data/lib/openai/models.rb +9 -1
  71. data/lib/openai/net_http_client.rb +20 -6
  72. data/lib/openai/providers/azure.rb +216 -0
  73. data/lib/openai/providers/bedrock.rb +3 -1
  74. data/lib/openai/resources/admin/organization/projects/groups/roles.rb +1 -1
  75. data/lib/openai/resources/admin/organization/projects/groups.rb +1 -1
  76. data/lib/openai/resources/admin/organization/projects/users/roles.rb +1 -1
  77. data/lib/openai/resources/admin/organization/projects.rb +4 -2
  78. data/lib/openai/resources/audio/transcriptions.rb +8 -0
  79. data/lib/openai/resources/audio/translations.rb +4 -0
  80. data/lib/openai/resources/beta/responses.rb +1 -1
  81. data/lib/openai/resources/beta/threads/runs/steps.rb +2 -2
  82. data/lib/openai/resources/containers/files.rb +4 -0
  83. data/lib/openai/resources/content_provenance_checks.rb +4 -0
  84. data/lib/openai/resources/conversations/items.rb +1 -1
  85. data/lib/openai/resources/evals/runs/output_items.rb +1 -1
  86. data/lib/openai/resources/files.rb +4 -0
  87. data/lib/openai/resources/images.rb +12 -0
  88. data/lib/openai/resources/responses.rb +3 -2
  89. data/lib/openai/resources/skills/versions.rb +4 -0
  90. data/lib/openai/resources/skills.rb +4 -0
  91. data/lib/openai/resources/uploads/parts.rb +4 -0
  92. data/lib/openai/resources/vector_stores/file_batches.rb +1 -1
  93. data/lib/openai/resources/videos.rb +38 -0
  94. data/lib/openai/resources/webhooks.rb +18 -6
  95. data/lib/openai/version.rb +1 -1
  96. data/lib/openai.rb +111 -107
  97. data/rbi/openai/auth.rbi +4 -4
  98. data/rbi/openai/client.rbi +13 -3
  99. data/rbi/openai/errors.rbi +7 -0
  100. data/rbi/openai/helpers/streaming/events.rbi +12 -0
  101. data/rbi/openai/http_client.rbi +62 -2
  102. data/rbi/openai/internal/logging.rbi +192 -0
  103. data/rbi/openai/internal/provider.rbi +3 -0
  104. data/rbi/openai/internal/read_io_adapter.rbi +6 -1
  105. data/rbi/openai/internal/transport/base_client.rbi +61 -7
  106. data/rbi/openai/internal/type/base_model.rbi +24 -1
  107. data/rbi/openai/internal/type/base_page.rbi +13 -2
  108. data/rbi/openai/internal/type/base_stream.rbi +15 -4
  109. data/rbi/openai/internal/util.rbi +26 -2
  110. data/rbi/openai/models/admin/organization/audit_log_list_params.rbi +5 -0
  111. data/rbi/openai/models/admin/organization/audit_log_list_response.rbi +5 -0
  112. data/rbi/openai/models/all_models.rbi +15 -0
  113. data/rbi/openai/models/audio/transcription_create_params.rbi +14 -2
  114. data/rbi/openai/models/audio/translation_create_params.rbi +14 -2
  115. data/rbi/openai/models/beta/beta_response.rbi +15 -0
  116. data/rbi/openai/models/beta/beta_response_stream_event.rbi +1 -1
  117. data/rbi/openai/models/beta/beta_responses_client_event.rbi +37 -1
  118. data/rbi/openai/models/beta/beta_responses_server_event.rbi +3791 -54
  119. data/rbi/openai/models/beta/response_compact_params.rbi +15 -0
  120. data/rbi/openai/models/beta/response_create_params.rbi +15 -0
  121. data/rbi/openai/models/containers/file_create_params.rbi +8 -0
  122. data/rbi/openai/models/content_provenance_check_create_params.rbi +8 -0
  123. data/rbi/openai/models/file_create_params.rbi +8 -0
  124. data/rbi/openai/models/image_create_variation_params.rbi +8 -0
  125. data/rbi/openai/models/image_edit_params.rbi +16 -0
  126. data/rbi/openai/models/responses/response_compact_params.rbi +15 -0
  127. data/rbi/openai/models/responses/response_stream_event.rbi +1 -1
  128. data/rbi/openai/models/responses/responses_client_event.rbi +19 -0
  129. data/rbi/openai/models/responses/responses_server_event.rbi +3282 -53
  130. data/rbi/openai/models/responses_model.rbi +15 -0
  131. data/rbi/openai/models/skill_create_params.rbi +8 -0
  132. data/rbi/openai/models/skills/version_create_params.rbi +8 -0
  133. data/rbi/openai/models/uploads/part_create_params.rbi +8 -0
  134. data/rbi/openai/models/video_create_character_params.rbi +8 -0
  135. data/rbi/openai/models/video_create_params.rbi +8 -0
  136. data/rbi/openai/models/video_edit_params.rbi +8 -0
  137. data/rbi/openai/models/video_extend_params.rbi +8 -0
  138. data/rbi/openai/net_http_client.rbi +2 -2
  139. data/rbi/openai/providers.rbi +9 -0
  140. data/rbi/openai/resources/audio/transcriptions.rbi +14 -2
  141. data/rbi/openai/resources/audio/translations.rbi +7 -1
  142. data/rbi/openai/resources/containers/files.rbi +4 -0
  143. data/rbi/openai/resources/content_provenance_checks.rbi +4 -0
  144. data/rbi/openai/resources/files.rbi +4 -0
  145. data/rbi/openai/resources/images.rbi +20 -0
  146. data/rbi/openai/resources/skills/versions.rbi +4 -0
  147. data/rbi/openai/resources/skills.rbi +4 -0
  148. data/rbi/openai/resources/uploads/parts.rbi +4 -0
  149. data/rbi/openai/resources/videos.rbi +16 -0
  150. data/sig/openai/client.rbs +6 -2
  151. data/sig/openai/errors.rbs +5 -0
  152. data/sig/openai/http_client.rbs +37 -2
  153. data/sig/openai/internal/logging.rbs +88 -0
  154. data/sig/openai/internal/provider.rbs +3 -1
  155. data/sig/openai/internal/read_io_adapter.rbs +3 -1
  156. data/sig/openai/internal/transport/base_client.rbs +33 -5
  157. data/sig/openai/internal/type/base_model.rbs +13 -0
  158. data/sig/openai/internal/type/base_page.rbs +8 -1
  159. data/sig/openai/internal/type/base_stream.rbs +8 -2
  160. data/sig/openai/internal/util.rbs +12 -0
  161. data/sig/openai/models/admin/organization/audit_log_list_params.rbs +2 -0
  162. data/sig/openai/models/admin/organization/audit_log_list_response.rbs +2 -0
  163. data/sig/openai/models/all_models.rbs +6 -0
  164. data/sig/openai/models/beta/beta_response.rbs +6 -0
  165. data/sig/openai/models/beta/beta_responses_client_event.rbs +13 -0
  166. data/sig/openai/models/beta/beta_responses_server_event.rbs +1946 -54
  167. data/sig/openai/models/beta/response_compact_params.rbs +6 -0
  168. data/sig/openai/models/beta/response_create_params.rbs +6 -0
  169. data/sig/openai/models/responses/response_compact_params.rbs +6 -0
  170. data/sig/openai/models/responses/responses_client_event.rbs +7 -0
  171. data/sig/openai/models/responses/responses_server_event.rbs +1786 -53
  172. data/sig/openai/models/responses_model.rbs +6 -0
  173. data/sig/openai/net_http_client.rbs +2 -2
  174. data/sig/openai/providers.rbs +6 -0
  175. metadata +24 -1
@@ -9,6 +9,10 @@ module OpenAI
9
9
  #
10
10
  # Creates a variation of a given image. This endpoint only supports `dall-e-2`.
11
11
  #
12
+ # `String`, `StringIO`, and pathless `IO` inputs are sent with generic upload
13
+ # metadata. Use `OpenAI::FilePart` when you need to override the filename or
14
+ # content type.
15
+ #
12
16
  # @overload create_variation(image:, model: nil, n: nil, response_format: nil, size: nil, user: nil, request_options: {})
13
17
  #
14
18
  # @param image [Pathname, StringIO, IO, String, OpenAI::FilePart] The image to use as the basis for the variation(s). Must be a valid PNG file, le
@@ -50,6 +54,10 @@ module OpenAI
50
54
  # prompt. This endpoint supports GPT Image models (`gpt-image-1.5`, `gpt-image-1`,
51
55
  # `gpt-image-1-mini`, and `chatgpt-image-latest`) and `dall-e-2`.
52
56
  #
57
+ # `String`, `StringIO`, and pathless `IO` inputs are sent with generic upload
58
+ # metadata. Use `OpenAI::FilePart` when you need to override the filename or
59
+ # content type.
60
+ #
53
61
  # @overload edit(image:, prompt:, background: nil, input_fidelity: nil, mask: nil, model: nil, n: nil, output_compression: nil, output_format: nil, partial_images: nil, quality: nil, response_format: nil, size: nil, user: nil, request_options: {})
54
62
  #
55
63
  # @param image [Pathname, StringIO, IO, String, OpenAI::FilePart, Array<Pathname, StringIO, IO, String, OpenAI::FilePart>] The image(s) to edit. Must be a supported image file or an array of images.
@@ -111,6 +119,10 @@ module OpenAI
111
119
  # prompt. This endpoint supports GPT Image models (`gpt-image-1.5`, `gpt-image-1`,
112
120
  # `gpt-image-1-mini`, and `chatgpt-image-latest`) and `dall-e-2`.
113
121
  #
122
+ # `String`, `StringIO`, and pathless `IO` inputs are sent with generic upload
123
+ # metadata. Use `OpenAI::FilePart` when you need to override the filename or
124
+ # content type.
125
+ #
114
126
  # @overload edit_stream_raw(image:, prompt:, background: nil, input_fidelity: nil, mask: nil, model: nil, n: nil, output_compression: nil, output_format: nil, partial_images: nil, quality: nil, response_format: nil, size: nil, user: nil, request_options: {})
115
127
  #
116
128
  # @param image [Pathname, StringIO, IO, String, OpenAI::FilePart, Array<Pathname, StringIO, IO, String, OpenAI::FilePart>] The image(s) to edit. Must be a supported image file or an array of images.
@@ -407,12 +407,12 @@ module OpenAI
407
407
  # @see OpenAI::Models::Responses::ResponseRetrieveParams
408
408
  def retrieve_streaming(response_id, params = {})
409
409
  parsed, options = OpenAI::Responses::ResponseRetrieveParams.dump_request(params)
410
- query = OpenAI::Internal::Util.encode_query_params(parsed)
411
410
  unless parsed.fetch(:stream, true)
412
411
  message = "Please use `#retrieve` for the non-streaming use case."
413
412
  raise ArgumentError.new(message)
414
413
  end
415
414
  parsed.store(:stream, true)
415
+ query = OpenAI::Internal::Util.encode_query_params(parsed)
416
416
  @client.request(
417
417
  method: :get,
418
418
  path: ["responses/%1$s", response_id],
@@ -662,7 +662,8 @@ module OpenAI
662
662
  tool_models.store(name, params)
663
663
  tool[:function][:parameters] = params.to_json_schema
664
664
  tool
665
- in {type: _, function: Hash => func} if func[:parameters].is_a?(Class) && func[:parameters] < OpenAI::Internal::Type::BaseModel
665
+ in {type: _, function: Hash => func} if func[:parameters].is_a?(Class) &&
666
+ func[:parameters] < OpenAI::Internal::Type::BaseModel
666
667
  params = func[:parameters]
667
668
  name = func[:name] || params.name.split("::").last
668
669
  tool_models.store(name, params)
@@ -9,6 +9,10 @@ module OpenAI
9
9
 
10
10
  # Create a new immutable skill version.
11
11
  #
12
+ # `String`, `StringIO`, and pathless `IO` inputs are sent with generic upload
13
+ # metadata. Use `OpenAI::FilePart` when you need to override the filename or
14
+ # content type.
15
+ #
12
16
  # @overload create(skill_id, default: nil, files: nil, request_options: {})
13
17
  #
14
18
  # @param skill_id [String] The identifier of the skill to version.
@@ -11,6 +11,10 @@ module OpenAI
11
11
 
12
12
  # Create a new skill.
13
13
  #
14
+ # `String`, `StringIO`, and pathless `IO` inputs are sent with generic upload
15
+ # metadata. Use `OpenAI::FilePart` when you need to override the filename or
16
+ # content type.
17
+ #
14
18
  # @overload create(files: nil, request_options: {})
15
19
  #
16
20
  # @param files [Array<Pathname, StringIO, IO, String, OpenAI::FilePart>, Pathname, StringIO, IO, String, OpenAI::FilePart] Skill files to upload (directory upload) or a single zip file.
@@ -20,6 +20,10 @@ module OpenAI
20
20
  # order of the Parts when you
21
21
  # [complete the Upload](https://platform.openai.com/docs/api-reference/uploads/complete).
22
22
  #
23
+ # `String`, `StringIO`, and pathless `IO` inputs are sent with generic upload
24
+ # metadata. Use `OpenAI::FilePart` when you need to override the filename or
25
+ # content type.
26
+ #
23
27
  # @overload create(upload_id, data:, request_options: {})
24
28
  #
25
29
  # @param upload_id [String] The ID of the Upload.
@@ -123,11 +123,11 @@ module OpenAI
123
123
  # @see OpenAI::Models::VectorStores::FileBatchListFilesParams
124
124
  def list_files(batch_id, params)
125
125
  parsed, options = OpenAI::VectorStores::FileBatchListFilesParams.dump_request(params)
126
- query = OpenAI::Internal::Util.encode_query_params(parsed)
127
126
  vector_store_id =
128
127
  parsed.delete(:vector_store_id) do
129
128
  raise ArgumentError.new("missing required path argument #{_1}")
130
129
  end
130
+ query = OpenAI::Internal::Util.encode_query_params(parsed)
131
131
  @client.request(
132
132
  method: :get,
133
133
  path: ["vector_stores/%1$s/file_batches/%2$s/files", vector_store_id, batch_id],
@@ -2,12 +2,20 @@
2
2
 
3
3
  module OpenAI
4
4
  module Resources
5
+ # @deprecated The Sora API is scheduled to permanently shut down on September
6
+ # 24, 2026.
5
7
  class Videos
8
+ # @deprecated The Sora API is scheduled to permanently shut down on September 24, 2026.
9
+ #
6
10
  # Some parameter documentations has been truncated, see
7
11
  # {OpenAI::Models::VideoCreateParams} for more details.
8
12
  #
9
13
  # Create a new video generation job from a prompt and optional reference assets.
10
14
  #
15
+ # `String`, `StringIO`, and pathless `IO` inputs are sent with generic upload
16
+ # metadata. Use `OpenAI::FilePart` when you need to override the filename or
17
+ # content type.
18
+ #
11
19
  # @overload create(prompt:, input_reference: nil, model: nil, seconds: nil, size: nil, request_options: {})
12
20
  #
13
21
  # @param prompt [String] Text prompt that describes the video to generate.
@@ -38,6 +46,8 @@ module OpenAI
38
46
  )
39
47
  end
40
48
 
49
+ # @deprecated The Sora API is scheduled to permanently shut down on September 24, 2026.
50
+ #
41
51
  # Fetch the latest metadata for a generated video.
42
52
  #
43
53
  # @overload retrieve(video_id, request_options: {})
@@ -59,6 +69,8 @@ module OpenAI
59
69
  )
60
70
  end
61
71
 
72
+ # @deprecated The Sora API is scheduled to permanently shut down on September 24, 2026.
73
+ #
62
74
  # Some parameter documentations has been truncated, see
63
75
  # {OpenAI::Models::VideoListParams} for more details.
64
76
  #
@@ -91,6 +103,8 @@ module OpenAI
91
103
  )
92
104
  end
93
105
 
106
+ # @deprecated The Sora API is scheduled to permanently shut down on September 24, 2026.
107
+ #
94
108
  # Permanently delete a completed or failed video and its stored assets.
95
109
  #
96
110
  # @overload delete(video_id, request_options: {})
@@ -112,8 +126,14 @@ module OpenAI
112
126
  )
113
127
  end
114
128
 
129
+ # @deprecated The Sora API is scheduled to permanently shut down on September 24, 2026.
130
+ #
115
131
  # Create a character from an uploaded video.
116
132
  #
133
+ # `String`, `StringIO`, and pathless `IO` inputs are sent with generic upload
134
+ # metadata. Use `OpenAI::FilePart` when you need to override the filename or
135
+ # content type.
136
+ #
117
137
  # @overload create_character(name:, video:, request_options: {})
118
138
  #
119
139
  # @param name [String] Display name for this API character.
@@ -138,6 +158,8 @@ module OpenAI
138
158
  )
139
159
  end
140
160
 
161
+ # @deprecated The Sora API is scheduled to permanently shut down on September 24, 2026.
162
+ #
141
163
  # Download the generated video bytes or a derived preview asset.
142
164
  #
143
165
  # Streams the rendered video content for the specified video job.
@@ -167,9 +189,15 @@ module OpenAI
167
189
  )
168
190
  end
169
191
 
192
+ # @deprecated The Sora API is scheduled to permanently shut down on September 24, 2026.
193
+ #
170
194
  # Create a new video generation job by editing a source video or existing
171
195
  # generated video.
172
196
  #
197
+ # `String`, `StringIO`, and pathless `IO` inputs are sent with generic upload
198
+ # metadata. Use `OpenAI::FilePart` when you need to override the filename or
199
+ # content type.
200
+ #
173
201
  # @overload edit(prompt:, video:, request_options: {})
174
202
  #
175
203
  # @param prompt [String] Text prompt that describes how to edit the source video.
@@ -194,11 +222,17 @@ module OpenAI
194
222
  )
195
223
  end
196
224
 
225
+ # @deprecated The Sora API is scheduled to permanently shut down on September 24, 2026.
226
+ #
197
227
  # Some parameter documentations has been truncated, see
198
228
  # {OpenAI::Models::VideoExtendParams} for more details.
199
229
  #
200
230
  # Create an extension of a completed video.
201
231
  #
232
+ # `String`, `StringIO`, and pathless `IO` inputs are sent with generic upload
233
+ # metadata. Use `OpenAI::FilePart` when you need to override the filename or
234
+ # content type.
235
+ #
202
236
  # @overload extend_(prompt:, seconds:, video:, request_options: {})
203
237
  #
204
238
  # @param prompt [String] Updated text prompt that directs the extension generation.
@@ -225,6 +259,8 @@ module OpenAI
225
259
  )
226
260
  end
227
261
 
262
+ # @deprecated The Sora API is scheduled to permanently shut down on September 24, 2026.
263
+ #
228
264
  # Fetch a character.
229
265
  #
230
266
  # @overload get_character(character_id, request_options: {})
@@ -246,6 +282,8 @@ module OpenAI
246
282
  )
247
283
  end
248
284
 
285
+ # @deprecated The Sora API is scheduled to permanently shut down on September 24, 2026.
286
+ #
249
287
  # Create a remix of a completed video using a refreshed prompt.
250
288
  #
251
289
  # @overload remix(video_id, prompt:, request_options: {})
@@ -37,16 +37,26 @@ module OpenAI
37
37
  webhook_secret = @client.webhook_secret || ENV["OPENAI_WEBHOOK_SECRET"],
38
38
  tolerance = 300
39
39
  )
40
- if webhook_secret.nil?
40
+ if webhook_secret.nil? || webhook_secret.strip.empty?
41
41
  raise ArgumentError,
42
42
  "The webhook secret must either be set using the env var, OPENAI_WEBHOOK_SECRET, " \
43
43
  "or passed to this function"
44
44
  end
45
45
 
46
- # Extract required headers
47
- signature_header = headers["webhook-signature"] || headers[:webhook_signature]
48
- timestamp_header = headers["webhook-timestamp"] || headers[:webhook_timestamp]
49
- webhook_id = headers["webhook-id"] || headers[:webhook_id]
46
+ header_names = %w[webhook-signature webhook-timestamp webhook-id]
47
+ normalized_headers = {}
48
+ headers.each do |name, value|
49
+ name = name.to_s.downcase.delete_prefix("http_").tr("_", "-")
50
+ next unless header_names.include?(name)
51
+
52
+ if normalized_headers.key?(name) && normalized_headers[name] != value
53
+ raise ArgumentError, "Conflicting values for #{name} header"
54
+ end
55
+
56
+ normalized_headers[name] = value
57
+ end
58
+
59
+ signature_header, timestamp_header, webhook_id = normalized_headers.values_at(*header_names)
50
60
 
51
61
  if signature_header.nil?
52
62
  raise ArgumentError, "Missing required webhook-signature header"
@@ -90,11 +100,13 @@ module OpenAI
90
100
 
91
101
  # Decode the secret if it starts with whsec_
92
102
  decoded_secret = if webhook_secret.start_with?("whsec_")
93
- Base64.decode64(webhook_secret[6..])
103
+ Base64.strict_decode64(webhook_secret[6..])
94
104
  else
95
105
  webhook_secret
96
106
  end
97
107
 
108
+ raise ArgumentError, "The webhook secret must not be empty" if decoded_secret.empty?
109
+
98
110
  # Create the signed payload: {webhook_id}.{timestamp}.{payload}
99
111
  signed_payload = "#{webhook_id}.#{timestamp_header}.#{payload}"
100
112
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenAI
4
- VERSION = "0.78.0"
4
+ VERSION = "0.79.0"
5
5
  end