openai 0.99.0 → 0.100.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 (157) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/README.md +1 -1
  4. data/bedrock.md +17 -1
  5. data/lib/openai/client.rb +9 -0
  6. data/lib/openai/helpers/agents/session_stream.rb +11 -36
  7. data/lib/openai/helpers/agents/tools.rb +34 -4
  8. data/lib/openai/helpers/beta/agents/creation_stream.rb +26 -0
  9. data/lib/openai/helpers/beta/agents/function_tool.rb +11 -4
  10. data/lib/openai/helpers/beta/agents/tool_dispatcher.rb +74 -0
  11. data/lib/openai/helpers/beta/agents/tool_error.rb +28 -0
  12. data/lib/openai/models/admin/organization/usage_audio_speeches_response.rb +125 -3
  13. data/lib/openai/models/admin/organization/usage_audio_transcriptions_response.rb +125 -3
  14. data/lib/openai/models/admin/organization/usage_code_interpreter_sessions_response.rb +125 -3
  15. data/lib/openai/models/admin/organization/usage_completions_params.rb +9 -4
  16. data/lib/openai/models/admin/organization/usage_completions_response.rb +125 -3
  17. data/lib/openai/models/admin/organization/usage_costs_params.rb +16 -4
  18. data/lib/openai/models/admin/organization/usage_costs_response.rb +125 -3
  19. data/lib/openai/models/admin/organization/usage_embeddings_response.rb +125 -3
  20. data/lib/openai/models/admin/organization/usage_file_search_calls_response.rb +125 -3
  21. data/lib/openai/models/admin/organization/usage_images_response.rb +125 -3
  22. data/lib/openai/models/admin/organization/usage_moderations_response.rb +125 -3
  23. data/lib/openai/models/admin/organization/usage_vector_stores_response.rb +125 -3
  24. data/lib/openai/models/admin/organization/usage_web_search_calls_params.rb +9 -4
  25. data/lib/openai/models/admin/organization/usage_web_search_calls_response.rb +125 -3
  26. data/lib/openai/models/beta/agents/environments/template_create_params.rb +6 -6
  27. data/lib/openai/models/beta/agents/environments/template_update_params.rb +3 -6
  28. data/lib/openai/models/beta/agents/sessions/turns/item_list_params.rb +79 -0
  29. data/lib/openai/models/beta/beta_response_computer_tool_call_output_item.rb +2 -2
  30. data/lib/openai/models/beta/beta_response_error.rb +12 -1
  31. data/lib/openai/models/beta/beta_responses_server_event.rb +12 -1
  32. data/lib/openai/models/beta/beta_tool.rb +12 -9
  33. data/lib/openai/models/beta/environment_param.rb +6 -6
  34. data/lib/openai/models/beta/session_turn_error.rb +9 -0
  35. data/lib/openai/models/chat/chat_completion_chunk.rb +85 -18
  36. data/lib/openai/models/error_object.rb +12 -1
  37. data/lib/openai/models/eval_create_params.rb +1 -1
  38. data/lib/openai/models/eval_create_response.rb +1 -1
  39. data/lib/openai/models/eval_list_response.rb +1 -1
  40. data/lib/openai/models/eval_retrieve_response.rb +1 -1
  41. data/lib/openai/models/eval_update_response.rb +1 -1
  42. data/lib/openai/models/fine_tuning/job_list_params.rb +14 -4
  43. data/lib/openai/models/graders/score_model_grader.rb +2 -2
  44. data/lib/openai/models/image_edit_params.rb +12 -9
  45. data/lib/openai/models/realtime/realtime_client_event.rb +9 -0
  46. data/lib/openai/models/realtime/session_update_event.rb +16 -0
  47. data/lib/openai/models/responses/response_computer_tool_call_output_item.rb +2 -2
  48. data/lib/openai/models/responses/response_error.rb +12 -1
  49. data/lib/openai/models/responses/responses_server_event.rb +12 -1
  50. data/lib/openai/models/responses/tool.rb +12 -9
  51. data/lib/openai/models/video_create_error.rb +12 -1
  52. data/lib/openai/providers/bedrock.rb +20 -1
  53. data/lib/openai/resources/admin/organization/usage.rb +15 -6
  54. data/lib/openai/resources/beta/agents/environments/templates.rb +3 -4
  55. data/lib/openai/resources/beta/agents/sessions/turns/items.rb +68 -0
  56. data/lib/openai/resources/beta/agents/sessions/turns.rb +4 -0
  57. data/lib/openai/resources/beta/agents/sessions.rb +22 -2
  58. data/lib/openai/resources/fine_tuning/jobs.rb +7 -2
  59. data/lib/openai/resources/images.rb +8 -6
  60. data/lib/openai/version.rb +1 -1
  61. data/lib/openai.rb +2 -0
  62. data/rbi/openai/helpers/agents.rbi +2 -0
  63. data/rbi/openai/helpers/beta/agents/function_tool.rbi +8 -3
  64. data/rbi/openai/helpers/beta/agents/tool_error.rbi +19 -0
  65. data/rbi/openai/helpers/beta/agents.rbi +12 -0
  66. data/rbi/openai/models/admin/organization/usage_audio_speeches_response.rbi +194 -1
  67. data/rbi/openai/models/admin/organization/usage_audio_transcriptions_response.rbi +194 -1
  68. data/rbi/openai/models/admin/organization/usage_code_interpreter_sessions_response.rbi +194 -1
  69. data/rbi/openai/models/admin/organization/usage_completions_params.rbi +9 -4
  70. data/rbi/openai/models/admin/organization/usage_completions_response.rbi +194 -1
  71. data/rbi/openai/models/admin/organization/usage_costs_params.rbi +16 -4
  72. data/rbi/openai/models/admin/organization/usage_costs_response.rbi +194 -1
  73. data/rbi/openai/models/admin/organization/usage_embeddings_response.rbi +194 -1
  74. data/rbi/openai/models/admin/organization/usage_file_search_calls_response.rbi +194 -1
  75. data/rbi/openai/models/admin/organization/usage_images_response.rbi +194 -1
  76. data/rbi/openai/models/admin/organization/usage_moderations_response.rbi +194 -1
  77. data/rbi/openai/models/admin/organization/usage_vector_stores_response.rbi +194 -1
  78. data/rbi/openai/models/admin/organization/usage_web_search_calls_params.rbi +12 -4
  79. data/rbi/openai/models/admin/organization/usage_web_search_calls_response.rbi +194 -1
  80. data/rbi/openai/models/beta/agents/environments/template_create_params.rbi +6 -6
  81. data/rbi/openai/models/beta/agents/environments/template_update_params.rbi +3 -6
  82. data/rbi/openai/models/beta/agents/sessions/turns/item_list_params.rbi +139 -0
  83. data/rbi/openai/models/beta/beta_response_computer_tool_call_output_item.rbi +2 -2
  84. data/rbi/openai/models/beta/beta_response_error.rbi +12 -0
  85. data/rbi/openai/models/beta/beta_responses_server_event.rbi +12 -0
  86. data/rbi/openai/models/beta/beta_tool.rbi +12 -9
  87. data/rbi/openai/models/beta/environment_param.rbi +6 -6
  88. data/rbi/openai/models/beta/session_turn_error.rbi +18 -0
  89. data/rbi/openai/models/chat/chat_completion_chunk.rbi +123 -17
  90. data/rbi/openai/models/error_object.rbi +12 -0
  91. data/rbi/openai/models/eval_create_params.rbi +1 -1
  92. data/rbi/openai/models/eval_create_response.rbi +1 -1
  93. data/rbi/openai/models/eval_list_response.rbi +1 -1
  94. data/rbi/openai/models/eval_retrieve_response.rbi +1 -1
  95. data/rbi/openai/models/eval_update_response.rbi +1 -1
  96. data/rbi/openai/models/fine_tuning/job_list_params.rbi +14 -4
  97. data/rbi/openai/models/graders/score_model_grader.rbi +2 -2
  98. data/rbi/openai/models/image_edit_params.rbi +12 -9
  99. data/rbi/openai/models/realtime/session_update_event.rbi +16 -0
  100. data/rbi/openai/models/responses/response_computer_tool_call_output_item.rbi +2 -2
  101. data/rbi/openai/models/responses/response_error.rbi +12 -0
  102. data/rbi/openai/models/responses/responses_server_event.rbi +12 -0
  103. data/rbi/openai/models/responses/tool.rbi +12 -9
  104. data/rbi/openai/models/video_create_error.rbi +12 -0
  105. data/rbi/openai/resources/admin/organization/usage.rbi +15 -6
  106. data/rbi/openai/resources/beta/agents/environments/templates.rbi +3 -4
  107. data/rbi/openai/resources/beta/agents/sessions/turns/items.rbi +62 -0
  108. data/rbi/openai/resources/beta/agents/sessions/turns.rbi +3 -0
  109. data/rbi/openai/resources/beta/agents/sessions.rbi +2 -0
  110. data/rbi/openai/resources/fine_tuning/jobs.rbi +7 -2
  111. data/rbi/openai/resources/images.rbi +8 -6
  112. data/sig/openai/helpers/agents.rbs +3 -0
  113. data/sig/openai/helpers/beta/agents/function_tool.rbs +5 -2
  114. data/sig/openai/helpers/beta/agents/tool_error.rbs +17 -0
  115. data/sig/openai/helpers/beta/agents.rbs +5 -0
  116. data/sig/openai/models/admin/organization/usage_audio_speeches_response.rbs +87 -2
  117. data/sig/openai/models/admin/organization/usage_audio_transcriptions_response.rbs +87 -2
  118. data/sig/openai/models/admin/organization/usage_code_interpreter_sessions_response.rbs +87 -2
  119. data/sig/openai/models/admin/organization/usage_completions_params.rbs +14 -6
  120. data/sig/openai/models/admin/organization/usage_completions_response.rbs +87 -2
  121. data/sig/openai/models/admin/organization/usage_costs_params.rbs +25 -7
  122. data/sig/openai/models/admin/organization/usage_costs_response.rbs +87 -2
  123. data/sig/openai/models/admin/organization/usage_embeddings_response.rbs +87 -2
  124. data/sig/openai/models/admin/organization/usage_file_search_calls_response.rbs +87 -2
  125. data/sig/openai/models/admin/organization/usage_images_response.rbs +87 -2
  126. data/sig/openai/models/admin/organization/usage_moderations_response.rbs +87 -2
  127. data/sig/openai/models/admin/organization/usage_vector_stores_response.rbs +87 -2
  128. data/sig/openai/models/admin/organization/usage_web_search_calls_params.rbs +19 -7
  129. data/sig/openai/models/admin/organization/usage_web_search_calls_response.rbs +87 -2
  130. data/sig/openai/models/beta/agents/environments/template_create_params.rbs +4 -4
  131. data/sig/openai/models/beta/agents/environments/template_update_params.rbs +2 -4
  132. data/sig/openai/models/beta/agents/sessions/turns/item_list_params.rbs +91 -0
  133. data/sig/openai/models/beta/beta_response_computer_tool_call_output_item.rbs +2 -2
  134. data/sig/openai/models/beta/beta_response_error.rbs +10 -0
  135. data/sig/openai/models/beta/beta_responses_server_event.rbs +10 -0
  136. data/sig/openai/models/beta/beta_tool.rbs +8 -6
  137. data/sig/openai/models/beta/environment_param.rbs +4 -4
  138. data/sig/openai/models/beta/session_turn_error.rbs +12 -0
  139. data/sig/openai/models/chat/chat_completion_chunk.rbs +96 -12
  140. data/sig/openai/models/error_object.rbs +10 -0
  141. data/sig/openai/models/eval_create_params.rbs +1 -1
  142. data/sig/openai/models/eval_create_response.rbs +1 -1
  143. data/sig/openai/models/eval_list_response.rbs +1 -1
  144. data/sig/openai/models/eval_retrieve_response.rbs +1 -1
  145. data/sig/openai/models/eval_update_response.rbs +1 -1
  146. data/sig/openai/models/fine_tuning/job_list_params.rbs +14 -4
  147. data/sig/openai/models/graders/score_model_grader.rbs +3 -3
  148. data/sig/openai/models/image_edit_params.rbs +8 -6
  149. data/sig/openai/models/responses/response_computer_tool_call_output_item.rbs +2 -2
  150. data/sig/openai/models/responses/response_error.rbs +10 -0
  151. data/sig/openai/models/responses/responses_server_event.rbs +10 -0
  152. data/sig/openai/models/responses/tool.rbs +8 -6
  153. data/sig/openai/models/video_create_error.rbs +10 -0
  154. data/sig/openai/resources/beta/agents/sessions/turns/items.rbs +24 -0
  155. data/sig/openai/resources/beta/agents/sessions/turns.rbs +2 -0
  156. data/sig/openai/resources/beta/agents/sessions.rbs +1 -0
  157. metadata +11 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7888217212b961954d41ed4bb25d807a08d7155598eac1c384819b35723ab529
