openai 0.99.0 → 0.101.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 (195) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +1 -1
  4. data/bedrock.md +17 -1
  5. data/lib/openai/client.rb +13 -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_response_tool_search_output_item_param.rb +1166 -3
  32. data/lib/openai/models/beta/beta_responses_server_event.rb +12 -1
  33. data/lib/openai/models/beta/beta_tool.rb +12 -9
  34. data/lib/openai/models/beta/beta_tool_search_output_namespace_tool.rb +176 -0
  35. data/lib/openai/models/beta/environment_param.rb +6 -6
  36. data/lib/openai/models/beta/session_turn_error.rb +9 -0
  37. data/lib/openai/models/chat/chat_completion_chunk.rb +85 -18
  38. data/lib/openai/models/decision.rb +313 -0
  39. data/lib/openai/models/decision_create_params.rb +233 -0
  40. data/lib/openai/models/decision_input_image.rb +53 -0
  41. data/lib/openai/models/decision_input_message.rb +60 -0
  42. data/lib/openai/models/decision_input_part.rb +20 -0
  43. data/lib/openai/models/decision_input_text.rb +21 -0
  44. data/lib/openai/models/error_object.rb +12 -1
  45. data/lib/openai/models/eval_create_params.rb +1 -1
  46. data/lib/openai/models/eval_create_response.rb +1 -1
  47. data/lib/openai/models/eval_list_response.rb +1 -1
  48. data/lib/openai/models/eval_retrieve_response.rb +1 -1
  49. data/lib/openai/models/eval_update_response.rb +1 -1
  50. data/lib/openai/models/fine_tuning/job_list_params.rb +14 -4
  51. data/lib/openai/models/graders/score_model_grader.rb +2 -2
  52. data/lib/openai/models/image_edit_params.rb +12 -9
  53. data/lib/openai/models/realtime/realtime_client_event.rb +9 -0
  54. data/lib/openai/models/realtime/session_update_event.rb +16 -0
  55. data/lib/openai/models/responses/response_computer_tool_call_output_item.rb +2 -2
  56. data/lib/openai/models/responses/response_error.rb +12 -1
  57. data/lib/openai/models/responses/response_tool_search_output_item_param.rb +1166 -3
  58. data/lib/openai/models/responses/responses_server_event.rb +12 -1
  59. data/lib/openai/models/responses/tool.rb +12 -9
  60. data/lib/openai/models/responses/tool_search_output_namespace_tool.rb +174 -0
  61. data/lib/openai/models/video_create_error.rb +12 -1
  62. data/lib/openai/models.rb +12 -0
  63. data/lib/openai/providers/bedrock.rb +20 -1
  64. data/lib/openai/resources/admin/organization/usage.rb +15 -6
  65. data/lib/openai/resources/beta/agents/environments/templates.rb +3 -4
  66. data/lib/openai/resources/beta/agents/sessions/turns/items.rb +68 -0
  67. data/lib/openai/resources/beta/agents/sessions/turns.rb +4 -0
  68. data/lib/openai/resources/beta/agents/sessions.rb +22 -2
  69. data/lib/openai/resources/decisions.rb +59 -0
  70. data/lib/openai/resources/fine_tuning/jobs.rb +7 -2
  71. data/lib/openai/resources/images.rb +8 -6
  72. data/lib/openai/version.rb +1 -1
  73. data/lib/openai.rb +11 -0
  74. data/rbi/openai/client.rbi +3 -0
  75. data/rbi/openai/helpers/agents.rbi +2 -0
  76. data/rbi/openai/helpers/beta/agents/function_tool.rbi +8 -3
  77. data/rbi/openai/helpers/beta/agents/tool_error.rbi +19 -0
  78. data/rbi/openai/helpers/beta/agents.rbi +12 -0
  79. data/rbi/openai/models/admin/organization/usage_audio_speeches_response.rbi +194 -1
  80. data/rbi/openai/models/admin/organization/usage_audio_transcriptions_response.rbi +194 -1
  81. data/rbi/openai/models/admin/organization/usage_code_interpreter_sessions_response.rbi +194 -1
  82. data/rbi/openai/models/admin/organization/usage_completions_params.rbi +9 -4
  83. data/rbi/openai/models/admin/organization/usage_completions_response.rbi +194 -1
  84. data/rbi/openai/models/admin/organization/usage_costs_params.rbi +16 -4
  85. data/rbi/openai/models/admin/organization/usage_costs_response.rbi +194 -1
  86. data/rbi/openai/models/admin/organization/usage_embeddings_response.rbi +194 -1
  87. data/rbi/openai/models/admin/organization/usage_file_search_calls_response.rbi +194 -1
  88. data/rbi/openai/models/admin/organization/usage_images_response.rbi +194 -1
  89. data/rbi/openai/models/admin/organization/usage_moderations_response.rbi +194 -1
  90. data/rbi/openai/models/admin/organization/usage_vector_stores_response.rbi +194 -1
  91. data/rbi/openai/models/admin/organization/usage_web_search_calls_params.rbi +12 -4
  92. data/rbi/openai/models/admin/organization/usage_web_search_calls_response.rbi +194 -1
  93. data/rbi/openai/models/beta/agents/environments/template_create_params.rbi +6 -6
  94. data/rbi/openai/models/beta/agents/environments/template_update_params.rbi +3 -6
  95. data/rbi/openai/models/beta/agents/sessions/turns/item_list_params.rbi +139 -0
  96. data/rbi/openai/models/beta/beta_response_computer_tool_call_output_item.rbi +2 -2
  97. data/rbi/openai/models/beta/beta_response_error.rbi +12 -0
  98. data/rbi/openai/models/beta/beta_response_tool_search_output_item_param.rbi +1893 -18
  99. data/rbi/openai/models/beta/beta_responses_server_event.rbi +12 -0
  100. data/rbi/openai/models/beta/beta_tool.rbi +12 -9
  101. data/rbi/openai/models/beta/beta_tool_search_output_namespace_tool.rbi +272 -0
  102. data/rbi/openai/models/beta/environment_param.rbi +6 -6
  103. data/rbi/openai/models/beta/session_turn_error.rbi +18 -0
  104. data/rbi/openai/models/chat/chat_completion_chunk.rbi +123 -17
  105. data/rbi/openai/models/decision.rbi +586 -0
  106. data/rbi/openai/models/decision_create_params.rbi +435 -0
  107. data/rbi/openai/models/decision_input_image.rbi +81 -0
  108. data/rbi/openai/models/decision_input_message.rbi +96 -0
  109. data/rbi/openai/models/decision_input_part.rbi +24 -0
  110. data/rbi/openai/models/decision_input_text.rbi +49 -0
  111. data/rbi/openai/models/error_object.rbi +12 -0
  112. data/rbi/openai/models/eval_create_params.rbi +1 -1
  113. data/rbi/openai/models/eval_create_response.rbi +1 -1
  114. data/rbi/openai/models/eval_list_response.rbi +1 -1
  115. data/rbi/openai/models/eval_retrieve_response.rbi +1 -1
  116. data/rbi/openai/models/eval_update_response.rbi +1 -1
  117. data/rbi/openai/models/fine_tuning/job_list_params.rbi +14 -4
  118. data/rbi/openai/models/graders/score_model_grader.rbi +2 -2
  119. data/rbi/openai/models/image_edit_params.rbi +12 -9
  120. data/rbi/openai/models/realtime/session_update_event.rbi +16 -0
  121. data/rbi/openai/models/responses/response_computer_tool_call_output_item.rbi +2 -2
  122. data/rbi/openai/models/responses/response_error.rbi +12 -0
  123. data/rbi/openai/models/responses/response_tool_search_output_item_param.rbi +1901 -18
  124. data/rbi/openai/models/responses/responses_server_event.rbi +12 -0
  125. data/rbi/openai/models/responses/tool.rbi +12 -9
  126. data/rbi/openai/models/responses/tool_search_output_namespace_tool.rbi +272 -0
  127. data/rbi/openai/models/video_create_error.rbi +12 -0
  128. data/rbi/openai/models.rbi +12 -0
  129. data/rbi/openai/resources/admin/organization/usage.rbi +15 -6
  130. data/rbi/openai/resources/beta/agents/environments/templates.rbi +3 -4
  131. data/rbi/openai/resources/beta/agents/sessions/turns/items.rbi +62 -0
  132. data/rbi/openai/resources/beta/agents/sessions/turns.rbi +3 -0
  133. data/rbi/openai/resources/beta/agents/sessions.rbi +2 -0
  134. data/rbi/openai/resources/decisions.rbi +57 -0
  135. data/rbi/openai/resources/fine_tuning/jobs.rbi +7 -2
  136. data/rbi/openai/resources/images.rbi +8 -6
  137. data/sig/openai/client.rbs +2 -0
  138. data/sig/openai/helpers/agents.rbs +3 -0
  139. data/sig/openai/helpers/beta/agents/function_tool.rbs +5 -2
  140. data/sig/openai/helpers/beta/agents/tool_error.rbs +17 -0
  141. data/sig/openai/helpers/beta/agents.rbs +5 -0
  142. data/sig/openai/models/admin/organization/usage_audio_speeches_response.rbs +87 -2
  143. data/sig/openai/models/admin/organization/usage_audio_transcriptions_response.rbs +87 -2
  144. data/sig/openai/models/admin/organization/usage_code_interpreter_sessions_response.rbs +87 -2
  145. data/sig/openai/models/admin/organization/usage_completions_params.rbs +14 -6
  146. data/sig/openai/models/admin/organization/usage_completions_response.rbs +87 -2
  147. data/sig/openai/models/admin/organization/usage_costs_params.rbs +25 -7
  148. data/sig/openai/models/admin/organization/usage_costs_response.rbs +87 -2
  149. data/sig/openai/models/admin/organization/usage_embeddings_response.rbs +87 -2
  150. data/sig/openai/models/admin/organization/usage_file_search_calls_response.rbs +87 -2
  151. data/sig/openai/models/admin/organization/usage_images_response.rbs +87 -2
  152. data/sig/openai/models/admin/organization/usage_moderations_response.rbs +87 -2
  153. data/sig/openai/models/admin/organization/usage_vector_stores_response.rbs +87 -2
  154. data/sig/openai/models/admin/organization/usage_web_search_calls_params.rbs +19 -7
  155. data/sig/openai/models/admin/organization/usage_web_search_calls_response.rbs +87 -2
  156. data/sig/openai/models/beta/agents/environments/template_create_params.rbs +4 -4
  157. data/sig/openai/models/beta/agents/environments/template_update_params.rbs +2 -4
  158. data/sig/openai/models/beta/agents/sessions/turns/item_list_params.rbs +91 -0
  159. data/sig/openai/models/beta/beta_response_computer_tool_call_output_item.rbs +2 -2
  160. data/sig/openai/models/beta/beta_response_error.rbs +10 -0
  161. data/sig/openai/models/beta/beta_response_tool_search_output_item_param.rbs +993 -4
  162. data/sig/openai/models/beta/beta_responses_server_event.rbs +10 -0
  163. data/sig/openai/models/beta/beta_tool.rbs +8 -6
  164. data/sig/openai/models/beta/beta_tool_search_output_namespace_tool.rbs +162 -0
  165. data/sig/openai/models/beta/environment_param.rbs +4 -4
  166. data/sig/openai/models/beta/session_turn_error.rbs +12 -0
  167. data/sig/openai/models/chat/chat_completion_chunk.rbs +96 -12
  168. data/sig/openai/models/decision.rbs +296 -0
  169. data/sig/openai/models/decision_create_params.rbs +227 -0
  170. data/sig/openai/models/decision_input_image.rbs +51 -0
  171. data/sig/openai/models/decision_input_message.rbs +57 -0
  172. data/sig/openai/models/decision_input_part.rbs +12 -0
  173. data/sig/openai/models/decision_input_text.rbs +15 -0
  174. data/sig/openai/models/error_object.rbs +10 -0
  175. data/sig/openai/models/eval_create_params.rbs +1 -1
  176. data/sig/openai/models/eval_create_response.rbs +1 -1
  177. data/sig/openai/models/eval_list_response.rbs +1 -1
  178. data/sig/openai/models/eval_retrieve_response.rbs +1 -1
  179. data/sig/openai/models/eval_update_response.rbs +1 -1
  180. data/sig/openai/models/fine_tuning/job_list_params.rbs +14 -4
  181. data/sig/openai/models/graders/score_model_grader.rbs +3 -3
  182. data/sig/openai/models/image_edit_params.rbs +8 -6
  183. data/sig/openai/models/responses/response_computer_tool_call_output_item.rbs +2 -2
  184. data/sig/openai/models/responses/response_error.rbs +10 -0
  185. data/sig/openai/models/responses/response_tool_search_output_item_param.rbs +993 -4
  186. data/sig/openai/models/responses/responses_server_event.rbs +10 -0
  187. data/sig/openai/models/responses/tool.rbs +8 -6
  188. data/sig/openai/models/responses/tool_search_output_namespace_tool.rbs +160 -0
  189. data/sig/openai/models/video_create_error.rbs +10 -0
  190. data/sig/openai/models.rbs +12 -0
  191. data/sig/openai/resources/beta/agents/sessions/turns/items.rbs +24 -0
  192. data/sig/openai/resources/beta/agents/sessions/turns.rbs +2 -0
  193. data/sig/openai/resources/beta/agents/sessions.rbs +1 -0
  194. data/sig/openai/resources/decisions.rbs +15 -0
  195. metadata +38 -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: 7c9c92914efc09920f412b87f40bd0401a68b41f69fa4ffcaee090cf8ee93959
