openai 0.106.0 → 0.107.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 +51 -0
- data/README.md +1 -1
- data/lib/openai/helpers/live/websocket.rb +12 -141
- data/lib/openai/helpers/local_audio/process.rb +13 -3
- data/lib/openai/helpers/models/embedding.rb +42 -0
- data/lib/openai/helpers/realtime/connection.rb +22 -5
- data/lib/openai/helpers/realtime/recovery.rb +11 -5
- data/lib/openai/helpers/realtime/translation.rb +37 -169
- data/lib/openai/helpers/realtime/unknown_server_event.rb +5 -12
- data/lib/openai/helpers/responses_websocket/connection.rb +56 -35
- data/lib/openai/helpers/responses_websocket/unknown_server_event.rb +2 -14
- data/lib/openai/helpers/websocket/async_websocket_transport.rb +8 -6
- data/lib/openai/helpers/websocket/client_request.rb +1 -1
- data/lib/openai/helpers/websocket/connection.rb +21 -10
- data/lib/openai/helpers/websocket/connection_manager.rb +2 -1
- data/lib/openai/helpers/websocket/protocol.rb +160 -0
- data/lib/openai/helpers/websocket/typed_connection.rb +22 -16
- data/lib/openai/helpers/websocket/typed_resource.rb +15 -0
- data/lib/openai/internal/transport/base_client.rb +1 -1
- data/lib/openai/models/admin/organization/usage_audio_speeches_response.rb +12 -1
- data/lib/openai/models/admin/organization/usage_audio_transcriptions_response.rb +12 -1
- data/lib/openai/models/admin/organization/usage_code_interpreter_sessions_response.rb +12 -1
- data/lib/openai/models/admin/organization/usage_completions_response.rb +12 -1
- data/lib/openai/models/admin/organization/usage_costs_response.rb +12 -1
- data/lib/openai/models/admin/organization/usage_decisions_response.rb +12 -1
- data/lib/openai/models/admin/organization/usage_embeddings_response.rb +12 -1
- data/lib/openai/models/admin/organization/usage_file_search_calls_response.rb +12 -1
- data/lib/openai/models/admin/organization/usage_images_response.rb +12 -1
- data/lib/openai/models/admin/organization/usage_moderations_response.rb +12 -1
- data/lib/openai/models/admin/organization/usage_vector_stores_response.rb +12 -1
- data/lib/openai/models/admin/organization/usage_web_search_calls_response.rb +12 -1
- data/lib/openai/models/beta/beta_response_input_item.rb +10 -1
- data/lib/openai/models/responses/response_input_item.rb +10 -1
- data/lib/openai/version.rb +1 -1
- data/lib/openai.rb +1 -0
- data/rbi/openai/client.rbi +1 -8
- data/rbi/openai/helpers/realtime/translation.rbi +0 -5
- data/rbi/openai/helpers/realtime/unknown_server_event.rbi +6 -0
- data/rbi/openai/helpers/websocket.rbi +44 -6
- data/rbi/openai/internal/transport/base_client.rbi +2 -6
- data/rbi/openai/models/admin/organization/usage_audio_speeches_response.rbi +12 -0
- data/rbi/openai/models/admin/organization/usage_audio_transcriptions_response.rbi +12 -0
- data/rbi/openai/models/admin/organization/usage_code_interpreter_sessions_response.rbi +12 -0
- data/rbi/openai/models/admin/organization/usage_completions_response.rbi +12 -0
- data/rbi/openai/models/admin/organization/usage_costs_response.rbi +12 -0
- data/rbi/openai/models/admin/organization/usage_decisions_response.rbi +12 -0
- data/rbi/openai/models/admin/organization/usage_embeddings_response.rbi +12 -0
- data/rbi/openai/models/admin/organization/usage_file_search_calls_response.rbi +12 -0
- data/rbi/openai/models/admin/organization/usage_images_response.rbi +12 -0
- data/rbi/openai/models/admin/organization/usage_moderations_response.rbi +12 -0
- data/rbi/openai/models/admin/organization/usage_vector_stores_response.rbi +12 -0
- data/rbi/openai/models/admin/organization/usage_web_search_calls_response.rbi +12 -0
- data/rbi/openai/models/beta/beta_response_input_item.rbi +14 -1
- data/rbi/openai/models/responses/response_input_item.rbi +10 -1
- data/realtime.md +22 -3
- data/sig/openai/client.rbs +1 -1
- data/sig/openai/helpers/realtime/translation.rbs +0 -2
- data/sig/openai/helpers/realtime/unknown_server_event.rbs +2 -0
- data/sig/openai/helpers/websocket.rbs +23 -6
- data/sig/openai/models/admin/organization/usage_audio_speeches_response.rbs +10 -0
- data/sig/openai/models/admin/organization/usage_audio_transcriptions_response.rbs +10 -0
- data/sig/openai/models/admin/organization/usage_code_interpreter_sessions_response.rbs +10 -0
- data/sig/openai/models/admin/organization/usage_completions_response.rbs +10 -0
- data/sig/openai/models/admin/organization/usage_costs_response.rbs +10 -0
- data/sig/openai/models/admin/organization/usage_decisions_response.rbs +10 -0
- data/sig/openai/models/admin/organization/usage_embeddings_response.rbs +10 -0
- data/sig/openai/models/admin/organization/usage_file_search_calls_response.rbs +10 -0
- data/sig/openai/models/admin/organization/usage_images_response.rbs +10 -0
- data/sig/openai/models/admin/organization/usage_moderations_response.rbs +10 -0
- data/sig/openai/models/admin/organization/usage_vector_stores_response.rbs +10 -0
- data/sig/openai/models/admin/organization/usage_web_search_calls_response.rbs +10 -0
- data/sig/openai/models/beta/beta_response_input_item.rbs +8 -0
- data/sig/openai/models/responses/response_input_item.rbs +8 -3
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e70580b3709d068f537d9eed65ea7e9da96f89e0e2260b36ebf23a9c3590ecf1
|
|
4
|
+
data.tar.gz: 83910cf81a9410c93588e877cca7f88e3d70cbe07e8eed3cd0789b08e3299a0c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e854d18548c800c72da1eb04b022befc61b36ccd3e1d24fa732db7b66a0d5c3fdca08a73dd0be579f97ad2c24fbf94f318c42e800eea5b9a8118b35b18b6cbe
|
|
7
|
+
data.tar.gz: 8dd6d3894a1137351eea2bcdad51308b46cdf025de798e913bc1f64c198beb31a264140dcd1c2d901249a21f0bea4c22a6ea61ed274a54f9f031fef73a457cf3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.107.0](https://github.com/openai/openai-ruby/compare/v0.106.0...v0.107.0) (2026-10-20)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **api:** add 30-minute cache-write usage counts ([#915](https://github.com/openai/openai-ruby/issues/915)) ([ea2dd66](https://github.com/openai/openai-ruby/commit/ea2dd66ed0c16e544490332fc2ea8ec4f5abd1bb))
|
|
9
|
+
* **api:** add compaction trigger IDs ([#934](https://github.com/openai/openai-ruby/issues/934)) ([224e04a](https://github.com/openai/openai-ruby/commit/224e04ac2f610c793d772efafe7244d6c88039fc))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **api:** correct authentication security key types ([#938](https://github.com/openai/openai-ruby/issues/938)) ([4a5cc74](https://github.com/openai/openai-ruby/commit/4a5cc744b38dc39042d98ae2ff5a32fdd0247bc9))
|
|
15
|
+
* decode base64 embedding vectors ([#942](https://github.com/openai/openai-ruby/issues/942)) ([d684df4](https://github.com/openai/openai-ruby/commit/d684df4dc2a633e34836a346bbbe1e7cbb3d2707))
|
|
16
|
+
* drain audio diagnostics while waiting on input ([#920](https://github.com/openai/openai-ruby/issues/920)) ([8a0b876](https://github.com/openai/openai-ruby/commit/8a0b87606e55f26e86e289577cdfd2533c329ad6))
|
|
17
|
+
* drain ready audio diagnostics without delay ([#918](https://github.com/openai/openai-ruby/issues/918)) ([3e87ce3](https://github.com/openai/openai-ruby/commit/3e87ce380fafe08bb4348b9b867d4006bb939e49))
|
|
18
|
+
* keep Realtime diagnostic rendering payload-free ([#924](https://github.com/openai/openai-ruby/issues/924)) ([3a29026](https://github.com/openai/openai-ruby/commit/3a29026ab177704dbe30d688fddd085e737dfc6d))
|
|
19
|
+
* preserve deep JSON in WebSocket event codecs ([#928](https://github.com/openai/openai-ruby/issues/928)) ([57462c9](https://github.com/openai/openai-ruby/commit/57462c938e3b7602c456b0816e8873bd67fad241))
|
|
20
|
+
* preserve typed Realtime event identity ([#931](https://github.com/openai/openai-ruby/issues/931)) ([9bb5618](https://github.com/openai/openai-ruby/commit/9bb56187c7eb0d72f992c76fed68369d34972d81))
|
|
21
|
+
* **realtime:** abort connections after failed or canceled WebSocket writes so buffered application data cannot be flushed later. On a poisoned connection, `close(code:, reason:)` aborts without a close handshake; the supplied code and reason are not sent. ([#914](https://github.com/openai/openai-ruby/issues/914)) ([d219f3d](https://github.com/openai/openai-ruby/commit/d219f3d0461dd96911429f1b89369eadd382f234))
|
|
22
|
+
* report terminal Realtime failures before transport cleanup ([#923](https://github.com/openai/openai-ruby/issues/923)) ([0d21d75](https://github.com/openai/openai-ruby/commit/0d21d751cd89621c6a32c0048c45ef6e5b7c1af4))
|
|
23
|
+
* retain Translation completion across callback exits ([#921](https://github.com/openai/openai-ruby/issues/921)) ([e6fb44e](https://github.com/openai/openai-ruby/commit/e6fb44e57d233e657d982eee4cf27c6a5d7977c7))
|
|
24
|
+
* share strict WebSocket model validation ([#929](https://github.com/openai/openai-ruby/issues/929)) ([51c6f0b](https://github.com/openai/openai-ruby/commit/51c6f0b73081f0f02f981d2beb4a8b129b2ec1ba))
|
|
25
|
+
* validate nested WebSocket wire field names ([#922](https://github.com/openai/openai-ruby/issues/922)) ([ae551ef](https://github.com/openai/openai-ruby/commit/ae551ef48d52d2c2f415f63c39e0c643fcfb1d75))
|
|
26
|
+
* validate typed WebSocket event constants before conversion ([#925](https://github.com/openai/openai-ruby/issues/925)) ([3ae0cd3](https://github.com/openai/openai-ruby/commit/3ae0cd3070b4fcf046f8823f4cca1b7b8829effe))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Chores
|
|
30
|
+
|
|
31
|
+
* **api:** clarify project API key update requirements ([#936](https://github.com/openai/openai-ruby/issues/936)) ([34606cc](https://github.com/openai/openai-ruby/commit/34606cc46087dae5a643256e9cae93966a106182))
|
|
32
|
+
* **api:** Keep SDK maintenance checks aligned ([#917](https://github.com/openai/openai-ruby/issues/917)) ([ebe5bf4](https://github.com/openai/openai-ruby/commit/ebe5bf4dd74d87acf86e30f5c7a78350ce3f8e8d))
|
|
33
|
+
* **api:** preserve audio transcription response compatibility ([#911](https://github.com/openai/openai-ruby/issues/911)) ([c93fb88](https://github.com/openai/openai-ruby/commit/c93fb88b350c80d724912351cf02d7ff51e3073a))
|
|
34
|
+
* **api:** preserve JSON Schema adapter types ([#930](https://github.com/openai/openai-ruby/issues/930)) ([f1430b8](https://github.com/openai/openai-ruby/commit/f1430b8038c7f01123fbf9f2703f6eb5b7fa8c14))
|
|
35
|
+
* **api:** preserve legacy Threads stream behavior ([#910](https://github.com/openai/openai-ruby/issues/910)) ([436887f](https://github.com/openai/openai-ruby/commit/436887fbe6bc3d0874cb75dca501ba1da0a9d9b4))
|
|
36
|
+
* **api:** preserve optional Decisions usage filters ([#909](https://github.com/openai/openai-ruby/issues/909)) ([34f61df](https://github.com/openai/openai-ruby/commit/34f61dfc23bd1ade311e09a036ef6696af37c138))
|
|
37
|
+
* **api:** preserve optional image edit safety identifiers ([#913](https://github.com/openai/openai-ruby/issues/913)) ([6bbfad1](https://github.com/openai/openai-ruby/commit/6bbfad1a6305b5efc71dbb41dce8560d2865bda2))
|
|
38
|
+
* **api:** preserve standard library loading ([#935](https://github.com/openai/openai-ruby/issues/935)) ([a742640](https://github.com/openai/openai-ruby/commit/a7426400cf499f0d88c9e88e694d86369f8d9cb5))
|
|
39
|
+
* **deps-dev:** bump the development-dependencies group with 3 updates ([#912](https://github.com/openai/openai-ruby/issues/912)) ([1227860](https://github.com/openai/openai-ruby/commit/1227860daec638117ce1f14dcd570cffc92c9647))
|
|
40
|
+
* **deps:** bump ruby/setup-ruby from 1.329.0 to 1.331.0 ([#937](https://github.com/openai/openai-ruby/issues/937)) ([8b3ab95](https://github.com/openai/openai-ruby/commit/8b3ab95e7332a7c5325b4718d4e01246c5a54054))
|
|
41
|
+
* prepare generic WebSocket binding signatures ([#941](https://github.com/openai/openai-ruby/issues/941)) ([9206c8c](https://github.com/openai/openai-ruby/commit/9206c8ceb99cc18df859d06c251fa5336c59ecdf))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
### Documentation
|
|
45
|
+
|
|
46
|
+
* clarify Realtime guide scope and Translation support ([#905](https://github.com/openai/openai-ruby/issues/905)) ([efb4004](https://github.com/openai/openai-ruby/commit/efb4004aa49a8a4374833f4b07fbd373fdebb685))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
### Refactors
|
|
50
|
+
|
|
51
|
+
* share Live codecs and use generated sideband commands ([#933](https://github.com/openai/openai-ruby/issues/933)) ([02861d0](https://github.com/openai/openai-ruby/commit/02861d0780eb18f493f881cfa9ed7cdf9a68abe3))
|
|
52
|
+
* share Translation and binding event codecs ([#932](https://github.com/openai/openai-ruby/issues/932)) ([7dc0088](https://github.com/openai/openai-ruby/commit/7dc00884ba30a54a965fc41e8b19e1840e9faec9))
|
|
53
|
+
|
|
3
54
|
## [0.106.0](https://github.com/openai/openai-ruby/compare/v0.105.0...v0.106.0) (2026-10-17)
|
|
4
55
|
|
|
5
56
|
|
data/README.md
CHANGED
|
@@ -26,6 +26,8 @@ module OpenAI
|
|
|
26
26
|
# A future server event retains its raw data, but routine diagnostics do not
|
|
27
27
|
# render potentially sensitive audio, transcript, or session fields.
|
|
28
28
|
class UnknownServerEvent
|
|
29
|
+
include OpenAI::WebSocket::Protocol
|
|
30
|
+
|
|
29
31
|
attr_reader :type, :data
|
|
30
32
|
|
|
31
33
|
def initialize(data:)
|
|
@@ -38,160 +40,33 @@ module OpenAI
|
|
|
38
40
|
def inspect = "#<#{self.class} type=#{@type.inspect}>"
|
|
39
41
|
alias to_s inspect
|
|
40
42
|
|
|
41
|
-
private def freeze_json(value)
|
|
42
|
-
pending = [value]
|
|
43
|
-
visited = {}.compare_by_identity
|
|
44
|
-
until pending.empty?
|
|
45
|
-
item = pending.pop
|
|
46
|
-
next if visited.key?(item)
|
|
47
|
-
visited[item] = true
|
|
48
|
-
case item
|
|
49
|
-
when Hash
|
|
50
|
-
item.each { |key, val| pending.push(key, val) }
|
|
51
|
-
when Array
|
|
52
|
-
pending.concat(item)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
item.freeze
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
value
|
|
59
|
-
end
|
|
60
43
|
end
|
|
61
44
|
|
|
62
45
|
# A transport-open primary connection. Startup is owned by the caller:
|
|
63
46
|
# send session.start and observe session.started before further commands.
|
|
64
|
-
class Connection < OpenAI::WebSocket::
|
|
65
|
-
include OpenAI::WebSocket::Protocol
|
|
66
|
-
include OpenAI::WebSocket::GuardedConnection
|
|
67
|
-
|
|
47
|
+
class Connection < OpenAI::WebSocket::TypedConnection
|
|
68
48
|
# @api private
|
|
69
49
|
def initialize(socket:, url:)
|
|
70
|
-
super
|
|
50
|
+
super(socket: socket, url: url, client_event: client_event_type, server_event: server_event_type)
|
|
71
51
|
@poisoned = false
|
|
72
52
|
@closed = false
|
|
73
|
-
@server_event_names = discriminator_values(server_event_type)
|
|
74
|
-
@client_event_names = discriminator_values(client_event_type)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def send_event(event)
|
|
78
|
-
send_raw(encode_client_event(event))
|
|
79
53
|
end
|
|
80
54
|
|
|
81
|
-
private
|
|
82
|
-
validate_event_tree!(event)
|
|
83
|
-
payload = OpenAI::Internal::Type::Converter.dump(client_event_type, event)
|
|
84
|
-
type = payload[:type] || payload["type"] if payload.is_a?(Hash)
|
|
85
|
-
raise ArgumentError unless (type.is_a?(String) || type.is_a?(Symbol)) && @client_event_names.key?(type.to_s)
|
|
86
|
-
|
|
87
|
-
coerced = coerce_event(client_event_type, payload, outbound: true)
|
|
88
|
-
serialized = OpenAI::Internal::Type::Converter.dump(client_event_type, coerced)
|
|
89
|
-
validate_event_tree!(serialized, json_only: true)
|
|
90
|
-
JSON.generate(serialized, max_nesting: false)
|
|
91
|
-
rescue StandardError, SystemStackError
|
|
92
|
-
raise ArgumentError, "Invalid Live client event.", cause: nil
|
|
93
|
-
end
|
|
55
|
+
private :parse_event
|
|
94
56
|
|
|
95
|
-
private def
|
|
96
|
-
parsed = JSON.parse(data, symbolize_names: true, max_nesting: false)
|
|
97
|
-
type = event_type(parsed, message: "Live server event must be an object with a string type")
|
|
98
|
-
unless @server_event_names.key?(type.to_s)
|
|
99
|
-
return OpenAI::Live::UnknownServerEvent.new(data: parsed)
|
|
100
|
-
end
|
|
57
|
+
private def coerce_client_event(payload) = coerce_event(@client_event, payload, outbound: true)
|
|
101
58
|
|
|
102
|
-
|
|
59
|
+
private def coerce(type, payload)
|
|
60
|
+
event = coerce_event(type, payload)
|
|
103
61
|
validate_event_tree!(event)
|
|
104
62
|
event
|
|
105
|
-
rescue StandardError, SystemStackError
|
|
106
|
-
raise OpenAI::Errors::LiveProtocolError.new, cause: nil
|
|
107
63
|
end
|
|
108
64
|
|
|
109
|
-
private def
|
|
110
|
-
state = OpenAI::Internal::Type::Converter.new_coerce_state(request_only: outbound)
|
|
111
|
-
event = OpenAI::Internal::Type::Converter.coerce(union, payload, state: state)
|
|
112
|
-
raise ArgumentError if state[:error] || !state.fetch(:exactness).fetch(:no).zero?
|
|
113
|
-
pending = [event]
|
|
114
|
-
until pending.empty?
|
|
115
|
-
value = pending.pop
|
|
116
|
-
case value
|
|
117
|
-
when OpenAI::Internal::Type::BaseModel
|
|
118
|
-
value.class.fields.each do |name, field|
|
|
119
|
-
if field.fetch(:required) &&
|
|
120
|
-
field.fetch(:mode) != (outbound ? :coerce : :dump) &&
|
|
121
|
-
(!outbound || field.fetch(:const) == OpenAI::Internal::OMIT) &&
|
|
122
|
-
!value.to_h.key?(name)
|
|
123
|
-
raise ArgumentError
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
pending.concat(value.to_h.values)
|
|
128
|
-
when Hash
|
|
129
|
-
pending.concat(value.values)
|
|
130
|
-
when Array
|
|
131
|
-
pending.concat(value)
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
event
|
|
136
|
-
end
|
|
65
|
+
private def client_event_error_message = "Invalid Live client event."
|
|
137
66
|
|
|
138
|
-
private def
|
|
139
|
-
ancestors = {}.compare_by_identity
|
|
140
|
-
pending = [[event, false]]
|
|
141
|
-
until pending.empty?
|
|
142
|
-
value, exiting = pending.pop
|
|
143
|
-
if exiting
|
|
144
|
-
ancestors.delete(value)
|
|
145
|
-
next
|
|
146
|
-
end
|
|
67
|
+
private def unknown_server_event(payload) = OpenAI::Live::UnknownServerEvent.new(data: payload)
|
|
147
68
|
|
|
148
|
-
|
|
149
|
-
when OpenAI::Internal::Type::BaseModel
|
|
150
|
-
raise ArgumentError if json_only
|
|
151
|
-
data = value.to_h
|
|
152
|
-
keys = {}
|
|
153
|
-
data.each do |key, val|
|
|
154
|
-
name = key.is_a?(String) ? key.to_sym : key
|
|
155
|
-
field = value.class.known_fields[name]
|
|
156
|
-
serialized_name = field ? field.fetch(:api_name).to_s : name.to_s
|
|
157
|
-
raise ArgumentError if keys.key?(serialized_name)
|
|
158
|
-
keys[serialized_name] = true
|
|
159
|
-
next unless field
|
|
160
|
-
const = field.fetch(:const)
|
|
161
|
-
next if const == OpenAI::Internal::OMIT
|
|
162
|
-
if const.is_a?(Symbol)
|
|
163
|
-
raise ArgumentError unless (val.is_a?(String) || val.is_a?(Symbol)) && val.to_s == const.to_s
|
|
164
|
-
elsif val != const
|
|
165
|
-
raise ArgumentError
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
[data]
|
|
170
|
-
when Hash
|
|
171
|
-
keys = {}
|
|
172
|
-
value.each_key do |key|
|
|
173
|
-
raise ArgumentError unless key.is_a?(String) || key.is_a?(Symbol)
|
|
174
|
-
name = key.to_s
|
|
175
|
-
raise ArgumentError if keys.key?(name)
|
|
176
|
-
keys[name] = true
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
value.values
|
|
180
|
-
when Array
|
|
181
|
-
value
|
|
182
|
-
when NilClass, TrueClass, FalseClass, String, Integer, Float, Symbol
|
|
183
|
-
next
|
|
184
|
-
else
|
|
185
|
-
raise ArgumentError if json_only
|
|
186
|
-
next
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
raise ArgumentError if ancestors.key?(value)
|
|
190
|
-
ancestors[value] = true
|
|
191
|
-
pending << [value, true]
|
|
192
|
-
children.each { |child| pending << [child, false] }
|
|
193
|
-
end
|
|
194
|
-
end
|
|
69
|
+
private def protocol_error = OpenAI::Errors::LiveProtocolError.new
|
|
195
70
|
|
|
196
71
|
private def transport_error = OpenAI::Errors::LiveConnectionError.new(url: @url)
|
|
197
72
|
private def failed_read_message = "Cannot read from a failed Live WebSocket."
|
|
@@ -206,11 +81,7 @@ module OpenAI
|
|
|
206
81
|
|
|
207
82
|
# Attached to an existing session; no new session.start handshake is sent.
|
|
208
83
|
class SidebandConnection < Connection
|
|
209
|
-
|
|
210
|
-
def initialize(socket:, url:)
|
|
211
|
-
super
|
|
212
|
-
@client_event_names = @client_event_names.except("session.start", "session.input_audio.append")
|
|
213
|
-
end
|
|
84
|
+
private def client_event_type = OpenAI::Live::SidebandClientEvent
|
|
214
85
|
end
|
|
215
86
|
|
|
216
87
|
# Caller-driven startup using overrides for an eligible stored session.
|
|
@@ -70,6 +70,7 @@ module OpenAI
|
|
|
70
70
|
if @error_reader && !@error_reader.closed?
|
|
71
71
|
chunk = @error_reader.read_nonblock(65_536, exception: false)
|
|
72
72
|
@errors = true if chunk.is_a?(String) && !chunk.empty?
|
|
73
|
+
@error_reader.close if chunk.nil?
|
|
73
74
|
end
|
|
74
75
|
|
|
75
76
|
!!@errors
|
|
@@ -86,7 +87,9 @@ module OpenAI
|
|
|
86
87
|
raise PlaybackError, "Audio player exited before the input ended." if @status
|
|
87
88
|
end
|
|
88
89
|
|
|
89
|
-
io
|
|
90
|
+
readers = [io]
|
|
91
|
+
readers << @error_reader if @error_reader && !@error_reader.closed?
|
|
92
|
+
IO.select(readers, nil, nil, 0.05)
|
|
90
93
|
end
|
|
91
94
|
end
|
|
92
95
|
|
|
@@ -96,7 +99,8 @@ module OpenAI
|
|
|
96
99
|
check_deadline
|
|
97
100
|
result = io.write_nonblock(bytes.byteslice(offset, 65_536), exception: false)
|
|
98
101
|
if result == :wait_writable
|
|
99
|
-
|
|
102
|
+
readers = @error_reader && !@error_reader.closed? ? [@error_reader] : []
|
|
103
|
+
IO.select(readers, [io], nil, 0.05)
|
|
100
104
|
else
|
|
101
105
|
offset += result
|
|
102
106
|
end
|
|
@@ -110,7 +114,13 @@ module OpenAI
|
|
|
110
114
|
until @status
|
|
111
115
|
check_deadline
|
|
112
116
|
reap
|
|
113
|
-
|
|
117
|
+
unless @status
|
|
118
|
+
if @error_reader && !@error_reader.closed?
|
|
119
|
+
@error_reader.wait_readable(0.01)
|
|
120
|
+
else
|
|
121
|
+
sleep(0.01)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
114
124
|
end
|
|
115
125
|
|
|
116
126
|
@status
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../models/embedding"
|
|
4
|
+
|
|
5
|
+
module OpenAI
|
|
6
|
+
module Models
|
|
7
|
+
class Embedding
|
|
8
|
+
alias_method :embedding_without_base64, :embedding
|
|
9
|
+
private :embedding_without_base64
|
|
10
|
+
|
|
11
|
+
def embedding
|
|
12
|
+
value = self[:embedding]
|
|
13
|
+
return embedding_without_base64 unless value.is_a?(String)
|
|
14
|
+
|
|
15
|
+
# The generated setter replaces this per-field state on assignment.
|
|
16
|
+
cached = @coerced[:embedding]
|
|
17
|
+
return cached if cached.is_a?(Array)
|
|
18
|
+
|
|
19
|
+
# Base64 embedding values contain little-endian float32 samples.
|
|
20
|
+
decoded = begin
|
|
21
|
+
value.unpack1("m0")
|
|
22
|
+
rescue ArgumentError
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
return embedding_without_base64 unless decoded && (decoded.bytesize % 4).zero?
|
|
27
|
+
|
|
28
|
+
@coerced[:embedding] = decoded.unpack("e*")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Equal embeddings still have equal raw data, so the inherited hash remains valid.
|
|
32
|
+
def ==(other)
|
|
33
|
+
return false unless super
|
|
34
|
+
return true unless self[:embedding].is_a?(String)
|
|
35
|
+
|
|
36
|
+
embedding == other.embedding
|
|
37
|
+
rescue OpenAI::Errors::ConversionError
|
|
38
|
+
true
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -33,7 +33,7 @@ module OpenAI
|
|
|
33
33
|
# Parse raw JSON as a typed server event. Valid events that are newer than this
|
|
34
34
|
# SDK remain observable as {UnknownServerEvent} values.
|
|
35
35
|
def parse_event(data)
|
|
36
|
-
parsed =
|
|
36
|
+
parsed = parse_json(data)
|
|
37
37
|
type = event_type(parsed)
|
|
38
38
|
unless @server_event_names.key?(type.to_s)
|
|
39
39
|
return OpenAI::Realtime::UnknownServerEvent.new(data: parsed)
|
|
@@ -46,14 +46,16 @@ module OpenAI
|
|
|
46
46
|
state: state
|
|
47
47
|
)
|
|
48
48
|
if (cause = coercion_error(state))
|
|
49
|
-
raise OpenAI::Errors::RealtimeProtocolError.new(data: data, cause: cause)
|
|
49
|
+
raise OpenAI::Errors::RealtimeProtocolError.new(data: data, cause: cause), cause: nil
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
event
|
|
52
|
+
validate_wire_field_names!(event, parsed)
|
|
53
53
|
rescue OpenAI::Errors::RealtimeProtocolError
|
|
54
54
|
raise
|
|
55
|
+
rescue SystemStackError
|
|
56
|
+
raise OpenAI::Errors::RealtimeProtocolError.new(data: data), cause: nil
|
|
55
57
|
rescue StandardError => e
|
|
56
|
-
raise OpenAI::Errors::RealtimeProtocolError.new(data: data, cause: e)
|
|
58
|
+
raise OpenAI::Errors::RealtimeProtocolError.new(data: data, cause: e), cause: nil
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
# Validate, encode, and send a typed client event.
|
|
@@ -61,6 +63,15 @@ module OpenAI
|
|
|
61
63
|
send_raw(encode_client_event(event))
|
|
62
64
|
end
|
|
63
65
|
|
|
66
|
+
private def write_text(text)
|
|
67
|
+
return super unless @recovery
|
|
68
|
+
|
|
69
|
+
# Recovery owns admission and uncertain sends; rejecting a full queue
|
|
70
|
+
# has not attempted a physical write and must leave this connection usable.
|
|
71
|
+
@recovery.write(text)
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
64
75
|
# Whether automatic recovery is opening or preparing a replacement socket.
|
|
65
76
|
def reconnecting? = @recovery ? @recovery.reconnecting? : false
|
|
66
77
|
|
|
@@ -83,6 +94,10 @@ module OpenAI
|
|
|
83
94
|
validate_discriminator!(event, @client_event_names, kind: "client")
|
|
84
95
|
end
|
|
85
96
|
|
|
97
|
+
# Preserve permissive raw JSON keys while checking original typed models
|
|
98
|
+
# before union conversion can replace their identities or renamed fields.
|
|
99
|
+
validate_event_tree!(event, strict_keys: false)
|
|
100
|
+
|
|
86
101
|
normalized = OpenAI::Internal::Type::Converter.dump(
|
|
87
102
|
OpenAI::Realtime::RealtimeClientEvent,
|
|
88
103
|
event
|
|
@@ -99,7 +114,9 @@ module OpenAI
|
|
|
99
114
|
|
|
100
115
|
payload = OpenAI::Internal::Type::Converter.dump(OpenAI::Realtime::RealtimeClientEvent, coerced)
|
|
101
116
|
validate_discriminator!(payload, @client_event_names, kind: "client")
|
|
102
|
-
|
|
117
|
+
generate_json(payload)
|
|
118
|
+
rescue SystemStackError
|
|
119
|
+
raise ArgumentError, "Invalid Realtime client event.", cause: nil
|
|
103
120
|
rescue StandardError => e
|
|
104
121
|
raise ArgumentError.new("Invalid Realtime client event."), cause: e
|
|
105
122
|
end
|
|
@@ -76,7 +76,7 @@ module OpenAI
|
|
|
76
76
|
|
|
77
77
|
def take_pending_messages
|
|
78
78
|
@mutex.synchronize do
|
|
79
|
-
raise state_error("Cannot remove queued events while flushing.") if @flushing
|
|
79
|
+
raise state_error("Cannot remove queued events while flushing."), cause: nil if @flushing
|
|
80
80
|
|
|
81
81
|
messages = @pending
|
|
82
82
|
@pending = []
|
|
@@ -106,7 +106,7 @@ module OpenAI
|
|
|
106
106
|
|
|
107
107
|
if @state != :open || @flushing
|
|
108
108
|
if @max_queue_bytes.zero? || @pending_bytes + [text.bytesize, 1].max > @max_queue_bytes
|
|
109
|
-
raise OpenAI::Errors::RealtimeQueueFullError
|
|
109
|
+
raise OpenAI::Errors::RealtimeQueueFullError, cause: nil
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
@pending << text.dup.freeze
|
|
@@ -124,13 +124,15 @@ module OpenAI
|
|
|
124
124
|
|
|
125
125
|
def flush_pending
|
|
126
126
|
if Fiber.current.equal?(@owner_fiber)
|
|
127
|
-
raise state_error("Queue flush interrupted; remaining events were not sent.") unless flush
|
|
127
|
+
raise state_error("Queue flush interrupted; remaining events were not sent."), cause: nil unless flush
|
|
128
128
|
return nil
|
|
129
129
|
end
|
|
130
130
|
|
|
131
131
|
reply = @mutex.synchronize do
|
|
132
132
|
raise @error, cause: nil if @error
|
|
133
|
-
|
|
133
|
+
unless @state == :open
|
|
134
|
+
raise state_error("Cannot flush until the replacement connection is ready."), cause: nil
|
|
135
|
+
end
|
|
134
136
|
|
|
135
137
|
register(:flush).tap { @commands << [:flush, nil, _1, @generation] }
|
|
136
138
|
end
|
|
@@ -348,7 +350,11 @@ module OpenAI
|
|
|
348
350
|
uncertain_message: @inflight
|
|
349
351
|
)
|
|
350
352
|
elsif @state != :closed
|
|
351
|
-
@
|
|
353
|
+
if recoverable?(e) && @attempts < @max_attempts
|
|
354
|
+
@state = :recovering
|
|
355
|
+
else
|
|
356
|
+
fail_locked("Realtime reconnect stopped; the connection could not be recovered.")
|
|
357
|
+
end
|
|
352
358
|
end
|
|
353
359
|
end
|
|
354
360
|
|