4
- data.tar.gz: ff4e8f985ffcd57f1aa276cead8dba172a1c686228afd15dd90f0dfa3f440078
3
+ metadata.gz: f48afd92b1ad4726998649830f3158bae15ba7b60dfdb6388453493ffffcec91
4
+ data.tar.gz: ad88a01ba827d00dad0f8f05e7d53165a70eae0bf8abdfdf043f0bbcf2ee45b5
5
5
  SHA512:
6
- metadata.gz: 4d54994a5e2f72c57ffc898b6bf8ed1ba30745296900d4d6c0c41cde5d88c182f55d8295619140eed845e1741283b0d68704bf92fd4684c2b7a070b91d2c3ae8
7
- data.tar.gz: c277185aabed1723fa217caa826ba9df358d40a3ab5018a8ed7a70ff7f13b9c1855543144a9ba8404c3ed95bbb4bdea095802a898878b8bfab6e49dd682939d7
6
+ metadata.gz: 7167c3f0f5586c6da077aad8f7203eb4d5b38b70674927750f61836f856d21553a999791bc0ceae1ea052228c7ce1e9fc49d495da3ee1f151fbb951bcd84465c
7
+ data.tar.gz: e141202139c857c8e071dfe9c0bb1cb7031fea50a58fda0a601c9b5980b8034babbc73382a573dce97c4ec71cd68cae9d867914d0b7edd97a184b27106d6d54f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.100.0](https://github.com/openai/openai-ruby/compare/v0.99.0...v0.100.0) (2026-10-06)
4
+
5
+
6
+ ### Features
7
+
8
+ * **agents:** handle local tools during streaming creation ([#805](https://github.com/openai/openai-ruby/issues/805)) ([a961bd6](https://github.com/openai/openai-ruby/commit/a961bd6aea012b8c1d47c84ac6b950142fd45470))
9
+ * **agents:** support tool search (defer_loading=true) in typed function tools ([#809](https://github.com/openai/openai-ruby/issues/809)) ([389fd0b](https://github.com/openai/openai-ruby/commit/389fd0b67186c625fd19a324412ae3166db5a1c6))
10
+ * **api:** add agent turn items and usage source grouping ([#810](https://github.com/openai/openai-ruby/issues/810)) ([606902b](https://github.com/openai/openai-ruby/commit/606902b255cd095fe3530fbe620edfe4b7210ab3))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * select the Mantle inference path for GPT-6.1 Sol ([#808](https://github.com/openai/openai-ruby/issues/808)) ([133bea0](https://github.com/openai/openai-ruby/commit/133bea01973fd3e6d195392891e732b2c07cdfd3))
16
+
17
+
18
+ ### Chores
19
+
20
+ * **deps-dev:** bump sorbet-runtime from 0.6.13506 to 0.6.13508 in the development-dependencies group ([#804](https://github.com/openai/openai-ruby/issues/804)) ([5bd0b99](https://github.com/openai/openai-ruby/commit/5bd0b99725e29d2646dcfdb5d41b9efba0955f99))
21
+
3
22
  ## [0.99.0](https://github.com/openai/openai-ruby/compare/v0.98.0...v0.99.0) (2026-10-02)
4
23
 
5
24
 
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.99.0"
18
+ gem "openai", "~> 0.100.0"
19
19
  ```
20
20
 
21
21
  <!-- x-release-please-end -->
data/bedrock.md CHANGED
@@ -17,7 +17,23 @@ response = client.responses.create(
17
17
  puts(response.output_text)
18
18
  ```
19
19
 
20
- The provider uses `https://bedrock-mantle.<region>.api.aws/v1` by default and exposes the normal SDK resources. AWS controls which endpoints and features are supported; unsupported calls surface as normal API errors.
20
+ The provider uses `https://bedrock-mantle.<region>.api.aws/v1` by default. AWS documents this route for [GPT OSS](https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-openai-gpt-oss-120b.html), including Responses and Chat Completions, and for model discovery.
21
+
22
+ When no base URL override is configured, Responses and Chat Completions creation requests whose final structured request body specifies exactly `model: "openai.gpt-6.1-sol"` use `/openai/v1/responses` and `/openai/v1/chat/completions`, respectively, as required by the [GPT-6.1 Sol model card](https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-openai-gpt-6-1-sol.html). **Sol on Mantle requires `region: "us-east-1"`; it is not available in the `us-west-2` region used in the general example above.** String and Ruby symbol model IDs are supported. This selection happens before bearer authentication or SigV4 signing. It does not change the client's base URL or infer routes for other model names.
23
+
24
+ Calls without a structured model field, including pre-encoded/streaming request bodies and retrieving or deleting a response, use the configured base URL. For Sol response follow-ups, use a client explicitly configured with the Sol API root:
25
+
26
+ ```ruby
27
+ sol_client = OpenAI::Client.new(
28
+ provider: OpenAI::Providers.bedrock(
29
+ region: "us-east-1",
30
+ base_url: "https://bedrock-mantle.us-east-1.api.aws/openai/v1"
31
+ )
32
+ )
33
+ response = sol_client.responses.retrieve("resp_example")
34
+ ```
35
+
36
+ Explicit `base_url` and `AWS_BEDROCK_BASE_URL` overrides are used as configured for every model. AWS controls which endpoints and features are supported; unsupported calls surface as normal API errors.
21
37
 
22
38
  ## Bedrock Runtime
23
39
 
data/lib/openai/client.rb CHANGED
@@ -195,6 +195,15 @@ module OpenAI
195
195
 
196
196
  # @api private
197
197
  private def build_request(request, options)
198
+ if @provider_runtime
199
+ built = super
200
+ body = built[:body]
201
+ # Capture only the final model; provider hooks run after body encoding.
202
+ model = body.reverse_each.find { |key, _| key == :model || key == "model" }&.last if body.is_a?(Hash)
203
+ model = model.to_s if model.is_a?(Symbol)
204
+ return built.merge(provider_request_model: model)
205
+ end
206
+
198
207
  unless @workload_identity_auth && x509_transport?(@requester)
199
208
  return super
200
209
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "tools"
3
+ require_relative "../beta/agents/tool_dispatcher"
4
4
  require_relative "../beta/agents/result"
5
5
 
6
6
  module OpenAI
@@ -19,6 +19,7 @@ module OpenAI
19
19
  session_id:,
20
20
  input:,
21
21
  tool_handlers: {},
22
+ on_tool_error: nil,
22
23
  idempotency_key: nil,
23
24
  output_type: nil,
24
25
  request_options: {}
@@ -46,7 +47,13 @@ module OpenAI
46
47
 
47
48
  @options[:extra_headers] = headers
48
49
  @recent_events = {}
49
- @handled_calls = {}
50
+ @dispatcher = OpenAI::Helpers::Beta::Agents::ToolDispatcher.new(
51
+ sessions: sessions,
52
+ session_id: session_id,
53
+ tool_handlers: @handlers,
54
+ on_tool_error: on_tool_error,
55
+ request_options: @options
56
+ )
50
57
  @closed = false
51
58
  session = @sessions.retrieve(@session_id, request_options: @options)
52
59
  unless session.status == :idle
@@ -154,12 +161,12 @@ module OpenAI
154
161
 
155
162
  terminal = event.type == :"agent.session.failed" ||
156
163
  (event.type == :"agent.session.idle" && @turn_ended)
157
- invocation = prepare_call(event) unless terminal
164
+ invocation = @dispatcher.prepare_call(event) unless terminal
158
165
  close if terminal
159
166
  yielder << event
160
167
  break if terminal || @closed
161
168
 
162
- dispatch(invocation.call) if invocation
169
+ @dispatcher.dispatch(invocation.call) if invocation
163
170
  end
164
171
 
165
172
  raise RuntimeError, "Session event stream ended before the turn reached idle or failed" unless @closed
@@ -187,38 +194,6 @@ module OpenAI
187
194
  true
188
195
  end
189
196
 
190
- def prepare_call(event)
191
- unless event.type == :"agent.session.turn.item.added" &&
192
- event.item.is_a?(OpenAI::Models::Beta::AgentFunctionCallItem)
193
- return
194
- end
195
-
196
- call = event.item
197
- key = [call.turn_id.dup, call.call_id.dup]
198
- return if @handled_calls.key?(key)
199
-
200
- @handled_calls[key] = true
201
- handler = @handlers[call.name]
202
- return unless handler
203
-
204
- Tools.prepare(call, handler)
205
- end
206
-
207
- def dispatch(result)
208
- idempotency_key = SecureRandom.uuid
209
- delays = [0.1, 0.3, 0.6]
210
- begin
211
- @sessions
212
- .events
213
- .create(@session_id, events: [result], idempotency_key: idempotency_key, request_options: @options)
214
- rescue OpenAI::Errors::BadRequestError => error
215
- delay = delays.shift
216
- raise unless delay && Tools.pending_call_race?(error, result[:call_id])
217
-
218
- sleep(delay)
219
- retry
220
- end
221
- end
222
197
  end
223
198
  end
224
199
  end
@@ -1,25 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../beta/agents/tool_error"
4
+
3
5
  module OpenAI
4
6
  module Helpers
5
7
  module Agents
6
8
  # @api private
7
9
  module Tools
10
+ # @api private
11
+ module TypedHandler
12
+ end
13
+
8
14
  # Capture routing and detached arguments before caller code sees the event,
9
15
  # but defer the application callback until iteration resumes after yielding it.
10
- def self.prepare(call, handler)
16
+ def self.prepare(call, handler, session_id:, on_tool_error: nil)
11
17
  base = {type: :"agent.session.input.tool_result", turn_id: call.turn_id.dup, call_id: call.call_id.dup}
18
+ tool_name = call.name.dup
19
+ arguments_error = nil
12
20
  arguments = begin
13
21
  JSON.parse(call.arguments.is_a?(String) ? call.arguments : JSON.generate(call.arguments))
14
- rescue StandardError
22
+ rescue StandardError => error
23
+ arguments_error = error
15
24
  nil
16
25
  end
17
26
 
18
27
  lambda do
28
+ stage = :arguments
19
29
  begin
30
+ raise arguments_error if arguments_error
20
31
  raise ArgumentError, "Function arguments must be a JSON object" unless arguments.is_a?(Hash)
21
32
 
22
- output = handler.call(arguments)
33
+ stage = :execution
34
+ report_stage = -> (value) { stage = value } if on_tool_error && handler.is_a?(TypedHandler)
35
+ output = handler.call(arguments, &report_stage)
36
+ stage = :output
23
37
  output = JSON.generate(output) if output.is_a?(Hash)
24
38
  unless output.nil? || output.is_a?(String) || output.is_a?(Array)
25
39
  raise ArgumentError, "Tool output must be a string, object, content array, or nil"
@@ -31,7 +45,23 @@ module OpenAI
31
45
  end
32
46
 
33
47
  base.merge(success: true, output: output)
34
- rescue StandardError
48
+ rescue StandardError => error
49
+ if on_tool_error
50
+ failure = OpenAI::Helpers::Beta::Agents::ToolError.new(
51
+ error: error,
52
+ tool_name: tool_name,
53
+ session_id: session_id,
54
+ turn_id: base[:turn_id],
55
+ call_id: base[:call_id],
56
+ stage: stage
57
+ )
58
+ begin
59
+ on_tool_error.call(failure)
60
+ rescue StandardError
61
+ # Diagnostics must not prevent submission of the original failure.
62
+ end
63
+ end
64
+
35
65
  base.merge(success: false, error: "Tool handler failed.")
36
66
  end
37
67
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "result"
4
+ require_relative "tool_dispatcher"
4
5
 
5
6
  module OpenAI
6
7
  module Helpers
@@ -46,6 +47,22 @@ module OpenAI
46
47
  self
47
48
  end
48
49
 
50
+ # @api private
51
+ def configure_tool_handlers(sessions:, tool_handlers:, request_options:)
52
+ @dispatcher = ToolDispatcher.new(
53
+ sessions: sessions,
54
+ tool_handlers: tool_handlers,
55
+ request_options: request_options
56
+ )
57
+ @collector = ResultCollector.new(handler_names: @dispatcher.handler_names)
58
+ self
59
+ end
60
+
61
+ def close
62
+ @closed = true
63
+ super
64
+ end
65
+
49
66
  private
50
67
 
51
68
  def iterator
@@ -53,8 +70,17 @@ module OpenAI
53
70
  source = super
54
71
  @iterator = OpenAI::Internal::Util.chain_fused(source) do |yielder|
55
72
  source.each do |event|
73
+ invocation = nil
74
+ if @dispatcher && event.is_a?(OpenAI::Internal::Type::BaseModel)
75
+ @dispatcher.observe(event)
76
+ invocation = @dispatcher.prepare_call(event)
77
+ end
78
+
56
79
  @collector.observe(event)
57
80
  yielder << event
81
+ break if @closed
82
+
83
+ @dispatcher.dispatch(invocation.call) if invocation
58
84
  end
59
85
 
60
86
  rescue StandardError => error
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "model_adapter"
4
+ require_relative "../../agents/tools"
4
5
 
5
6
  module OpenAI
6
7
  module Helpers
@@ -14,8 +15,9 @@ module OpenAI
14
15
  # @param name [String]
15
16
  # @param arguments [Class<OpenAI::BaseModel>]
16
17
  # @param description [String]
18
+ # @param defer_loading [Boolean, nil] Load this tool through hosted tool search; nil leaves the setting omitted.
17
19
  # @yieldparam arguments [OpenAI::BaseModel]
18
- def initialize(name:, arguments:, description: "", &handler)
20
+ def initialize(name:, arguments:, description: "", defer_loading: nil, &handler)
19
21
  ModelAdapter.validate!(arguments, parameter: "arguments")
20
22
 
21
23
  raise ArgumentError, "a callback is required" unless handler
@@ -29,7 +31,8 @@ module OpenAI
29
31
  type: :function,
30
32
  name: @name,
31
33
  parameters: arguments.to_json_schema,
32
- description: description
34
+ description: description,
35
+ **(defer_loading.nil? ? {} : {defer_loading: defer_loading})
33
36
  )
34
37
  .freeze
35
38
  end
@@ -40,7 +43,9 @@ module OpenAI
40
43
 
41
44
  # Pass these local bindings to sessions.stream(tool_handlers: ...).
42
45
  # @return [Hash{String=>Proc}]
43
- def handlers = {name => method(:call).to_proc}
46
+ def handlers
47
+ {name => method(:call).to_proc.extend(OpenAI::Helpers::Agents::Tools::TypedHandler)}
48
+ end
44
49
 
45
50
  # Parse using the SDK's BaseModel conventions, then invoke the callback.
46
51
  # This is not JSON Schema validation; validate application constraints
@@ -48,6 +53,8 @@ module OpenAI
48
53
  # @param arguments [Hash{String=>Object}, String]
49
54
  # @return [Object]
50
55
  def call(arguments)
56
+ # The dispatcher supplies this internal stage reporter only when observing errors.
57
+ yield :arguments if block_given?
51
58
  values = JSON.parse(arguments.is_a?(String) ? arguments : JSON.generate(arguments), symbolize_names: true)
52
59
  raise ArgumentError, "Tool arguments must be a JSON object" unless values.is_a?(Hash)
53
60
  parsed = ModelAdapter.coerce(@arguments, values)
@@ -55,9 +62,9 @@ module OpenAI
55
62
  raise ArgumentError, "Tool arguments cannot be parsed into the argument model"
56
63
  end
57
64
 
65
+ yield :execution if block_given?
58
66
  @handler.call(parsed)
59
67
  end
60
-
61
68
  end
62
69
  end
63
70
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../agents/tools"
4
+
5
+ module OpenAI
6
+ module Helpers
7
+ module Beta
8
+ module Agents
9
+ # @api private
10
+ # Shared callback preparation, deduplication, and tool-result submission.
11
+ class ToolDispatcher
12
+ def initialize(sessions:, tool_handlers:, request_options:, session_id: nil, on_tool_error: nil)
13
+ @sessions = sessions
14
+ @session_id = session_id
15
+ @handlers = tool_handlers.to_h.dup
16
+ @on_tool_error = on_tool_error
17
+ @handled_calls = {}
18
+ @options = request_options.to_h.dup
19
+ @options.delete(:idempotency_key)
20
+ headers = {"OpenAI-Beta" => "agents=v1"}.merge(@options[:extra_headers].to_h)
21
+ @options[:extra_headers] = headers.reject do |key, _|
22
+ key.to_s.casecmp?("idempotency-key")
23
+ end
24
+ end
25
+
26
+ def handler_names = @handlers.keys
27
+
28
+ def observe(event)
29
+ @session_id ||= event.session.id.dup if event.type == :"agent.session.created"
30
+ end
31
+
32
+ def prepare_call(event)
33
+ unless event.type == :"agent.session.turn.item.added" &&
34
+ event.item.is_a?(OpenAI::Models::Beta::AgentFunctionCallItem)
35
+ return
36
+ end
37
+
38
+ call = event.item
39
+ key = [call.turn_id.dup, call.call_id.dup]
40
+ return if @handled_calls.key?(key)
41
+
42
+ @handled_calls[key] = true
43
+ handler = @handlers[call.name]
44
+ return unless handler
45
+ raise RuntimeError, "Creation stream did not identify a session" unless @session_id
46
+
47
+ OpenAI::Helpers::Agents::Tools.prepare(
48
+ call,
49
+ handler,
50
+ session_id: @session_id,
51
+ on_tool_error: @on_tool_error
52
+ )
53
+ end
54
+
55
+ def dispatch(result)
56
+ idempotency_key = SecureRandom.uuid
57
+ delays = [0.1, 0.3, 0.6]
58
+ begin
59
+ @sessions
60
+ .events
61
+ .create(@session_id, events: [result], idempotency_key: idempotency_key, request_options: @options)
62
+ rescue OpenAI::Errors::BadRequestError => error
63
+ delay = delays.shift
64
+ raise unless delay && OpenAI::Helpers::Agents::Tools.pending_call_race?(error, result[:call_id])
65
+
66
+ sleep(delay)
67
+ retry
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenAI
4
+ module Helpers
5
+ module Beta
6
+ module Agents
7
+ # Beta, local-only diagnostic delivered to sessions.stream(on_tool_error:).
8
+ # Use alongside tool_handlers for logging or monitoring argument parsing,
9
+ # handler execution, and output conversion failures, not API/transport errors.
10
+ # The original exception may contain sensitive application data.
11
+ class ToolError
12
+ attr_reader :error, :tool_name, :session_id, :turn_id, :call_id, :stage
13
+
14
+ # @api private
15
+ def initialize(error:, tool_name:, session_id:, turn_id:, call_id:, stage:)
16
+ @error = error
17
+ @tool_name = tool_name.dup.freeze
18
+ @session_id = session_id.dup.freeze
19
+ @turn_id = turn_id.dup.freeze
20
+ @call_id = call_id.dup.freeze
21
+ @stage = stage
22
+ freeze
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -200,6 +200,21 @@ module OpenAI
200
200
  # @return [String, nil]
201
201
  optional :api_key_id, String, nil?: true
202
202
 
203
+ # @!attribute api_source
204
+ # When grouped by `api_source`, `agents_api` identifies attributed Agents API
205
+ # activity and `unlabeled` includes all records without published source
206
+ # attribution, including historical and unknown origins. Unlabeled does not imply
207
+ # direct API usage. Without source grouping, this field is null.
208
+ #
209
+ # @return [Symbol, OpenAI::Models::Admin::Organization::UsageAudioSpeechesResponse::Data::Result::OrganizationUsageCompletionsResult::APISource, nil]
210
+ optional(
211
+ :api_source,
212
+ enum: -> {
213
+ OpenAI::Models::Admin::Organization::UsageAudioSpeechesResponse::Data::Result::OrganizationUsageCompletionsResult::APISource
214
+ },
215
+ nil?: true
216
+ )
217
+
203
218
  # @!attribute batch
204
219
  # When `group_by=batch`, this field tells whether the grouped usage result is
205
220
  # batch or not.
@@ -318,7 +333,7 @@ module OpenAI
318
333
  # @return [String, nil]
319
334
  optional :user_id, String, nil?: true
320
335
 
321
- # @!method initialize(input_tokens:, num_model_requests:, output_tokens:, api_key_id: nil, batch: nil, input_audio_tokens: nil, input_cache_write_12h_tokens: nil, input_cache_write_tokens: nil, input_cached_audio_tokens: nil, input_cached_image_tokens: nil, input_cached_text_tokens: nil, input_cached_tokens: nil, input_image_tokens: nil, input_text_tokens: nil, input_uncached_tokens: nil, model: nil, output_audio_tokens: nil, output_image_tokens: nil, output_text_tokens: nil, project_id: nil, service_tier: nil, user_id: nil, object: :"organization.usage.completions.result")
336
+ # @!method initialize(input_tokens:, num_model_requests:, output_tokens:, api_key_id: nil, api_source: nil, batch: nil, input_audio_tokens: nil, input_cache_write_12h_tokens: nil, input_cache_write_tokens: nil, input_cached_audio_tokens: nil, input_cached_image_tokens: nil, input_cached_text_tokens: nil, input_cached_tokens: nil, input_image_tokens: nil, input_text_tokens: nil, input_uncached_tokens: nil, model: nil, output_audio_tokens: nil, output_image_tokens: nil, output_text_tokens: nil, project_id: nil, service_tier: nil, user_id: nil, object: :"organization.usage.completions.result")
322
337
  # The aggregated completions usage details of the specific time bucket.
323
338
  #
324
339
  # @param input_tokens [Integer]
@@ -338,6 +353,12 @@ module OpenAI
338
353
  # When `group_by=api_key_id`, this field provides the API key ID of the grouped
339
354
  # usage result.
340
355
  #
356
+ # @param api_source [Symbol, OpenAI::Models::Admin::Organization::UsageAudioSpeechesResponse::Data::Result::OrganizationUsageCompletionsResult::APISource, nil]
357
+ # When grouped by `api_source`, `agents_api` identifies attributed Agents API
358
+ # activity and `unlabeled` includes all records without published source
359
+ # attribution, including historical and unknown origins. Unlabeled does not imply
360
+ # direct API usage. Without source grouping, this field is null.
361
+ #
341
362
  # @param batch [Boolean, nil]
342
363
  # When `group_by=batch`, this field tells whether the grouped usage result is
343
364
  # batch or not.
@@ -403,6 +424,22 @@ module OpenAI
403
424
  # result.
404
425
  #
405
426
  # @param object [Symbol, :"organization.usage.completions.result"]
427
+
428
+ # When grouped by `api_source`, `agents_api` identifies attributed Agents API
429
+ # activity and `unlabeled` includes all records without published source
430
+ # attribution, including historical and unknown origins. Unlabeled does not imply
431
+ # direct API usage. Without source grouping, this field is null.
432
+ #
433
+ # @see OpenAI::Models::Admin::Organization::UsageAudioSpeechesResponse::Data::Result::OrganizationUsageCompletionsResult#api_source
434
+ module APISource
435
+ extend OpenAI::Internal::Type::Enum
436
+
437
+ AGENTS_API = :agents_api
438
+ UNLABELED = :unlabeled
439
+
440
+ # @!method self.values
441
+ # @return [Array<Symbol>]
442
+ end
406
443
  end
407
444
 
408
445
  class OrganizationUsageEmbeddingsResult < OpenAI::Internal::Type::BaseModel
@@ -952,6 +989,21 @@ module OpenAI
952
989
  # @return [String, nil]
953
990
  optional :api_key_id, String, nil?: true
954
991
 
992
+ # @!attribute api_source
993
+ # When grouped by `api_source`, `agents_api` identifies attributed Agents API
994
+ # activity and `unlabeled` includes all records without published source
995
+ # attribution, including historical and unknown origins. Unlabeled does not imply
996
+ # direct API usage. Without source grouping, this field is null.
997
+ #
998
+ # @return [Symbol, OpenAI::Models::Admin::Organization::UsageAudioSpeechesResponse::Data::Result::OrganizationUsageWebSearchesResult::APISource, nil]
999
+ optional(
1000
+ :api_source,
1001
+ enum: -> {
1002
+ OpenAI::Models::Admin::Organization::UsageAudioSpeechesResponse::Data::Result::OrganizationUsageWebSearchesResult::APISource
1003
+ },
1004
+ nil?: true
1005
+ )
1006
+
955
1007
  # @!attribute context_level
956
1008
  # When `group_by=context_level`, this field provides the search context size of
957
1009
  # the grouped usage result.
@@ -980,7 +1032,7 @@ module OpenAI
980
1032
  # @return [String, nil]
981
1033
  optional :user_id, String, nil?: true
982
1034
 
983
- # @!method initialize(num_model_requests:, num_requests:, api_key_id: nil, context_level: nil, model: nil, project_id: nil, user_id: nil, object: :"organization.usage.web_searches.result")
1035
+ # @!method initialize(num_model_requests:, num_requests:, api_key_id: nil, api_source: nil, context_level: nil, model: nil, project_id: nil, user_id: nil, object: :"organization.usage.web_searches.result")
984
1036
  # The aggregated web search calls usage details of the specific time bucket.
985
1037
  #
986
1038
  # @param num_model_requests [Integer]
@@ -993,6 +1045,12 @@ module OpenAI
993
1045
  # When `group_by=api_key_id`, this field provides the API key ID of the grouped
994
1046
  # usage result.
995
1047
  #
1048
+ # @param api_source [Symbol, OpenAI::Models::Admin::Organization::UsageAudioSpeechesResponse::Data::Result::OrganizationUsageWebSearchesResult::APISource, nil]
1049
+ # When grouped by `api_source`, `agents_api` identifies attributed Agents API
1050
+ # activity and `unlabeled` includes all records without published source
1051
+ # attribution, including historical and unknown origins. Unlabeled does not imply
1052
+ # direct API usage. Without source grouping, this field is null.
1053
+ #
996
1054
  # @param context_level [String, nil]
997
1055
  # When `group_by=context_level`, this field provides the search context size of
998
1056
  # the grouped usage result.
@@ -1010,6 +1068,22 @@ module OpenAI
1010
1068
  # result.
1011
1069
  #
1012
1070
  # @param object [Symbol, :"organization.usage.web_searches.result"]
1071
+
1072
+ # When grouped by `api_source`, `agents_api` identifies attributed Agents API
1073
+ # activity and `unlabeled` includes all records without published source
1074
+ # attribution, including historical and unknown origins. Unlabeled does not imply
1075
+ # direct API usage. Without source grouping, this field is null.
1076
+ #
1077
+ # @see OpenAI::Models::Admin::Organization::UsageAudioSpeechesResponse::Data::Result::OrganizationUsageWebSearchesResult#api_source
1078
+ module APISource
1079
+ extend OpenAI::Internal::Type::Enum
1080
+
1081
+ AGENTS_API = :agents_api
1082
+ UNLABELED = :unlabeled
1083
+
1084
+ # @!method self.values
1085
+ # @return [Array<Symbol>]
1086
+ end
1013
1087
  end
1014
1088
 
1015
1089
  class OrganizationCostsResult < OpenAI::Internal::Type::BaseModel
@@ -1036,6 +1110,21 @@ module OpenAI
1036
1110
  # @return [String, nil]
1037
1111
  optional :api_key_id, String, nil?: true
1038
1112
 
1113
+ # @!attribute api_source
1114
+ # When grouped by `api_source`, `agents_api` identifies attributed Agents API
1115
+ # activity and `unlabeled` includes all records without published source
1116
+ # attribution, including historical and unknown origins. Unlabeled does not imply
1117
+ # direct API usage. Without source grouping, this field is null.
1118
+ #
1119
+ # @return [Symbol, OpenAI::Models::Admin::Organization::UsageAudioSpeechesResponse::Data::Result::OrganizationCostsResult::APISource, nil]
1120
+ optional(
1121
+ :api_source,
1122
+ enum: -> {
1123
+ OpenAI::Models::Admin::Organization::UsageAudioSpeechesResponse::Data::Result::OrganizationCostsResult::APISource
1124
+ },
1125
+ nil?: true
1126
+ )
1127
+
1039
1128
  # @!attribute line_item
1040
1129
  # When `group_by=line_item`, this field provides the line item of the grouped
1041
1130
  # costs result.
@@ -1070,7 +1159,14 @@ module OpenAI
1070
1159
  nil?: true
1071
1160
  )
1072
1161
 
1073
- # @!method initialize(amount: nil, api_key_id: nil, line_item: nil, project_id: nil, quantity: nil, quantity_unit: nil, object: :"organization.costs.result")
1162
+ # @!attribute user_id
1163
+ # When `group_by=user_id`, this field provides the user ID of the grouped costs
1164
+ # result.
1165
+ #
1166
+ # @return [String, nil]
1167
+ optional :user_id, String, nil?: true
1168
+
1169
+ # @!method initialize(amount: nil, api_key_id: nil, api_source: nil, line_item: nil, project_id: nil, quantity: nil, quantity_unit: nil, user_id: nil, object: :"organization.costs.result")
1074
1170
  # The aggregated costs details of the specific time bucket.
1075
1171
  #
1076
1172
  # @param amount [OpenAI::Models::Admin::Organization::UsageAudioSpeechesResponse::Data::Result::OrganizationCostsResult::Amount]
@@ -1080,6 +1176,12 @@ module OpenAI
1080
1176
  # When `group_by=api_key_id`, this field provides the API Key ID of the grouped
1081
1177
  # costs result.
1082
1178
  #
1179
+ # @param api_source [Symbol, OpenAI::Models::Admin::Organization::UsageAudioSpeechesResponse::Data::Result::OrganizationCostsResult::APISource, nil]
1180
+ # When grouped by `api_source`, `agents_api` identifies attributed Agents API
1181
+ # activity and `unlabeled` includes all records without published source
1182
+ # attribution, including historical and unknown origins. Unlabeled does not imply
1183
+ # direct API usage. Without source grouping, this field is null.
1184
+ #
1083
1185
  # @param line_item [String, nil]
1084
1186
  # When `group_by=line_item`, this field provides the line item of the grouped
1085
1187
  # costs result.
@@ -1096,6 +1198,10 @@ module OpenAI
1096
1198
  # The unit of the `quantity` value. If no single supported unit applies to the
1097
1199
  # result, this field is `null`.
1098
1200
  #
1201
+ # @param user_id [String, nil]
1202
+ # When `group_by=user_id`, this field provides the user ID of the grouped costs
1203
+ # result.
1204
+ #
1099
1205
  # @param object [Symbol, :"organization.costs.result"]
1100
1206
 
1101
1207
  # @see OpenAI::Models::Admin::Organization::UsageAudioSpeechesResponse::Data::Result::OrganizationCostsResult#amount
@@ -1121,6 +1227,22 @@ module OpenAI
1121
1227
  # @param value [Float]
1122
1228
  # The numeric value of the cost.
1123
1229
  end
1230
+
1231
+ # When grouped by `api_source`, `agents_api` identifies attributed Agents API
1232
+ # activity and `unlabeled` includes all records without published source
1233
+ # attribution, including historical and unknown origins. Unlabeled does not imply
1234
+ # direct API usage. Without source grouping, this field is null.
1235
+ #
1236
+ # @see OpenAI::Models::Admin::Organization::UsageAudioSpeechesResponse::Data::Result::OrganizationCostsResult#api_source
1237
+ module APISource
1238
+ extend OpenAI::Internal::Type::Enum
1239
+
1240
+ AGENTS_API = :agents_api
1241
+ UNLABELED = :unlabeled
1242
+
1243
+ # @!method self.values
1244
+ # @return [Array<Symbol>]
1245
+ end
1124
1246
  end
1125
1247
 
1126
1248
  # @!method self.variants