4
+ data.tar.gz: af39812b8849fa3bdb1f0f04dd5da8368c45b244ec24c52cfe901e5e22445690
5
5
  SHA512:
6
- metadata.gz: 4d54994a5e2f72c57ffc898b6bf8ed1ba30745296900d4d6c0c41cde5d88c182f55d8295619140eed845e1741283b0d68704bf92fd4684c2b7a070b91d2c3ae8
7
- data.tar.gz: c277185aabed1723fa217caa826ba9df358d40a3ab5018a8ed7a70ff7f13b9c1855543144a9ba8404c3ed95bbb4bdea095802a898878b8bfab6e49dd682939d7
6
+ metadata.gz: 49f421e6b6fca640e3abbee1028b7df1935c9cc7c79ddecebc6fb50b01e920ca5fc0cbed0b652cc065730b5e6696f0de18367d51e0f4fa0ceec8caee92ccdcf5
7
+ data.tar.gz: ae6558f4994712ecb02afe85d5961c26657bc48001ad4c8dde460f408b256434cf741a71f60bb17432e3c35d2dca4180d81a8b77ceafdc6831a8117ff7971116
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.101.0](https://github.com/openai/openai-ruby/compare/v0.100.0...v0.101.0) (2026-10-06)
4
+
5
+
6
+ ### Features
7
+
8
+ * **api:** add standalone Decisions support ([#811](https://github.com/openai/openai-ruby/issues/811)) ([ab10fa3](https://github.com/openai/openai-ruby/commit/ab10fa3742cde6fe968d13099917c2c6282bd76d))
9
+
10
+ ## [0.100.0](https://github.com/openai/openai-ruby/compare/v0.99.0...v0.100.0) (2026-10-06)
11
+
12
+
13
+ ### Features
14
+
15
+ * **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))
16
+ * **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))
17
+ * **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))
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * 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))
23
+
24
+
25
+ ### Chores
26
+
27
+ * **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))
28
+
3
29
  ## [0.99.0](https://github.com/openai/openai-ruby/compare/v0.98.0...v0.99.0) (2026-10-02)
