openai 0.84.0 → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/README.md +1 -1
- data/SECURITY.md +10 -0
- data/docs/architecture/security-model.md +267 -0
- data/lib/openai/client.rb +4 -0
- data/lib/openai/helpers/streaming/chat_completion_stream.rb +17 -8
- data/lib/openai/helpers/streaming/response_stream.rb +13 -8
- data/lib/openai/helpers/structured_output/array_of.rb +2 -2
- data/lib/openai/helpers/structured_output/base_model.rb +19 -5
- data/lib/openai/helpers/structured_output/chat_completion_parser.rb +6 -3
- data/lib/openai/helpers/structured_output/json_schema_converter.rb +34 -11
- data/lib/openai/internal/vector_store_file_uploader.rb +1 -1
- data/lib/openai/models/admin/organization/usage_costs_params.rb +1 -1
- data/lib/openai/models/batch.rb +2 -2
- data/lib/openai/models/beta/beta_custom_tool.rb +13 -1
- data/lib/openai/models/beta/beta_function_tool.rb +8 -1
- data/lib/openai/models/beta/beta_namespace_tool.rb +10 -1
- data/lib/openai/models/beta/beta_response.rb +20 -9
- data/lib/openai/models/beta/beta_response_configuration_update_item.rb +103 -0
- data/lib/openai/models/beta/beta_response_configuration_update_item_param.rb +104 -0
- data/lib/openai/models/beta/beta_response_custom_tool_call.rb +9 -1
- data/lib/openai/models/beta/beta_response_custom_tool_call_item.rb +3 -1
- data/lib/openai/models/beta/beta_response_error.rb +96 -1
- data/lib/openai/models/beta/beta_response_function_tool_call.rb +9 -1
- data/lib/openai/models/beta/beta_response_function_tool_call_item.rb +3 -1
- data/lib/openai/models/beta/beta_response_incomplete_event.rb +4 -0
- data/lib/openai/models/beta/beta_response_inject_event.rb +2 -2
- data/lib/openai/models/beta/beta_response_inject_failed_event.rb +2 -2
- data/lib/openai/models/beta/beta_response_input_item.rb +6 -1
- data/lib/openai/models/beta/beta_response_item.rb +5 -1
- data/lib/openai/models/beta/beta_response_steer_accepted_event.rb +81 -0
- data/lib/openai/models/beta/beta_response_steer_error_code.rb +61 -0
- data/lib/openai/models/beta/beta_response_steer_event.rb +75 -0
- data/lib/openai/models/beta/beta_response_steer_failed_event.rb +151 -0
- data/lib/openai/models/beta/beta_response_steer_input.rb +360 -0
- data/lib/openai/models/beta/beta_response_steer_input_content.rb +28 -0
- data/lib/openai/models/beta/beta_response_steer_pending_event.rb +113 -0
- data/lib/openai/models/beta/beta_response_steer_pending_reason.rb +26 -0
- data/lib/openai/models/beta/beta_response_steer_required_input.rb +215 -0
- data/lib/openai/models/beta/beta_response_stream_event.rb +4 -0
- data/lib/openai/models/beta/beta_responses_client_event.rb +40 -7
- data/lib/openai/models/beta/beta_responses_server_event.rb +164 -2
- data/lib/openai/models/beta/response_compact_params.rb +9 -6
- data/lib/openai/models/beta/response_create_params.rb +9 -6
- data/lib/openai/models/beta/responses/beta_response_item_list.rb +2 -2
- data/lib/openai/models/beta/responses/input_token_count_params.rb +3 -3
- data/lib/openai/models/chat/completion_create_params.rb +4 -4
- data/lib/openai/models/chat_model.rb +1 -0
- data/lib/openai/models/conversations/conversation_create_params.rb +2 -2
- data/lib/openai/models/conversations/conversation_item.rb +5 -1
- data/lib/openai/models/conversations/conversation_item_list.rb +2 -2
- data/lib/openai/models/conversations/item_create_params.rb +2 -2
- data/lib/openai/models/error_object.rb +90 -1
- data/lib/openai/models/eval_create_response.rb +1 -1
- data/lib/openai/models/eval_list_response.rb +1 -1
- data/lib/openai/models/eval_retrieve_response.rb +1 -1
- data/lib/openai/models/eval_update_response.rb +1 -1
- data/lib/openai/models/evals/create_eval_completions_run_data_source.rb +2 -2
- data/lib/openai/models/responses/custom_tool.rb +13 -1
- data/lib/openai/models/responses/function_tool.rb +8 -1
- data/lib/openai/models/responses/input_token_count_params.rb +3 -3
- data/lib/openai/models/responses/namespace_tool.rb +10 -1
- data/lib/openai/models/responses/response.rb +16 -8
- data/lib/openai/models/responses/response_compact_params.rb +9 -6
- data/lib/openai/models/responses/response_configuration_update_item.rb +56 -0
- data/lib/openai/models/responses/response_configuration_update_item_param.rb +57 -0
- data/lib/openai/models/responses/response_create_params.rb +5 -5
- data/lib/openai/models/responses/response_custom_tool_call.rb +9 -1
- data/lib/openai/models/responses/response_custom_tool_call_item.rb +3 -1
- data/lib/openai/models/responses/response_error.rb +96 -1
- data/lib/openai/models/responses/response_function_tool_call.rb +9 -1
- data/lib/openai/models/responses/response_function_tool_call_item.rb +3 -1
- data/lib/openai/models/responses/response_incomplete_event.rb +4 -0
- data/lib/openai/models/responses/response_input_item.rb +6 -1
- data/lib/openai/models/responses/response_item.rb +5 -1
- data/lib/openai/models/responses/response_item_list.rb +2 -2
- data/lib/openai/models/responses/response_steer_accepted_event.rb +79 -0
- data/lib/openai/models/responses/response_steer_error_code.rb +59 -0
- data/lib/openai/models/responses/response_steer_event.rb +73 -0
- data/lib/openai/models/responses/response_steer_failed_event.rb +149 -0
- data/lib/openai/models/responses/response_steer_input.rb +304 -0
- data/lib/openai/models/responses/response_steer_input_content.rb +26 -0
- data/lib/openai/models/responses/response_steer_pending_event.rb +111 -0
- data/lib/openai/models/responses/response_steer_pending_reason.rb +24 -0
- data/lib/openai/models/responses/response_steer_required_input.rb +213 -0
- data/lib/openai/models/responses/response_stream_event.rb +4 -0
- data/lib/openai/models/responses/responses_client_event.rb +872 -769
- data/lib/openai/models/responses/responses_server_event.rb +162 -2
- data/lib/openai/models/safety/alert_retrieve_params.rb +24 -0
- data/lib/openai/models/safety/safety_alert.rb +96 -0
- data/lib/openai/models/video_create_error.rb +93 -1
- data/lib/openai/models/webhooks/safety_alert_created_webhook_event.rb +63 -0
- data/lib/openai/models/webhooks/safety_org_alert_created_webhook_event.rb +63 -0
- data/lib/openai/models/webhooks/unwrap_webhook_event.rb +7 -1
- data/lib/openai/models.rb +2 -0
- data/lib/openai/net_http_client.rb +1 -1
- data/lib/openai/providers/bedrock.rb +20 -12
- data/lib/openai/resources/beta/responses/input_items.rb +1 -1
- data/lib/openai/resources/beta/responses/input_tokens.rb +1 -1
- data/lib/openai/resources/beta/responses.rb +6 -6
- data/lib/openai/resources/chat/completions.rb +2 -2
- data/lib/openai/resources/conversations/items.rb +3 -3
- data/lib/openai/resources/conversations.rb +1 -1
- data/lib/openai/resources/responses/input_items.rb +1 -1
- data/lib/openai/resources/responses/input_tokens.rb +1 -1
- data/lib/openai/resources/responses.rb +6 -6
- data/lib/openai/resources/safety/alerts.rb +37 -0
- data/lib/openai/resources/safety.rb +18 -0
- data/lib/openai/resources/webhooks.rb +1 -1
- data/lib/openai/version.rb +1 -1
- data/lib/openai.rb +28 -0
- data/rbi/openai/client.rbi +3 -0
- data/rbi/openai/models/admin/organization/usage_costs_params.rbi +2 -2
- data/rbi/openai/models/batch.rbi +2 -2
- data/rbi/openai/models/beta/beta_custom_tool.rbi +15 -0
- data/rbi/openai/models/beta/beta_function_tool.rbi +11 -0
- data/rbi/openai/models/beta/beta_namespace_tool.rbi +15 -0
- data/rbi/openai/models/beta/beta_response.rbi +14 -6
- data/rbi/openai/models/beta/beta_response_configuration_update_item.rbi +200 -0
- data/rbi/openai/models/beta/beta_response_configuration_update_item_param.rbi +207 -0
- data/rbi/openai/models/beta/beta_response_custom_tool_call.rbi +13 -0
- data/rbi/openai/models/beta/beta_response_custom_tool_call_item.rbi +6 -0
- data/rbi/openai/models/beta/beta_response_error.rbi +161 -3
- data/rbi/openai/models/beta/beta_response_function_tool_call.rbi +13 -0
- data/rbi/openai/models/beta/beta_response_function_tool_call_item.rbi +6 -0
- data/rbi/openai/models/beta/beta_response_incomplete_event.rbi +4 -0
- data/rbi/openai/models/beta/beta_response_inject_event.rbi +3 -0
- data/rbi/openai/models/beta/beta_response_inject_failed_event.rbi +3 -0
- data/rbi/openai/models/beta/beta_response_input_item.rbi +1 -0
- data/rbi/openai/models/beta/beta_response_item.rbi +1 -0
- data/rbi/openai/models/beta/beta_response_steer_accepted_event.rbi +148 -0
- data/rbi/openai/models/beta/beta_response_steer_error_code.rbi +65 -0
- data/rbi/openai/models/beta/beta_response_steer_event.rbi +118 -0
- data/rbi/openai/models/beta/beta_response_steer_failed_event.rbi +267 -0
- data/rbi/openai/models/beta/beta_response_steer_input.rbi +602 -0
- data/rbi/openai/models/beta/beta_response_steer_input_content.rbi +31 -0
- data/rbi/openai/models/beta/beta_response_steer_pending_event.rbi +222 -0
- data/rbi/openai/models/beta/beta_response_steer_pending_reason.rbi +35 -0
- data/rbi/openai/models/beta/beta_response_steer_required_input.rbi +379 -0
- data/rbi/openai/models/beta/beta_responses_client_event.rbi +8 -3
- data/rbi/openai/models/beta/beta_responses_server_event.rbi +206 -3
- data/rbi/openai/models/beta/response_compact_params.rbi +4 -3
- data/rbi/openai/models/beta/response_create_params.rbi +4 -3
- data/rbi/openai/models/beta/responses/beta_response_item_list.rbi +1 -0
- data/rbi/openai/models/chat/completion_create_params.rbi +3 -3
- data/rbi/openai/models/chat_model.rbi +1 -0
- data/rbi/openai/models/conversations/conversation_create_params.rbi +3 -0
- data/rbi/openai/models/conversations/conversation_item.rbi +1 -0
- data/rbi/openai/models/conversations/conversation_item_list.rbi +1 -0
- data/rbi/openai/models/conversations/item_create_params.rbi +3 -0
- data/rbi/openai/models/error_object.rbi +156 -3
- data/rbi/openai/models/eval_create_response.rbi +1 -1
- data/rbi/openai/models/eval_list_response.rbi +1 -1
- data/rbi/openai/models/eval_retrieve_response.rbi +1 -1
- data/rbi/openai/models/eval_update_response.rbi +1 -1
- data/rbi/openai/models/evals/create_eval_completions_run_data_source.rbi +2 -2
- data/rbi/openai/models/responses/custom_tool.rbi +15 -0
- data/rbi/openai/models/responses/function_tool.rbi +11 -0
- data/rbi/openai/models/responses/namespace_tool.rbi +15 -0
- data/rbi/openai/models/responses/response.rbi +12 -5
- data/rbi/openai/models/responses/response_compact_params.rbi +4 -3
- data/rbi/openai/models/responses/response_configuration_update_item.rbi +112 -0
- data/rbi/openai/models/responses/response_configuration_update_item_param.rbi +117 -0
- data/rbi/openai/models/responses/response_create_params.rbi +2 -2
- data/rbi/openai/models/responses/response_custom_tool_call.rbi +13 -0
- data/rbi/openai/models/responses/response_custom_tool_call_item.rbi +6 -0
- data/rbi/openai/models/responses/response_error.rbi +161 -3
- data/rbi/openai/models/responses/response_function_tool_call.rbi +13 -0
- data/rbi/openai/models/responses/response_function_tool_call_item.rbi +6 -0
- data/rbi/openai/models/responses/response_incomplete_event.rbi +4 -0
- data/rbi/openai/models/responses/response_input_item.rbi +1 -0
- data/rbi/openai/models/responses/response_item.rbi +1 -0
- data/rbi/openai/models/responses/response_item_list.rbi +1 -0
- data/rbi/openai/models/responses/response_steer_accepted_event.rbi +146 -0
- data/rbi/openai/models/responses/response_steer_error_code.rbi +63 -0
- data/rbi/openai/models/responses/response_steer_event.rbi +119 -0
- data/rbi/openai/models/responses/response_steer_failed_event.rbi +268 -0
- data/rbi/openai/models/responses/response_steer_input.rbi +474 -0
- data/rbi/openai/models/responses/response_steer_input_content.rbi +29 -0
- data/rbi/openai/models/responses/response_steer_pending_event.rbi +220 -0
- data/rbi/openai/models/responses/response_steer_pending_reason.rbi +33 -0
- data/rbi/openai/models/responses/response_steer_required_input.rbi +377 -0
- data/rbi/openai/models/responses/responses_client_event.rbi +1204 -1036
- data/rbi/openai/models/responses/responses_server_event.rbi +202 -3
- data/rbi/openai/models/safety/alert_retrieve_params.rbi +55 -0
- data/rbi/openai/models/safety/safety_alert.rbi +147 -0
- data/rbi/openai/models/video_create_error.rbi +149 -3
- data/rbi/openai/models/webhooks/safety_alert_created_webhook_event.rbi +130 -0
- data/rbi/openai/models/webhooks/safety_org_alert_created_webhook_event.rbi +130 -0
- data/rbi/openai/models/webhooks/unwrap_webhook_event.rbi +3 -1
- data/rbi/openai/models.rbi +2 -0
- data/rbi/openai/resources/admin/organization/usage.rbi +1 -1
- data/rbi/openai/resources/beta/responses.rbi +3 -3
- data/rbi/openai/resources/chat/completions.rbi +2 -2
- data/rbi/openai/resources/conversations/items.rbi +1 -0
- data/rbi/openai/resources/conversations.rbi +1 -0
- data/rbi/openai/resources/responses.rbi +3 -3
- data/rbi/openai/resources/safety/alerts.rbi +30 -0
- data/rbi/openai/resources/safety.rbi +18 -0
- data/rbi/openai/resources/webhooks.rbi +3 -1
- data/sig/openai/client.rbs +2 -0
- data/sig/openai/models/beta/beta_custom_tool.rbs +7 -0
- data/sig/openai/models/beta/beta_function_tool.rbs +7 -0
- data/sig/openai/models/beta/beta_namespace_tool.rbs +7 -0
- data/sig/openai/models/beta/beta_response.rbs +6 -2
- data/sig/openai/models/beta/beta_response_configuration_update_item.rbs +91 -0
- data/sig/openai/models/beta/beta_response_configuration_update_item_param.rbs +87 -0
- data/sig/openai/models/beta/beta_response_custom_tool_call.rbs +7 -0
- data/sig/openai/models/beta/beta_response_custom_tool_call_item.rbs +3 -0
- data/sig/openai/models/beta/beta_response_error.rbs +82 -3
- data/sig/openai/models/beta/beta_response_function_tool_call.rbs +7 -0
- data/sig/openai/models/beta/beta_response_function_tool_call_item.rbs +3 -0
- data/sig/openai/models/beta/beta_response_input_item.rbs +1 -0
- data/sig/openai/models/beta/beta_response_item.rbs +1 -0
- data/sig/openai/models/beta/beta_response_steer_accepted_event.rbs +53 -0
- data/sig/openai/models/beta/beta_response_steer_error_code.rbs +31 -0
- data/sig/openai/models/beta/beta_response_steer_event.rbs +34 -0
- data/sig/openai/models/beta/beta_response_steer_failed_event.rbs +102 -0
- data/sig/openai/models/beta/beta_response_steer_input.rbs +214 -0
- data/sig/openai/models/beta/beta_response_steer_input_content.rbs +18 -0
- data/sig/openai/models/beta/beta_response_steer_pending_event.rbs +63 -0
- data/sig/openai/models/beta/beta_response_steer_pending_reason.rbs +16 -0
- data/sig/openai/models/beta/beta_response_steer_required_input.rbs +161 -0
- data/sig/openai/models/beta/beta_responses_client_event.rbs +4 -1
- data/sig/openai/models/beta/beta_responses_server_event.rbs +80 -3
- data/sig/openai/models/beta/response_compact_params.rbs +3 -1
- data/sig/openai/models/beta/response_create_params.rbs +3 -1
- data/sig/openai/models/chat_model.rbs +3 -1
- data/sig/openai/models/conversations/conversation_item.rbs +1 -0
- data/sig/openai/models/error_object.rbs +82 -3
- data/sig/openai/models/responses/custom_tool.rbs +7 -0
- data/sig/openai/models/responses/function_tool.rbs +7 -0
- data/sig/openai/models/responses/namespace_tool.rbs +7 -0
- data/sig/openai/models/responses/response.rbs +3 -1
- data/sig/openai/models/responses/response_compact_params.rbs +3 -1
- data/sig/openai/models/responses/response_configuration_update_item.rbs +46 -0
- data/sig/openai/models/responses/response_configuration_update_item_param.rbs +46 -0
- data/sig/openai/models/responses/response_custom_tool_call.rbs +7 -0
- data/sig/openai/models/responses/response_custom_tool_call_item.rbs +3 -0
- data/sig/openai/models/responses/response_error.rbs +79 -3
- data/sig/openai/models/responses/response_function_tool_call.rbs +7 -0
- data/sig/openai/models/responses/response_function_tool_call_item.rbs +3 -0
- data/sig/openai/models/responses/response_input_item.rbs +1 -0
- data/sig/openai/models/responses/response_item.rbs +1 -0
- data/sig/openai/models/responses/response_steer_accepted_event.rbs +51 -0
- data/sig/openai/models/responses/response_steer_error_code.rbs +29 -0
- data/sig/openai/models/responses/response_steer_event.rbs +32 -0
- data/sig/openai/models/responses/response_steer_failed_event.rbs +100 -0
- data/sig/openai/models/responses/response_steer_input.rbs +182 -0
- data/sig/openai/models/responses/response_steer_input_content.rbs +16 -0
- data/sig/openai/models/responses/response_steer_pending_event.rbs +61 -0
- data/sig/openai/models/responses/response_steer_pending_reason.rbs +13 -0
- data/sig/openai/models/responses/response_steer_required_input.rbs +159 -0
- data/sig/openai/models/responses/responses_client_event.rbs +355 -344
- data/sig/openai/models/responses/responses_server_event.rbs +80 -3
- data/sig/openai/models/safety/alert_retrieve_params.rbs +22 -0
- data/sig/openai/models/safety/safety_alert.rbs +81 -0
- data/sig/openai/models/video_create_error.rbs +87 -3
- data/sig/openai/models/webhooks/safety_alert_created_webhook_event.rbs +52 -0
- data/sig/openai/models/webhooks/safety_org_alert_created_webhook_event.rbs +52 -0
- data/sig/openai/models/webhooks/unwrap_webhook_event.rbs +2 -0
- data/sig/openai/models.rbs +2 -0
- data/sig/openai/resources/safety/alerts.rbs +14 -0
- data/sig/openai/resources/safety.rbs +9 -0
- data/sig/openai/resources/webhooks.rbs +3 -1
- metadata +86 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 37f60ef5c84fcaef53fccd86fa30e1cc0988b4033abdd3e14711247e082de5d4
|
|
4
|
+
data.tar.gz: 97cd2b216e53d22574e00031f94b0d2975393a9128a9d2528ea735679bb04f8f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1493b225ac23056cda189b6af2bdd547aeb3441fef8f85c9454ae2d2d51e87e84743807cd86875f85a1d055eeb1f8c30ab684b7a11f76e46f86fbcd47c3b0995
|
|
7
|
+
data.tar.gz: 702dc478f26dff8454563e36b2d3ed4113ced5f04f104d34ab399091b290bf34ca3cccbb1ad8f44b2dae132d40053e33b9d0a86dc20f3a66316e0aed9378459d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.85.0](https://github.com/openai/openai-ruby/compare/v0.84.0...v0.85.0) (2026-09-03)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **api:** add gpt-6-astra and related features ([#596](https://github.com/openai/openai-ruby/issues/596)) ([a08d882](https://github.com/openai/openai-ruby/commit/a08d882c2575e0743a18eb5a4eae7ef3f94b0314))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **ci:** restore Castiron statuses for fork PRs ([#572](https://github.com/openai/openai-ruby/issues/572)) ([b78af5a](https://github.com/openai/openai-ruby/commit/b78af5acf775627c8eddf42da41bef7ea7325235))
|
|
14
|
+
* correct StringIO request content length ([#583](https://github.com/openai/openai-ruby/issues/583)) ([5baeac8](https://github.com/openai/openai-ruby/commit/5baeac8131511141491fc41abdb2e4e4c596f3de))
|
|
15
|
+
* emit done events for terminal chat tool calls ([#573](https://github.com/openai/openai-ruby/issues/573)) ([891936c](https://github.com/openai/openai-ruby/commit/891936ce58db613a61d270792f40da400ea44b97))
|
|
16
|
+
* handle nullable structured-output literals ([#568](https://github.com/openai/openai-ruby/issues/568)) ([c4fee85](https://github.com/openai/openai-ruby/commit/c4fee85338aa283d6cec80538e4524a406fa8b7d))
|
|
17
|
+
* isolate chat stream logprob chunks ([#585](https://github.com/openai/openai-ruby/issues/585)) ([0daedb2](https://github.com/openai/openai-ruby/commit/0daedb2878373da899dac8b029fdb0523a26c6ef))
|
|
18
|
+
* preserve initial chat stream system fingerprint ([#580](https://github.com/openai/openai-ruby/issues/580)) ([a31c1ce](https://github.com/openai/openai-ruby/commit/a31c1cef9ee8e1fc5ed7a9b29f23171260a101e4))
|
|
19
|
+
* preserve late chat completion service tier ([#567](https://github.com/openai/openai-ruby/issues/567)) ([52a0c64](https://github.com/openai/openai-ruby/commit/52a0c64fbb04535d4c1250986a5116298048764e))
|
|
20
|
+
* preserve nested array schema definitions ([#581](https://github.com/openai/openai-ruby/issues/581)) ([bbfbe29](https://github.com/openai/openai-ruby/commit/bbfbe29f53018b4a3a1c61edab355ad901bb0239))
|
|
21
|
+
* preserve recursive structured output refs ([#584](https://github.com/openai/openai-ruby/issues/584)) ([b23fc75](https://github.com/openai/openai-ruby/commit/b23fc752952314de279a32175d98b7ca7fd33f8c))
|
|
22
|
+
* preserve retained Responses stream events ([#577](https://github.com/openai/openai-ruby/issues/577)) ([149e685](https://github.com/openai/openai-ruby/commit/149e685c64f72052d19d9389ba07d40a14c4c070))
|
|
23
|
+
* preserve string-tagged structured output models ([#582](https://github.com/openai/openai-ruby/issues/582)) ([983eaf9](https://github.com/openai/openai-ruby/commit/983eaf9c240f6888d2872abddd555fa3cdd5af95))
|
|
24
|
+
* raise finish errors for initial chat stream chunks ([#579](https://github.com/openai/openai-ruby/issues/579)) ([c923659](https://github.com/openai/openai-ruby/commit/c923659aeddd2ed5e928830babd2f75ea6cbae3d))
|
|
25
|
+
* short-circuit Bedrock profile credentials ([#588](https://github.com/openai/openai-ruby/issues/588)) ([1c7ed46](https://github.com/openai/openai-ruby/commit/1c7ed46782288a882aed9ccedcf69ecc53fd9c6d))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Documentation
|
|
29
|
+
|
|
30
|
+
* clarify commit prefix user impact ([#575](https://github.com/openai/openai-ruby/issues/575)) ([73752ad](https://github.com/openai/openai-ruby/commit/73752ad281e7d3fde9c4b63bd1efa17b151ff5a6))
|
|
31
|
+
* establish canonical security model ([#565](https://github.com/openai/openai-ruby/issues/565)) ([2ded215](https://github.com/openai/openai-ruby/commit/2ded21565f969d026c93027d1d7264a8081d6242))
|
|
32
|
+
|
|
3
33
|
## [0.84.0](https://github.com/openai/openai-ruby/compare/v0.83.0...v0.84.0) (2026-09-02)
|
|
4
34
|
|
|
5
35
|
|
data/README.md
CHANGED
data/SECURITY.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Security Policy
|
|
2
2
|
|
|
3
|
+
## Threat model authority
|
|
4
|
+
|
|
5
|
+
This file governs vulnerability disclosure and reportability. The repository's
|
|
6
|
+
canonical detailed threat model, trust boundaries, security invariants, and
|
|
7
|
+
severity calibration are defined in
|
|
8
|
+
[docs/architecture/security-model.md](docs/architecture/security-model.md).
|
|
9
|
+
Codex Security scans and reviewers should use that document from the scanned
|
|
10
|
+
revision rather than maintaining a second pasted threat model here or in scan
|
|
11
|
+
configuration.
|
|
12
|
+
|
|
3
13
|
## Reporting a vulnerability
|
|
4
14
|
|
|
5
15
|
Please report potential security vulnerabilities through OpenAI's
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# OpenAI Ruby SDK Security Model
|
|
2
|
+
|
|
3
|
+
This is the canonical detailed threat model for this repository. `SECURITY.md`
|
|
4
|
+
remains the authority for disclosure and reportability; this document is the
|
|
5
|
+
authority for architecture, trust boundaries, security invariants, attack
|
|
6
|
+
surface, and severity calibration. Codex Security scans must load this file
|
|
7
|
+
from the scanned revision instead of relying on a duplicated cloud-configured
|
|
8
|
+
model.
|
|
9
|
+
|
|
10
|
+
## 1. Overview
|
|
11
|
+
|
|
12
|
+
The OpenAI Ruby gem is a caller-embedded Ruby 3.3+ library, not a hosted
|
|
13
|
+
service. It converts caller-supplied configuration, parameters, files, and
|
|
14
|
+
credentials into authenticated REST, SSE, and optional Realtime WebSocket
|
|
15
|
+
requests, then parses remote responses into typed Ruby values
|
|
16
|
+
([README.md](../../README.md#L1-L3),
|
|
17
|
+
[README.md](../../README.md#L38-L59)).
|
|
18
|
+
|
|
19
|
+
| Component | Role | Evidence |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `OpenAI::Client` and generated resources | Public REST entry points, credentials, retries, request options | `lib/openai/client.rb:5-41`, `lib/openai/client.rb:129-170` |
|
|
22
|
+
| Base transport, HTTP client, and multipart encoder | URL construction, redirects, retries, default Net::HTTP TLS/pooling, multipart headers/files, serialization, response decoding | `lib/openai/internal/transport/base_client.rb:13-27`, `lib/openai/net_http_client.rb:55-71`, `lib/openai/net_http_client.rb:139-205`, `lib/openai/net_http_client.rb:241-345`, `lib/openai/file_part.rb:33-43`, `lib/openai/file_part.rb:110-119`, `lib/openai/internal/util.rb:533-565`, `lib/openai/http_client.rb:138-237` |
|
|
23
|
+
| Streaming and Realtime helpers | Construct authenticated WebSocket handshakes, enforce transport policy, and parse SSE/WebSocket JSON into typed events | `lib/openai/internal/stream.rb:19-60`, `lib/openai/helpers/realtime/client_extension.rb:167-280`, `lib/openai/helpers/realtime/transports/async_websocket.rb:185-308`, `lib/openai/helpers/realtime/connection.rb:47-100` |
|
|
24
|
+
| Webhook helper | Verify timestamp and HMAC before parsing webhook events | `lib/openai/resources/webhooks.rb:15-119` |
|
|
25
|
+
| Providers and workload identity | Azure, Bedrock, built-in subject-token acquisition, exchange, and X.509 credential paths | `lib/openai/providers/azure.rb:40-58`, `lib/openai/auth/subject_token_providers/gcp_id_token_provider.rb:9-67`, `lib/openai/auth/subject_token_providers/azure_managed_identity_token_provider.rb:9-92`, `lib/openai/auth/subject_token_providers/k8s_service_account_token_provider.rb:9-37`, `lib/openai/auth/workload_identity_auth.rb:280-330` |
|
|
26
|
+
| CI and release workflows | Run repository code, upload CodeQL results, publish trusted Castiron results, live checks/examples, and releases | `.github/workflows/ci.yml:18-32`, `.github/workflows/codeql.yml:3-53`, `.github/workflows/castiron-custom-code-comment.yml:4-38`, `.github/workflows/examples-e2e.yml:1-43`, `.github/workflows/create-releases.yml:14-79` |
|
|
27
|
+
|
|
28
|
+
```mermaid
|
|
29
|
+
flowchart LR
|
|
30
|
+
Caller[Embedding application] --> SDK[OpenAI Ruby SDK]
|
|
31
|
+
SDK --> API[Configured API or provider origin]
|
|
32
|
+
API --> Parser[JSON / SSE / WebSocket parsers]
|
|
33
|
+
Parser --> Caller
|
|
34
|
+
Webhook[Webhook sender] --> Verify[HMAC + timestamp verifier]
|
|
35
|
+
Verify --> Caller
|
|
36
|
+
PR[Candidate checkout] --> CI[Ordinary read-only CI]
|
|
37
|
+
PR --> CodeQL[CodeQL analysis]
|
|
38
|
+
CodeQL --> SecurityEvents[Code-scanning results]
|
|
39
|
+
PR --> Castiron[Candidate Castiron workflow]
|
|
40
|
+
Castiron --> Trusted[Main-sourced workflow_run publisher]
|
|
41
|
+
Trusted --> GitHub[Statuses and PR comments]
|
|
42
|
+
Main[Main revision] --> Live[Live / release workflows]
|
|
43
|
+
Live --> Secrets[Referenced credentials and publishing authority]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Deployment or workflow | Resource or capability | Configuration and precedence | Safe effective value or location | Readers, writers, or recipients | Enforcing control | Evidence or unknowns |
|
|
47
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
48
|
+
| Default SDK | API/admin bearer credential and default Net::HTTP connection | Explicit option, then environment fallback; explicit/data-residency base URL, then `OPENAI_BASE_URL`, then default | Bearer credential to configured origin; default `https://api.openai.com/v1`; pooled connection keyed by origin | Configured API origin | Configured-origin request construction; cross-origin redirects strip credentials and reject bodies; HTTPS downgrade rejected; default Net::HTTP selects TLS from the URL, loads default trust roots, pools by origin, requires configurators to leave new connections unstarted, requires validators to leave previously unstarted connections unstarted, and checks `use_ssl?` against the URL scheme | `lib/openai/client.rb:634-645`, `lib/openai/client.rb:669-693`, `lib/openai/helpers/data_residency.rb:16-33`, `lib/openai/internal/transport/base_client.rb:418-452`, `lib/openai/internal/transport/base_client.rb:153-215`, `lib/openai/net_http_client.rb:55-71`, `lib/openai/net_http_client.rb:139-205`, `lib/openai/net_http_client.rb:241-345` |
|
|
49
|
+
| Webhook handler | Webhook secret | Explicit argument, then client option, then `OPENAI_WEBHOOK_SECRET` | In-process HMAC key | Local verifier only | Required headers, freshness window, HMAC, timing-safe comparison before `unwrap` parses JSON | `lib/openai/resources/webhooks.rb:15-119` |
|
|
50
|
+
| Workload identity | Built-in GCP/Azure/Kubernetes subject token and exchanged access token | GCP provider uses fixed metadata host/path plus configurable audience/timeout; Azure managed identity uses fixed IMDS endpoint plus configurable resource, identity selectors, API version, and timeout; both metadata fetches inherit Net::HTTP environment-proxy routing; Kubernetes provider uses configured/default token path; exchange uses identity/service-account IDs and default issuer URL | GCP GET to `http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity`; Azure GET to `http://169.254.169.254/metadata/identity/oauth2/token`; Kubernetes token read from configured/default path; subject token sent to `https://auth.openai.com/oauth/token`; returned token cached in memory | Local GCP/Azure metadata service, caller-owned environment proxy, or token-file path; issuer; configured API origin | Fixed GCP host/path and `Metadata-Flavor` header; fixed Azure IMDS endpoint and `Metadata` header; configured open/read timeouts; Azure JSON parsing and `access_token` extraction; Kubernetes file read/error mapping; token-type mapping, exchange timeout/deadline handling, coordinated refresh; fixed metadata URLs do not bypass caller-owned proxy configuration | `lib/openai/auth/subject_token_providers/gcp_id_token_provider.rb:9-67`, `lib/openai/auth/subject_token_providers/azure_managed_identity_token_provider.rb:9-92`, `lib/openai/auth/subject_token_providers/k8s_service_account_token_provider.rb:9-37`, `lib/openai/auth/workload_identity_auth.rb:13-15`, `lib/openai/auth/workload_identity_auth.rb:280-330` |
|
|
51
|
+
| X.509 workload identity | Attested mTLS transport and detached exchanged bearer token | Client uses the identity-configured transport when present, otherwise a caller-supplied attested `http_client` for a detached identity, and selects `X509TokenExchange` before token acquisition | Exchange POST to `https://mtls.auth.openai.com/oauth/token`; API requests use the selected transport's attested global/US/EU mTLS API origin | OpenAI mTLS issuer and matching attested API origin | Exact issuer endpoint and attested API-origin/header checks, TLS peer/hostname verification, proxy policy, no redirects | `lib/openai/client.rb:616-644`, `lib/openai/client.rb:739-756`, `test/openai/auth/x509_client_test.rb:16-34`, `lib/openai/auth/x509_transport.rb:5-10`, `lib/openai/auth/x509_transport.rb:14-26`, `lib/openai/auth/x509_transport.rb:174-237`, `lib/openai/auth/x509_transport.rb:243-315` |
|
|
52
|
+
| Azure provider | API key or Entra bearer token | Explicit endpoint/credential, then Azure environment fallback | Credential attached only to normalized Azure origin | Configured Azure endpoint | Origin validation before credential attachment; conflicting auth rejected | `lib/openai/providers/azure.rb:47-58`, `lib/openai/providers/azure.rb:141-147` |
|
|
53
|
+
| Bedrock provider | Bearer token or SigV4 authority | Explicit mode, environment, then AWS credential chain | Credential/signature to configured Bedrock origin | Configured Bedrock endpoint | Origin and endpoint/region validation; SigV4 disables redirects | `lib/openai/providers/bedrock.rb:105-168`, `lib/openai/providers/bedrock.rb:398-427` |
|
|
54
|
+
| Realtime WebSocket transport | API/workload bearer credential, WebSocket destination, TLS/proxy policy, and trace confidentiality | Client handshake builder applies bearer auth and optional workload-token refresh; caller may select `websocket_base_url` only within provider restrictions | Authenticated `ws://` or `wss://` handshake to configured Realtime origin; proxy credentials stay on CONNECT | Configured Realtime endpoint and optional proxy | Base URL validation, provider/X.509 restrictions, proxy-authorization stripping, WSS peer/hostname verification, trace redaction | `lib/openai/helpers/realtime/client_extension.rb:167-280`, `lib/openai/helpers/realtime/transports/async_websocket.rb:185-224`, `lib/openai/helpers/realtime/transports/async_websocket.rb:294-378` |
|
|
55
|
+
| Multipart uploads | Caller file contents, effective filename, content type, and multipart header parameters | Caller supplies `Pathname`, `IO`, or `FilePart`; optional filename/content type | File bytes to configured API origin; effective filename reduced to basename; header parameters escaped | Configured API origin | Media-type validation; `FilePart` basename reduction; encoder basename reduction for raw paths; quote/backslash escaping and CR/LF removal for multipart header parameters | `lib/openai/file_part.rb:33-43`, `lib/openai/file_part.rb:110-119`, `lib/openai/internal/util.rb:533-565` |
|
|
56
|
+
| Diagnostic logging | Request/response diagnostics | Explicit log level, then `OPENAI_LOG`; supplied logger or stderr | Sanitized metadata and structural body summaries | Caller logger or stderr | Credential/query redaction; multipart, streaming, binary, and large JSON bodies omitted | `lib/openai/internal/logging.rb:10-26`, `lib/openai/internal/logging.rb:299-343` |
|
|
57
|
+
| Dependency acquisition and installation | Gem source, locked revision, transitive gem, native extension, and install/build-script execution | Contributor/CI `Gemfile` selects `syntax_tree-rbs` from GitHub branch `main` and `Gemfile.lock` pins revision `247832988a850b8df050cf207f652872fda49973`; Bedrock CI selects `aws-sdk-core` through `gemfiles/bedrock.gemfile` and its lock; docs lockfiles and gemspec dependencies select other installed code | Locked RubyGems and Git sources plus package metadata from the reviewed revision | CI runners and consumer Bundler/RubyGems installations | Human/source review of dependency and lockfile changes; CI verifies the reviewed checkout but does not make an unreviewed dependency trustworthy | `AGENTS.md:39-42`, `Gemfile:3-15`, `Gemfile.lock:1-20`, `gemfiles/bedrock.gemfile:1-5`, `gemfiles/bedrock.gemfile.lock:1-58`, `.github/workflows/ci-checks.yml:124-144`, `openai.gemspec:18-48`, `docs/Gemfile:1-8`, `docs/Gemfile.lock:1-21` |
|
|
58
|
+
| Ordinary PR CI | Candidate checkout execution | Checked-out PR revision runs lint, typecheck, build, and tests | Candidate code executes with repository-code authority | Ephemeral CI runner | `permissions: {}`, `contents: read`, `persist-credentials: false` | `.github/workflows/ci.yml:18-32`, `.github/workflows/ci-checks.yml:100-120` |
|
|
59
|
+
| CodeQL PR analysis | Candidate checkout and code-scanning result upload | Pull requests, main pushes, and merge-group checks run action and Ruby analysis | Candidate source produces SARIF under runner temp; `security-events: write` uploads code-scanning results | GitHub code scanning | Top-level `permissions: {}`; job grants only `actions: read`, `contents: read`, and `security-events: write`; pinned checkout/CodeQL actions; `persist-credentials: false`; checked SARIF must exist and contain no findings | `.github/workflows/codeql.yml:3-28`, `.github/workflows/codeql.yml:37-53`, `.github/workflows/codeql.yml:55-96` |
|
|
60
|
+
| Trusted Castiron workflow-run publisher | Commit-status and PR-comment writes | Candidate-associated `workflow_run` metadata -> main-sourced reporter recomputes from current Git objects for statuses and successful report comments; fallback failure comments use event/path, current-head, and monotonic-replacement checks | `statuses: write` and `pull-requests: write` only in publisher jobs | GitHub commit statuses and PR comments | Status path: no candidate artifact, run identity validation, exact head/base checks; successful comment path: trusted report artifact; fallback failure-comment path: main-sourced code, event/path filtering, current-head matching, monotonic replacement | `.github/workflows/castiron-custom-code-comment.yml:4-38`, `.github/workflows/castiron-custom-code-comment.yml:40-98`, `.github/workflows/castiron-custom-code-comment.yml:116-171`, `.github/workflows/castiron-custom-code-comment.yml:174-237` |
|
|
61
|
+
| Live/release workflows | API keys, X.509 material, GitHub App private key, generated GitHub App token, RubyGems OIDC | Main-only conditions plus named environments; release job passes `OPENAI_SDKS_APP_PRIVATE_KEY` to pinned token action, then passes generated token to Release Please | Referenced long-lived private key as action input; generated short-lived release token; publishing OIDC | Main revision, token action, OpenAI API, GitHub, RubyGems | Repository/ref conditions, separate named environments, least privilege, pinned actions, generated token handoff, OIDC only for publishing | `.github/workflows/live-smoke.yml:17-92`, `.github/workflows/create-releases.yml:17-79` |
|
|
62
|
+
| Examples E2E workflow | Live-example API credential and uploaded reports | Manual dispatch; main/repository condition; named `ci` environment; `OPENAI_API_KEY` secret reference | Credential only in the live-example step; allowlisted `report.json` and `summary.md` under runner temp | OpenAI API and GitHub artifact readers | `permissions: {}`, `contents: read`, checkout without persisted credentials, main/repository condition, allowlisted reports, 14-day retention, focused confidentiality/isolation tests | `.github/workflows/examples-e2e.yml:1-43`, `test/scripts/examples_e2e_test.rb:202-234`, `test/scripts/examples_e2e_test.rb:306-327` |
|
|
63
|
+
|
|
64
|
+
## 2. Threat Model, Trust Boundaries, and Assumptions
|
|
65
|
+
|
|
66
|
+
Protected assets include caller API/admin/provider credentials, workload subject
|
|
67
|
+
tokens and exchanged access tokens, webhook secrets, X.509 private-key
|
|
68
|
+
material, uploaded file contents, request destinations and headers, parsed
|
|
69
|
+
response integrity, diagnostic confidentiality, GitHub App private-key
|
|
70
|
+
material, and release/publishing integrity.
|
|
71
|
+
|
|
72
|
+
Important boundaries:
|
|
73
|
+
|
|
74
|
+
- Caller-controlled parameters, headers, files, base URLs, callbacks, loggers,
|
|
75
|
+
and custom transports cross into SDK request construction. These remain
|
|
76
|
+
caller authority inside the embedding process; the SDK is not a sandbox
|
|
77
|
+
(`lib/openai/http_client.rb:138-142`,
|
|
78
|
+
`lib/openai/internal/transport/base_client.rb:293-339`).
|
|
79
|
+
- Configured origin to redirect destination is a network boundary. Credential
|
|
80
|
+
headers must not reach a different origin, request bodies must not cross
|
|
81
|
+
origins, and HTTPS must not downgrade to HTTP
|
|
82
|
+
(`lib/openai/internal/transport/base_client.rb:153-215`).
|
|
83
|
+
- The default Net::HTTP implementation is the ordinary REST TLS and connection-
|
|
84
|
+
reuse boundary. It derives TLS from the requested scheme, installs default
|
|
85
|
+
trust roots, keys pools by origin, requires configurators to leave new
|
|
86
|
+
connections unstarted, requires validators to leave previously unstarted
|
|
87
|
+
connections unstarted, and checks `use_ssl?` against the URL scheme each time
|
|
88
|
+
(`lib/openai/net_http_client.rb:55-71`,
|
|
89
|
+
`lib/openai/net_http_client.rb:139-205`,
|
|
90
|
+
`lib/openai/net_http_client.rb:241-345`).
|
|
91
|
+
- API, SSE, and WebSocket bytes cross parser/type-conversion boundaries. They
|
|
92
|
+
are parsed as data, not evaluated as code
|
|
93
|
+
(`lib/openai/internal/stream.rb:21-60`,
|
|
94
|
+
`lib/openai/helpers/realtime/connection.rb:61-84`).
|
|
95
|
+
- Raw webhook payloads and headers cross an authenticity boundary. `unwrap`
|
|
96
|
+
verifies freshness and HMAC before parsing or coercion
|
|
97
|
+
(`lib/openai/resources/webhooks.rb:15-23`,
|
|
98
|
+
`lib/openai/resources/webhooks.rb:75-119`).
|
|
99
|
+
- Built-in subject-token sources, the generic token issuer, and Azure/Bedrock
|
|
100
|
+
endpoints are credential-brokering boundaries. GCP acquisition uses the fixed
|
|
101
|
+
plaintext metadata host/path and metadata header with configurable audience
|
|
102
|
+
and timeout; Azure managed-identity acquisition uses the fixed plaintext IMDS
|
|
103
|
+
endpoint and metadata header with configurable resource, identity selectors,
|
|
104
|
+
API version, and timeout, then parses the returned JSON token. Both metadata
|
|
105
|
+
fetches inherit Net::HTTP environment-proxy routing, so a caller-owned proxy
|
|
106
|
+
can mediate the plaintext request/response and fixed URLs alone do not prove
|
|
107
|
+
direct source binding. Kubernetes acquisition reads its configured/default
|
|
108
|
+
token path and maps file failures. Credentials must remain bound to their
|
|
109
|
+
intended source, proxy, issuer, and destination
|
|
110
|
+
(`lib/openai/auth/subject_token_providers/gcp_id_token_provider.rb:9-67`,
|
|
111
|
+
`lib/openai/auth/subject_token_providers/azure_managed_identity_token_provider.rb:9-92`,
|
|
112
|
+
`lib/openai/auth/subject_token_providers/k8s_service_account_token_provider.rb:9-37`,
|
|
113
|
+
`lib/openai/auth/workload_identity_auth.rb:280-330`,
|
|
114
|
+
`lib/openai/providers/azure.rb:141-147`,
|
|
115
|
+
`lib/openai/providers/bedrock.rb:398-427`).
|
|
116
|
+
- X.509 workload identity is a separate credential boundary: the exchange uses
|
|
117
|
+
the fixed mTLS issuer while API requests use the selected transport's attested
|
|
118
|
+
matching mTLS origin. The certificate identity and exchanged bearer token
|
|
119
|
+
remain detached; controls enforce the exact issuer endpoint and approved API
|
|
120
|
+
origin/header/TLS/proxy/no-redirect policy
|
|
121
|
+
(`lib/openai/client.rb:616-644`,
|
|
122
|
+
`lib/openai/client.rb:739-756`,
|
|
123
|
+
`test/openai/auth/x509_client_test.rb:16-34`,
|
|
124
|
+
`lib/openai/auth/x509_transport.rb:5-10`,
|
|
125
|
+
`lib/openai/auth/x509_transport.rb:174-315`).
|
|
126
|
+
- Authenticated Realtime handshakes are a transport boundary distinct from
|
|
127
|
+
event parsing. Bearer credentials, caller-selected WebSocket destinations,
|
|
128
|
+
TLS, proxy credentials, and traces must remain bound to their intended
|
|
129
|
+
origin and diagnostic audience
|
|
130
|
+
(`lib/openai/helpers/realtime/client_extension.rb:167-280`,
|
|
131
|
+
`lib/openai/helpers/realtime/transports/async_websocket.rb:185-224`,
|
|
132
|
+
`lib/openai/helpers/realtime/transports/async_websocket.rb:294-378`).
|
|
133
|
+
- Multipart upload serialization is a file-path and header-integrity boundary.
|
|
134
|
+
`FilePart` validates media types and reduces explicit or inferred filenames to
|
|
135
|
+
basenames; the encoder again basenames raw paths and escapes quote, backslash,
|
|
136
|
+
CR, and LF header-parameter characters before bytes reach the API origin
|
|
137
|
+
(`lib/openai/file_part.rb:33-43`,
|
|
138
|
+
`lib/openai/file_part.rb:110-119`,
|
|
139
|
+
`lib/openai/internal/util.rb:533-565`).
|
|
140
|
+
- Runtime/API data crossing into logs, exceptions, retained metadata objects, or
|
|
141
|
+
CI artifacts is a sensitive sink boundary. Logging redacts or omits sensitive
|
|
142
|
+
bodies; derived HTTP status messages are bounded and sanitized when no
|
|
143
|
+
top-level scalar or explicit message is supplied; top-level scalar and
|
|
144
|
+
explicit SSE error messages remain remote data; Realtime connection errors
|
|
145
|
+
redact call IDs from URLs; response metadata omits retained bodies from
|
|
146
|
+
inspection and serialization
|
|
147
|
+
(`lib/openai/internal/logging.rb:299-343`,
|
|
148
|
+
`lib/openai/errors.rb:259-409`,
|
|
149
|
+
`lib/openai/internal/stream.rb:35-40`,
|
|
150
|
+
`lib/openai/helpers/realtime/errors.rb:26-70`,
|
|
151
|
+
`lib/openai/http_client.rb:46-79`,
|
|
152
|
+
`CONTRIBUTING.md:42-50`).
|
|
153
|
+
- Candidate PR code reaching sensitive live/release credentials or repository
|
|
154
|
+
write/publishing authority is a genuine boundary
|
|
155
|
+
(`.github/workflows/live-smoke.yml:17-92`,
|
|
156
|
+
`.github/workflows/examples-e2e.yml:1-43`,
|
|
157
|
+
`.github/workflows/create-releases.yml:17-79`).
|
|
158
|
+
- The release GitHub App private key is distinct from the generated app token.
|
|
159
|
+
The main/repository-gated release job passes the referenced private key only
|
|
160
|
+
to a pinned token action, which mints the narrower token passed to Release
|
|
161
|
+
Please; disclosure of the private key can outlive one generated token
|
|
162
|
+
(`.github/workflows/create-releases.yml:17-46`).
|
|
163
|
+
- Candidate source and generated SARIF crossing into CodeQL result upload are a
|
|
164
|
+
separate PR-adjacent write boundary. The workflow scopes the token to
|
|
165
|
+
`security-events: write` plus read-only metadata/content, uses pinned actions
|
|
166
|
+
without persisted checkout credentials, and validates that SARIF exists and
|
|
167
|
+
has no findings before the job succeeds
|
|
168
|
+
(`.github/workflows/codeql.yml:3-28`,
|
|
169
|
+
`.github/workflows/codeql.yml:37-53`,
|
|
170
|
+
`.github/workflows/codeql.yml:55-96`).
|
|
171
|
+
- The manually dispatched Examples E2E job is a separate live-data boundary:
|
|
172
|
+
its main/repository-gated job references `OPENAI_API_KEY`, runs examples, and
|
|
173
|
+
always uploads only two allowlisted reports. Tests enforce report
|
|
174
|
+
confidentiality and workflow isolation
|
|
175
|
+
(`.github/workflows/examples-e2e.yml:1-43`,
|
|
176
|
+
`test/scripts/examples_e2e_test.rb:202-234`,
|
|
177
|
+
`test/scripts/examples_e2e_test.rb:306-327`).
|
|
178
|
+
- Candidate-associated `workflow_run` metadata and Git objects crossing into
|
|
179
|
+
the trusted Castiron publisher are a genuine write-capable boundary. The
|
|
180
|
+
status path must preserve run-identity and exact-head/base checks; successful
|
|
181
|
+
report comments must remain bound to the trusted report artifact. The
|
|
182
|
+
fallback failure-comment path has narrower main-sourced event/path,
|
|
183
|
+
current-head, and monotonic-replacement checks that scans must assess
|
|
184
|
+
independently
|
|
185
|
+
(`.github/workflows/castiron-custom-code-comment.yml:34-98`,
|
|
186
|
+
`.github/workflows/castiron-custom-code-comment.yml:116-171`,
|
|
187
|
+
`.github/workflows/castiron-custom-code-comment.yml:174-237`).
|
|
188
|
+
- Dependency declarations and locks cross a supply-chain boundary when Bundler
|
|
189
|
+
or RubyGems fetches and executes selected package code in CI or consumer
|
|
190
|
+
applications. A changed source, Git revision, transitive gem, native
|
|
191
|
+
extension, or install/build script needs explicit dependency review. The root
|
|
192
|
+
contributor/CI bundle currently selects `syntax_tree-rbs` from GitHub branch
|
|
193
|
+
`main` and locks revision `247832988a850b8df050cf207f652872fda49973`; the
|
|
194
|
+
Bedrock CI job separately selects `aws-sdk-core` through
|
|
195
|
+
`gemfiles/bedrock.gemfile` and its lock
|
|
196
|
+
(`AGENTS.md:39-42`, `Gemfile:3-15`, `Gemfile.lock:1-20`,
|
|
197
|
+
`gemfiles/bedrock.gemfile:1-5`, `gemfiles/bedrock.gemfile.lock:1-58`,
|
|
198
|
+
`.github/workflows/ci-checks.yml:124-144`,
|
|
199
|
+
`openai.gemspec:18-48`,
|
|
200
|
+
`docs/Gemfile:1-8`, `docs/Gemfile.lock:1-21`).
|
|
201
|
+
|
|
202
|
+
Checked-in executable source has repository-code authority. Reviewed tracked
|
|
203
|
+
examples, tests, fixtures, Rake tasks, build scripts, generators, and other
|
|
204
|
+
checkout files are intentionally executable by local development and ordinary
|
|
205
|
+
PR CI. A contributor who can modify those tracked files does not gain a new
|
|
206
|
+
privilege merely because CI executes them. Do not report that execution alone
|
|
207
|
+
as a security finding.
|
|
208
|
+
|
|
209
|
+
That rule does not suppress a real finding when independently mutable
|
|
210
|
+
lower-trust input crosses a parser/evaluator boundary, untrusted runtime/API/
|
|
211
|
+
network data reaches a sensitive sink, candidate code can reach sensitive
|
|
212
|
+
secrets or write/publishing authority, or shipped production code violates a
|
|
213
|
+
security invariant.
|
|
214
|
+
|
|
215
|
+
Assumptions:
|
|
216
|
+
|
|
217
|
+
- The embedding application owns its local filesystem, network, process,
|
|
218
|
+
callback, proxy, and credential authority.
|
|
219
|
+
- Custom transports, token providers, and loggers are caller-owned extension
|
|
220
|
+
points, not less-trusted sandboxed plugins.
|
|
221
|
+
- Azure and Bedrock custom HTTP endpoints are caller-selected; HTTPS outside
|
|
222
|
+
local/private testing is a caller obligation where the provider permits HTTP.
|
|
223
|
+
- External branch protection, environment approval rules, secret configuration,
|
|
224
|
+
and RubyGems trusted-publisher bindings are not proven by repository files.
|
|
225
|
+
- Large JSON bodies and SSE events are supported API contracts; arbitrary fixed
|
|
226
|
+
rejection limits are not an acceptable security fix.
|
|
227
|
+
|
|
228
|
+
## 3. Attack Surface, Mitigations, and Attacker Stories
|
|
229
|
+
|
|
230
|
+
The following are hypotheses and review guidance, not confirmed findings.
|
|
231
|
+
|
|
232
|
+
| Priority | Scenario and capability gain | Prerequisites | Impact | Existing controls | Mitigation | Evidence |
|
|
233
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
234
|
+
| High | Redirect, TLS, or pooled-connection confusion sends credentials or a body to an unintended origin | Remote endpoint controls redirect or caller/provider origin handling is bypassed, or default connection validation/pooling regresses | Credential or payload disclosure | Cross-origin credential stripping, body rejection, HTTPS downgrade rejection, provider origin validation, default trust roots, origin-keyed pools, return-time unstarted postconditions for new connections, scheme-matched `use_ssl?` checks | Preserve origin, redirect, TLS, and pool controls | `lib/openai/internal/transport/base_client.rb:153-215`, `lib/openai/providers/azure.rb:141-147`, `lib/openai/net_http_client.rb:55-71`, `lib/openai/net_http_client.rb:139-205`, `lib/openai/net_http_client.rb:241-345` |
|
|
235
|
+
| High | Candidate PR code reaches live/release credentials or publishing authority | Privileged workflow executes candidate code or exposes secrets/write tokens | Credential theft or release compromise | Main-only conditions, named environments, least-privilege permissions, trusted publisher separation | Keep candidate and live/release workflows isolated | `.github/workflows/live-smoke.yml:17-92`, `.github/workflows/create-releases.yml:17-79` |
|
|
236
|
+
| High | Release GitHub App private key reaches an unintended action, log, or artifact | Main/repository release gate or pinned token-action input boundary regresses | Attacker can mint GitHub App tokens beyond one workflow token lifetime | Main/repository condition, named release environment, empty job permissions, pinned token action, generated token passed separately to Release Please | Keep the private key confined to token minting and preserve generated-token handoff | `.github/workflows/create-releases.yml:17-46` |
|
|
237
|
+
| Medium | Candidate source or generated SARIF influences the write-capable CodeQL upload outside its intended code-scanning result path | PR-adjacent CodeQL action or token scoping regresses | Forged or unintended code-scanning results | Top-level empty permissions; scoped `security-events: write`; read-only content/metadata; pinned actions; no persisted checkout credentials; SARIF existence/shape/finding checks | Preserve least privilege and keep upload input limited to CodeQL output | `.github/workflows/codeql.yml:3-28`, `.github/workflows/codeql.yml:37-53`, `.github/workflows/codeql.yml:55-96` |
|
|
238
|
+
| High | Candidate-associated workflow metadata or Git objects influence the write-capable Castiron publisher without the path-specific trusted validation | Attacker controls a PR head or associated workflow metadata and a status, successful-comment, or fallback-comment check regresses | Forged or stale commit statuses and PR comments | Statuses use main-sourced recomputation plus run/head/base checks; successful comments use a trusted report artifact; fallback comments rely on main-sourced event/path, current-head, and monotonic-replacement checks | Preserve each path's distinct recomputation, freshness, artifact-binding, or fallback-isolation checks before its write | `.github/workflows/castiron-custom-code-comment.yml:34-98`, `.github/workflows/castiron-custom-code-comment.yml:116-171`, `.github/workflows/castiron-custom-code-comment.yml:174-237` |
|
|
239
|
+
| High | A dependency declaration or lockfile selects malicious package code, native extension, or install/build script | Lower-trust change alters a source, Git revision, transitive dependency, or executable package hook that CI or a consumer installs; the contributor/CI bundle currently includes Git-sourced `syntax_tree-rbs` locked at `247832988a850b8df050cf207f652872fda49973`; Bedrock CI separately installs its `aws-sdk-core` bundle | Code execution in CI or consumer applications | Reviewed lockfiles and dependency policy; ordinary and Bedrock CI execute selected dependencies with runner authority | Review direct/transitive dependencies, sources, locked revisions, native extensions, and scripts before acceptance | `AGENTS.md:39-42`, `Gemfile:3-15`, `Gemfile.lock:1-20`, `gemfiles/bedrock.gemfile:1-5`, `gemfiles/bedrock.gemfile.lock:1-58`, `.github/workflows/ci-checks.yml:124-144`, `openai.gemspec:18-48`, `docs/Gemfile:1-8`, `docs/Gemfile.lock:1-21` |
|
|
240
|
+
| High | Built-in subject-token acquisition or exchange sends a token to an unintended source, proxy, issuer, or origin | Lower-trust configuration, caller-owned environment proxy, local token path, metadata response, or endpoint handling is bypassed | Credential disclosure or forged authentication | Fixed GCP metadata host/path/header with configurable audience/timeout; fixed Azure IMDS endpoint/header with configurable resource/selectors/API version/timeout and JSON token extraction; inherited Net::HTTP environment-proxy routing remains caller-owned; configured/default Kubernetes path with mapped failures; fixed/default issuer paths, origin checks, X.509 destination restrictions | Preserve strict source, proxy, destination, timeout, redirect, and response-parsing handling | `lib/openai/auth/subject_token_providers/gcp_id_token_provider.rb:9-67`, `lib/openai/auth/subject_token_providers/azure_managed_identity_token_provider.rb:9-92`, `lib/openai/auth/subject_token_providers/k8s_service_account_token_provider.rb:9-37`, `lib/openai/auth/workload_identity_auth.rb:303-330`, `lib/openai/providers/bedrock.rb:398-427` |
|
|
241
|
+
| High | Realtime bearer credentials, proxy credentials, or trace data reach an unintended destination or audience | Lower-trust destination, proxy, TLS, or tracing behavior bypasses handshake controls | Credential disclosure or authenticated connection to the wrong peer | Base-URL/provider restrictions, proxy-header stripping, WSS verification, proxy handling, trace redaction | Preserve destination/TLS/proxy/redaction controls independently from event parsing | `lib/openai/helpers/realtime/client_extension.rb:167-280`, `lib/openai/helpers/realtime/transports/async_websocket.rb:185-224`, `lib/openai/helpers/realtime/transports/async_websocket.rb:294-378` |
|
|
242
|
+
| Medium | Malformed or adversarial API/SSE/WebSocket data causes sensitive leakage, type confusion, or availability failure | Victim consumes untrusted remote data | Data exposure, incorrect application behavior, or DoS | JSON parsing, typed coercion, protocol errors, safe unknown events | Keep parsers data-only, incremental where applicable, and redact errors/logs | `lib/openai/internal/stream.rb:21-60`, `lib/openai/helpers/realtime/connection.rb:61-84` |
|
|
243
|
+
| Medium | Multipart filename, content type, or field name reaches headers without normalization | Caller-supplied path or header parameter crosses multipart serialization | Absolute local path disclosure or multipart header injection | Media-type validation, basename reduction for `FilePart` and raw path/IO values, quote/backslash escaping, CR/LF removal | Preserve basename and header-parameter controls before upload | `lib/openai/file_part.rb:33-43`, `lib/openai/file_part.rb:110-119`, `lib/openai/internal/util.rb:533-565` |
|
|
244
|
+
| Medium | Forged webhook, or replay of a captured valid webhook, becomes an accepted typed event | For forgery, attacker lacks the webhook secret; for replay, attacker can resend a valid signed payload within the tolerance window | Application accepts an unauthentic or repeated event | Required headers, HMAC, and timing-safe comparison reject forgery; timestamp validation bounds replay age but does not deduplicate `webhook-id` | Verify before parsing; callers that need replay prevention must deduplicate `webhook-id` | `lib/openai/resources/webhooks.rb:15-119` |
|
|
245
|
+
| Medium | Sensitive runtime or live-example data leaks through diagnostics, exceptions, retained metadata, or artifacts | Untrusted data reaches logger/error/object serialization or a live-example report | Credential, prompt, response, file, header, or call-identifier disclosure | Redacted headers/queries; unsafe body categories omitted; derived HTTP errors are bounded/sanitized when no top-level scalar or explicit message is supplied; top-level scalar and explicit SSE messages remain remote data; Realtime URL call-ID redaction; retained bodies omitted from inspect/YAML/Marshal; Examples E2E reports are allowlisted and tested for sensitive output | Preserve redaction, bounded derived errors, metadata serialization controls, and allowlisted artifacts; treat preserved remote messages as remote data | `lib/openai/internal/logging.rb:299-343`, `lib/openai/errors.rb:259-409`, `lib/openai/internal/stream.rb:35-40`, `lib/openai/helpers/realtime/errors.rb:26-70`, `lib/openai/http_client.rb:46-79`, `.github/workflows/examples-e2e.yml:29-43`, `test/scripts/examples_e2e_test.rb:202-234`, `test/scripts/examples_e2e_test.rb:306-327`, `CONTRIBUTING.md:42-50` |
|
|
246
|
+
| Not a finding by itself | Contributor changes a tracked test/example/fixture/build script and ordinary CI runs it | Contributor already controls candidate checkout code | No new authority | Read-only ordinary CI without referenced secrets or write authority | Require a separate privilege or boundary violation before reporting | `.github/workflows/ci.yml:18-32`, `.github/workflows/ci-checks.yml:100-120` |
|
|
247
|
+
|
|
248
|
+
## 4. Severity Calibration
|
|
249
|
+
|
|
250
|
+
- Critical: unauthorized release/publishing compromise, protected credential
|
|
251
|
+
theft with broad impact, or a cross-account/tenant boundary failure.
|
|
252
|
+
- High: realistic credential disclosure, authenticated request redirection,
|
|
253
|
+
protected CI/release boundary bypass, or forged authentication with meaningful
|
|
254
|
+
account impact.
|
|
255
|
+
- Medium: production-reachable parser, webhook, retry, logging, or availability
|
|
256
|
+
failures with bounded impact or meaningful prerequisites.
|
|
257
|
+
- Low: narrowly reachable confidentiality/integrity defects with limited impact
|
|
258
|
+
and strong mitigating prerequisites.
|
|
259
|
+
- Out of scope without additional authority: ordinary execution of
|
|
260
|
+
contributor-controlled tracked checkout code, caller-chosen dangerous
|
|
261
|
+
configuration, caller-owned callbacks/custom transports, purely local
|
|
262
|
+
self-effects, and hypothetical stories without a realistic lower-trust route
|
|
263
|
+
to a sensitive operation.
|
|
264
|
+
|
|
265
|
+
Severity depends on the attacker's initial authority, the new capability gained,
|
|
266
|
+
reachability, deployment prerequisites, and effective controls. Missing evidence
|
|
267
|
+
is an explicit uncertainty, not proof that a control succeeds or fails.
|
data/lib/openai/client.rb
CHANGED
|
@@ -86,6 +86,9 @@ module OpenAI
|
|
|
86
86
|
# @return [OpenAI::Resources::VectorStores]
|
|
87
87
|
attr_reader :vector_stores
|
|
88
88
|
|
|
89
|
+
# @return [OpenAI::Resources::Safety]
|
|
90
|
+
attr_reader :safety
|
|
91
|
+
|
|
89
92
|
# @return [OpenAI::Resources::Webhooks]
|
|
90
93
|
attr_reader :webhooks
|
|
91
94
|
|
|
@@ -806,6 +809,7 @@ module OpenAI
|
|
|
806
809
|
@fine_tuning = OpenAI::Resources::FineTuning.new(client: self)
|
|
807
810
|
@graders = OpenAI::Resources::Graders.new(client: self)
|
|
808
811
|
@vector_stores = OpenAI::Resources::VectorStores.new(client: self)
|
|
812
|
+
@safety = OpenAI::Resources::Safety.new(client: self)
|
|
809
813
|
@webhooks = OpenAI::Resources::Webhooks.new(client: self)
|
|
810
814
|
@beta = OpenAI::Resources::Beta.new(client: self)
|
|
811
815
|
@batches = OpenAI::Resources::Batches.new(client: self)
|
|
@@ -133,6 +133,12 @@ module OpenAI
|
|
|
133
133
|
index_choice_snapshot!(choice_snapshot)
|
|
134
134
|
end
|
|
135
135
|
|
|
136
|
+
@current_completion_snapshot = completion_snapshot
|
|
137
|
+
|
|
138
|
+
chunk.choices.each do |choice|
|
|
139
|
+
handle_finish_reason(choice.finish_reason, completion_snapshot) if choice.finish_reason
|
|
140
|
+
end
|
|
141
|
+
|
|
136
142
|
return completion_snapshot
|
|
137
143
|
end
|
|
138
144
|
|
|
@@ -144,6 +150,7 @@ module OpenAI
|
|
|
144
150
|
|
|
145
151
|
completion_snapshot.usage = chunk.usage if chunk.usage
|
|
146
152
|
completion_snapshot.system_fingerprint = chunk.system_fingerprint if chunk.system_fingerprint
|
|
153
|
+
completion_snapshot.service_tier = chunk.service_tier if chunk.service_tier
|
|
147
154
|
|
|
148
155
|
completion_snapshot
|
|
149
156
|
end
|
|
@@ -332,8 +339,8 @@ module OpenAI
|
|
|
332
339
|
|
|
333
340
|
if choice_snapshot.logprobs.nil?
|
|
334
341
|
choice_snapshot.logprobs = OpenAI::Chat::ChatCompletionChunk::Choice::Logprobs.new(
|
|
335
|
-
content: choice_logprobs.content,
|
|
336
|
-
refusal: choice_logprobs.refusal
|
|
342
|
+
content: choice_logprobs.content&.dup,
|
|
343
|
+
refusal: choice_logprobs.refusal&.dup
|
|
337
344
|
)
|
|
338
345
|
else
|
|
339
346
|
if choice_logprobs.content
|
|
@@ -527,7 +534,7 @@ module OpenAI
|
|
|
527
534
|
model: data[:model],
|
|
528
535
|
choices: choices,
|
|
529
536
|
usage: data[:usage],
|
|
530
|
-
system_fingerprint:
|
|
537
|
+
system_fingerprint: data[:system_fingerprint],
|
|
531
538
|
service_tier: data[:service_tier]
|
|
532
539
|
}
|
|
533
540
|
)
|
|
@@ -647,11 +654,6 @@ module OpenAI
|
|
|
647
654
|
|
|
648
655
|
if choice_snapshot.finish_reason
|
|
649
656
|
events.concat(content_done_events(choice_snapshot, response_format))
|
|
650
|
-
|
|
651
|
-
if @current_tool_call_index && !@done_tool_calls.include?(@current_tool_call_index)
|
|
652
|
-
event = tool_done_event(tool_calls_by_index, @current_tool_call_index)
|
|
653
|
-
events << event if event
|
|
654
|
-
end
|
|
655
657
|
end
|
|
656
658
|
|
|
657
659
|
Array(choice_chunk.delta.tool_calls).each do |tool_call|
|
|
@@ -667,6 +669,13 @@ module OpenAI
|
|
|
667
669
|
@current_tool_call_index = tool_call.index
|
|
668
670
|
end
|
|
669
671
|
|
|
672
|
+
if choice_snapshot.finish_reason &&
|
|
673
|
+
@current_tool_call_index &&
|
|
674
|
+
!@done_tool_calls.include?(@current_tool_call_index)
|
|
675
|
+
event = tool_done_event(tool_calls_by_index, @current_tool_call_index)
|
|
676
|
+
events << event if event
|
|
677
|
+
end
|
|
678
|
+
|
|
670
679
|
events
|
|
671
680
|
end
|
|
672
681
|
|
|
@@ -190,12 +190,7 @@ module OpenAI
|
|
|
190
190
|
def accumulate_event(event:, current_snapshot:)
|
|
191
191
|
if current_snapshot.nil?
|
|
192
192
|
if event.is_a?(OpenAI::Models::Responses::ResponseCreatedEvent)
|
|
193
|
-
|
|
194
|
-
# This ensures proper type validation and prevents shared object references.
|
|
195
|
-
return OpenAI::Internal::Type::Converter.coerce(
|
|
196
|
-
OpenAI::Models::Responses::Response,
|
|
197
|
-
event.response
|
|
198
|
-
)
|
|
193
|
+
return isolated_value(event.response)
|
|
199
194
|
end
|
|
200
195
|
|
|
201
196
|
unless @resumed
|
|
@@ -212,12 +207,12 @@ module OpenAI
|
|
|
212
207
|
|
|
213
208
|
case event
|
|
214
209
|
when OpenAI::Models::Responses::ResponseOutputItemAddedEvent
|
|
215
|
-
current_snapshot.output.push(event.item)
|
|
210
|
+
current_snapshot.output.push(isolated_value(event.item))
|
|
216
211
|
|
|
217
212
|
when OpenAI::Models::Responses::ResponseContentPartAddedEvent
|
|
218
213
|
output = current_snapshot.output[event.output_index]
|
|
219
214
|
if output && output.type == :message
|
|
220
|
-
output.content.push(event.part)
|
|
215
|
+
output.content.push(isolated_value(event.part))
|
|
221
216
|
current_snapshot.output[event.output_index] = output
|
|
222
217
|
end
|
|
223
218
|
|
|
@@ -248,6 +243,16 @@ module OpenAI
|
|
|
248
243
|
|
|
249
244
|
private
|
|
250
245
|
|
|
246
|
+
# Materialize fresh containers before snapshot accumulation mutates them.
|
|
247
|
+
# Coercing an already-typed model returns it unchanged, so first use the
|
|
248
|
+
# model's recursive raw representation. Unknown union fallbacks stay raw.
|
|
249
|
+
def isolated_value(value)
|
|
250
|
+
raw = OpenAI::Internal::Type::BaseModel.recursively_to_h(value, convert: false)
|
|
251
|
+
return raw unless value.is_a?(OpenAI::Internal::Type::BaseModel)
|
|
252
|
+
|
|
253
|
+
OpenAI::Internal::Type::Converter.coerce(value.class, raw)
|
|
254
|
+
end
|
|
255
|
+
|
|
251
256
|
def assert_type(object, expected_type)
|
|
252
257
|
return if object && object.type == expected_type
|
|
253
258
|
actual_type = object ? object.type : "nil"
|
|
@@ -24,10 +24,10 @@ module OpenAI
|
|
|
24
24
|
# @return [Hash{Symbol=>Object}]
|
|
25
25
|
def to_json_schema_inner(state:)
|
|
26
26
|
OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.cache_def!(state, type: self) do
|
|
27
|
-
state.fetch(:path)
|
|
27
|
+
new_state = {**state, path: [*state.fetch(:path), "[]"]}
|
|
28
28
|
items = OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.to_json_schema_inner(
|
|
29
29
|
item_type,
|
|
30
|
-
state:
|
|
30
|
+
state: new_state
|
|
31
31
|
)
|
|
32
32
|
items = OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.to_nilable(items) if nilable?
|
|
33
33
|
OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.assoc_meta!(items, meta: @meta)
|
|
@@ -32,13 +32,27 @@ module OpenAI
|
|
|
32
32
|
OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.cache_def!(state, type: self) do
|
|
33
33
|
path = state.fetch(:path)
|
|
34
34
|
properties = field_values.to_h do |field|
|
|
35
|
-
api_name, type, nilable, meta = field.fetch_values(:api_name, :type, :nilable, :meta)
|
|
35
|
+
api_name, type, nilable, const, meta = field.fetch_values(:api_name, :type, :nilable, :const, :meta)
|
|
36
36
|
new_state = {**state, path: [*path, ".#{api_name}"]}
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
literal_const = case const
|
|
39
|
+
when Integer, TrueClass, FalseClass
|
|
40
|
+
true
|
|
41
|
+
when Float
|
|
42
|
+
const.finite?
|
|
43
|
+
else
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
schema = if literal_const
|
|
48
|
+
{const: const}
|
|
49
|
+
else
|
|
50
|
+
OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.to_json_schema_inner(
|
|
51
|
+
type,
|
|
52
|
+
state: new_state
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
42
56
|
schema = OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.to_nilable(schema) if nilable
|
|
43
57
|
OpenAI::Helpers::StructuredOutput::JsonSchemaConverter.assoc_meta!(
|
|
44
58
|
schema,
|
|
@@ -67,7 +67,10 @@ module OpenAI
|
|
|
67
67
|
}
|
|
68
68
|
)
|
|
69
69
|
in {
|
|
70
|
-
response_format: {
|
|
70
|
+
response_format: {
|
|
71
|
+
type: :json_schema | "json_schema",
|
|
72
|
+
json_schema: OpenAI::StructuredOutput::JsonSchemaConverter => model
|
|
73
|
+
}
|
|
71
74
|
}
|
|
72
75
|
parsed.fetch(:response_format).update(
|
|
73
76
|
json_schema: {
|
|
@@ -78,7 +81,7 @@ module OpenAI
|
|
|
78
81
|
)
|
|
79
82
|
in {
|
|
80
83
|
response_format: {
|
|
81
|
-
type: :json_schema,
|
|
84
|
+
type: :json_schema | "json_schema",
|
|
82
85
|
json_schema: {schema: OpenAI::StructuredOutput::JsonSchemaConverter => model}
|
|
83
86
|
}
|
|
84
87
|
}
|
|
@@ -124,7 +127,7 @@ module OpenAI
|
|
|
124
127
|
return [] if tools.nil?
|
|
125
128
|
|
|
126
129
|
tools.map do |tool|
|
|
127
|
-
next tool unless tool[:type]
|
|
130
|
+
next tool unless [:function, "function"].include?(tool[:type])
|
|
128
131
|
|
|
129
132
|
function_name = tool.dig(:function, :name)
|
|
130
133
|
model = tool_models[function_name]
|