4
30
 
5
31
 
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.101.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
@@ -40,6 +40,9 @@ module OpenAI
40
40
  # @api private
41
41
  attr_reader :workload_identity_auth
42
42
 
43
+ # @return [OpenAI::Resources::Decisions]
44
+ attr_reader :decisions
45
+
43
46
  # Given a prompt, the model will return one or more predicted completions, and can
44
47
  # also return the probabilities of alternative tokens at each position.
45
48
  # @return [OpenAI::Resources::Completions]
@@ -195,6 +198,15 @@ module OpenAI
195
198
 
196
199
  # @api private
197
200
  private def build_request(request, options)
201
+ if @provider_runtime
202
+ built = super
203
+ body = built[:body]
204
+ # Capture only the final model; provider hooks run after body encoding.
205
+ model = body.reverse_each.find { |key, _| key == :model || key == "model" }&.last if body.is_a?(Hash)
206
+ model = model.to_s if model.is_a?(Symbol)
207
+ return built.merge(provider_request_model: model)
208
+ end
209
+
198
210
  unless @workload_identity_auth && x509_transport?(@requester)
199
211
  return super
200
212
  end
@@ -803,6 +815,7 @@ module OpenAI
803
815
  on_retry: self.on_retry()
804
816
  )
805
817
 
818
+ @decisions = OpenAI::Resources::Decisions.new(client: self)
806
819
  @completions = OpenAI::Resources::Completions.new(client: self)
807
820
  @chat = OpenAI::Resources::Chat.new(client: self)
808
821
  @embeddings = OpenAI::Resources::Embeddings.new(client: self)
@@ -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