openai 0.95.0 → 0.96.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 +44 -0
- data/README.md +5 -2
- data/docs/architecture/security-model.md +2 -2
- data/examples/audio/record_and_play.rb +1 -1
- data/lib/openai/helpers/live/transcript_grouper.rb +264 -0
- data/lib/openai/helpers/live/transcript_grouping.rb +309 -0
- data/lib/openai/helpers/live/websocket.rb +454 -0
- data/lib/openai/helpers/realtime/translation.rb +440 -0
- data/lib/openai/helpers/realtime.rb +1 -0
- data/lib/openai/helpers/responses_websocket/incremental.rb +246 -0
- data/lib/openai/helpers/responses_websocket/transports/async_websocket.rb +183 -0
- data/lib/openai/helpers/responses_websocket.rb +1 -0
- data/lib/openai/helpers/streaming/response_stream.rb +19 -11
- data/lib/openai/helpers/structured_output/chat_completion_parser.rb +0 -2
- data/lib/openai/helpers/structured_output/response_parser.rb +1 -2
- data/lib/openai/helpers/structured_output/union_of.rb +0 -2
- data/lib/openai/helpers/websocket/async_websocket_transport.rb +11 -6
- data/lib/openai/helpers/websocket/connection_manager.rb +9 -0
- data/lib/openai/internal/read_io_adapter.rb +0 -2
- data/lib/openai/internal/stream.rb +0 -2
- data/lib/openai/internal/type/base_model.rb +4 -6
- data/lib/openai/internal/type/converter.rb +5 -3
- data/lib/openai/internal/type/enum.rb +0 -2
- data/lib/openai/internal/type/union.rb +0 -6
- data/lib/openai/internal/type/unknown.rb +34 -6
- data/lib/openai/internal/util.rb +0 -4
- data/lib/openai/models/audio/speech_create_params.rb +9 -0
- data/lib/openai/models/audio/transcription_create_response.rb +4 -1
- data/lib/openai/models/audio/translation_create_response.rb +4 -1
- data/lib/openai/models/beta/agent.rb +1 -0
- data/lib/openai/models/beta/agent_create_params.rb +3 -0
- data/lib/openai/models/beta/agent_session.rb +1 -0
- data/lib/openai/models/beta/agent_update_params.rb +3 -0
- data/lib/openai/models/beta/agents/environments/template_create_params.rb +3 -3
- data/lib/openai/models/beta/agents/environments/template_update_params.rb +6 -6
- data/lib/openai/models/beta/agents/session_create_params.rb +3 -0
- data/lib/openai/models/beta/agents/session_update_params.rb +3 -0
- data/lib/openai/models/beta/agents/vaults/credential.rb +10 -1
- data/lib/openai/models/beta/agents/vaults/credential_create_params.rb +12 -1
- data/lib/openai/models/beta/agents/vaults/credential_update_params.rb +14 -3
- data/lib/openai/models/beta/beta_response.rb +38 -1
- data/lib/openai/models/beta/beta_responses_client_event.rb +73 -1
- data/lib/openai/models/beta/beta_web_search_preview_tool.rb +9 -3
- data/lib/openai/models/beta/beta_web_search_tool.rb +9 -3
- data/lib/openai/models/beta/environment_param.rb +3 -3
- data/lib/openai/models/beta/response_compact_params.rb +3 -0
- data/lib/openai/models/beta/response_create_params.rb +68 -1
- data/lib/openai/models/beta/session_turn_error.rb +3 -0
- data/lib/openai/models/chat_model.rb +1 -0
- data/lib/openai/models/responses/response.rb +35 -1
- data/lib/openai/models/responses/response_compact_params.rb +3 -0
- data/lib/openai/models/responses/response_create_params.rb +65 -1
- data/lib/openai/models/responses/responses_client_event.rb +70 -1
- data/lib/openai/models/responses/web_search_preview_tool.rb +9 -3
- data/lib/openai/models/responses/web_search_tool.rb +9 -3
- data/lib/openai/net_http_client.rb +0 -2
- data/lib/openai/resources/audio/transcriptions.rb +5 -3
- data/lib/openai/resources/audio/translations.rb +1 -1
- data/lib/openai/resources/beta/agents/environments/templates.rb +3 -3
- data/lib/openai/resources/beta/agents/vaults/credentials.rb +12 -4
- data/lib/openai/resources/beta/responses.rb +8 -2
- data/lib/openai/resources/responses.rb +8 -1
- data/lib/openai/version.rb +1 -1
- data/lib/openai.rb +1 -0
- data/rbi/openai/helpers/live/transcript_grouper.rbi +67 -0
- data/rbi/openai/helpers/live/websocket.rbi +299 -0
- data/rbi/openai/helpers/realtime/translation.rbi +137 -0
- data/rbi/openai/helpers/responses_websocket/incremental.rbi +32 -0
- data/rbi/openai/helpers/responses_websocket/transports/async_websocket.rbi +24 -0
- data/rbi/openai/helpers/streaming/response_stream.rbi +5 -0
- data/rbi/openai/internal/type/converter.rbi +3 -2
- data/rbi/openai/models/audio/speech_create_params.rbi +3 -0
- data/rbi/openai/models/audio/transcription_create_response.rbi +2 -1
- data/rbi/openai/models/audio/translation_create_response.rbi +2 -1
- data/rbi/openai/models/beta/agent.rbi +1 -0
- data/rbi/openai/models/beta/agent_create_params.rbi +3 -0
- data/rbi/openai/models/beta/agent_session.rbi +1 -0
- data/rbi/openai/models/beta/agent_update_params.rbi +3 -0
- data/rbi/openai/models/beta/agents/environments/template_create_params.rbi +3 -3
- data/rbi/openai/models/beta/agents/environments/template_update_params.rbi +6 -6
- data/rbi/openai/models/beta/agents/session_create_params.rbi +3 -0
- data/rbi/openai/models/beta/agents/session_update_params.rbi +3 -0
- data/rbi/openai/models/beta/agents/vaults/credential.rbi +10 -0
- data/rbi/openai/models/beta/agents/vaults/credential_create_params.rbi +15 -0
- data/rbi/openai/models/beta/agents/vaults/credential_update_params.rbi +35 -6
- data/rbi/openai/models/beta/beta_response.rbi +64 -0
- data/rbi/openai/models/beta/beta_responses_client_event.rbi +128 -0
- data/rbi/openai/models/beta/beta_web_search_preview_tool.rbi +9 -3
- data/rbi/openai/models/beta/beta_web_search_tool.rbi +9 -3
- data/rbi/openai/models/beta/environment_param.rbi +3 -3
- data/rbi/openai/models/beta/response_compact_params.rbi +1 -0
- data/rbi/openai/models/beta/response_create_params.rbi +100 -0
- data/rbi/openai/models/beta/session_turn_error.rbi +6 -0
- data/rbi/openai/models/chat_model.rbi +1 -0
- data/rbi/openai/models/responses/response.rbi +63 -0
- data/rbi/openai/models/responses/response_compact_params.rbi +1 -0
- data/rbi/openai/models/responses/response_create_params.rbi +106 -0
- data/rbi/openai/models/responses/responses_client_event.rbi +125 -0
- data/rbi/openai/models/responses/web_search_preview_tool.rbi +9 -3
- data/rbi/openai/models/responses/web_search_tool.rbi +9 -3
- data/rbi/openai/resources/audio/transcriptions.rbi +4 -2
- data/rbi/openai/resources/beta/agents/environments/templates.rbi +3 -3
- data/rbi/openai/resources/beta/agents/vaults/credentials.rbi +11 -3
- data/rbi/openai/resources/beta/responses.rbi +6 -0
- data/rbi/openai/resources/responses.rbi +11 -0
- data/responses-websocket.md +55 -0
- data/sig/openai/helpers/live/transcript_grouper.rbs +42 -0
- data/sig/openai/helpers/live/websocket.rbs +152 -0
- data/sig/openai/helpers/realtime/translation.rbs +81 -0
- data/sig/openai/helpers/responses_websocket/incremental.rbs +14 -0
- data/sig/openai/helpers/responses_websocket/transports/async_websocket.rbs +21 -0
- data/sig/openai/helpers/streaming/response_stream.rbs +9 -0
- data/sig/openai/internal/type/converter.rbs +3 -1
- data/sig/openai/models/audio/speech_create_params.rbs +6 -0
- data/sig/openai/models/audio/transcription_create_response.rbs +1 -0
- data/sig/openai/models/audio/translation_create_response.rbs +3 -1
- data/sig/openai/models/beta/agent.rbs +3 -1
- data/sig/openai/models/beta/agent_create_params.rbs +5 -1
- data/sig/openai/models/beta/agent_session.rbs +3 -1
- data/sig/openai/models/beta/agent_update_params.rbs +5 -1
- data/sig/openai/models/beta/agents/environments/template_create_params.rbs +2 -2
- data/sig/openai/models/beta/agents/environments/template_update_params.rbs +4 -4
- data/sig/openai/models/beta/agents/session_create_params.rbs +5 -1
- data/sig/openai/models/beta/agents/session_update_params.rbs +5 -1
- data/sig/openai/models/beta/agents/vaults/credential.rbs +8 -0
- data/sig/openai/models/beta/agents/vaults/credential_create_params.rbs +14 -0
- data/sig/openai/models/beta/agents/vaults/credential_update_params.rbs +23 -4
- data/sig/openai/models/beta/beta_response.rbs +37 -0
- data/sig/openai/models/beta/beta_responses_client_event.rbs +79 -0
- data/sig/openai/models/beta/beta_web_search_preview_tool.rbs +6 -2
- data/sig/openai/models/beta/beta_web_search_tool.rbs +6 -2
- data/sig/openai/models/beta/environment_param.rbs +2 -2
- data/sig/openai/models/beta/response_compact_params.rbs +2 -0
- data/sig/openai/models/beta/response_create_params.rbs +79 -0
- data/sig/openai/models/beta/session_turn_error.rbs +4 -0
- data/sig/openai/models/chat_model.rbs +2 -0
- data/sig/openai/models/responses/response.rbs +35 -0
- data/sig/openai/models/responses/response_compact_params.rbs +2 -0
- data/sig/openai/models/responses/response_create_params.rbs +77 -0
- data/sig/openai/models/responses/responses_client_event.rbs +77 -0
- data/sig/openai/models/responses/web_search_preview_tool.rbs +6 -2
- data/sig/openai/models/responses/web_search_tool.rbs +6 -2
- data/sig/openai/resources/beta/agents/vaults/credentials.rbs +3 -1
- data/sig/openai/resources/beta/responses.rbs +2 -0
- data/sig/openai/resources/responses.rbs +4 -0
- metadata +14 -1
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenAI
|
|
4
|
+
module Live
|
|
5
|
+
class TranscriptGrouper
|
|
6
|
+
# @api private
|
|
7
|
+
# The established Live display policy, driven only by public text intervals.
|
|
8
|
+
class Grouping
|
|
9
|
+
Turn = Struct.new(
|
|
10
|
+
:id,
|
|
11
|
+
:previous_id,
|
|
12
|
+
:speaker,
|
|
13
|
+
:text,
|
|
14
|
+
:start_ms,
|
|
15
|
+
:end_ms,
|
|
16
|
+
:emitted_bytes,
|
|
17
|
+
:can_drop,
|
|
18
|
+
:acknowledgment
|
|
19
|
+
) do
|
|
20
|
+
def snapshot
|
|
21
|
+
Segment.new(id, previous_id, speaker, text.dup.freeze, start_ms, end_ms)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Acknowledgment = Data.define(:characters, :text)
|
|
26
|
+
ACKNOWLEDGMENTS = [
|
|
27
|
+
"aha",
|
|
28
|
+
"alright",
|
|
29
|
+
"gotcha",
|
|
30
|
+
"hm",
|
|
31
|
+
"hmm",
|
|
32
|
+
"mhm",
|
|
33
|
+
"mm",
|
|
34
|
+
"mm hmm",
|
|
35
|
+
"okay",
|
|
36
|
+
"ok",
|
|
37
|
+
"right",
|
|
38
|
+
"sure",
|
|
39
|
+
"uh huh",
|
|
40
|
+
"yeah",
|
|
41
|
+
"yep",
|
|
42
|
+
"yes"
|
|
43
|
+
].freeze
|
|
44
|
+
# Match the JavaScript/Python policy, including BOM but not NEL.
|
|
45
|
+
SPACE = "\u0009\u000a\u000b\u000c\u000d\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff"
|
|
46
|
+
SEPARATORS = SPACE + ".,!?;:\"'()[]{}"
|
|
47
|
+
TRIM = /\A[#{Regexp.escape(SEPARATORS)}]+|[#{Regexp.escape(SEPARATORS)}]+\z/
|
|
48
|
+
WHITESPACE = /[#{SPACE}]+/
|
|
49
|
+
# Ruby downcase omits the contextual final sigma used by JavaScript/Python.
|
|
50
|
+
FINAL_SIGMA = /([\p{Cased}&&\P{Case_Ignorable}]\p{Case_Ignorable}*)Σ(?!\p{Case_Ignorable}*[\p{Cased}&&\P{Case_Ignorable}])/
|
|
51
|
+
|
|
52
|
+
def initialize(options, id_prefix)
|
|
53
|
+
@options = options
|
|
54
|
+
@id_prefix = id_prefix
|
|
55
|
+
@next_id = 0
|
|
56
|
+
@acknowledgments = ACKNOWLEDGMENTS +
|
|
57
|
+
options.additional_acknowledgments.map { |text| normalize(text) }.reject(&:empty?)
|
|
58
|
+
@max_acknowledgment_length = @acknowledgments.map(&:length).max
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def speaker = @current&.speaker
|
|
62
|
+
|
|
63
|
+
def process(fragments)
|
|
64
|
+
preferred = speaker || :user
|
|
65
|
+
ordered = fragments.partition { |part| part.speaker == preferred }.flatten(1)
|
|
66
|
+
first = ordered.first
|
|
67
|
+
return [] unless first
|
|
68
|
+
|
|
69
|
+
emitted = []
|
|
70
|
+
while (due = deadline) && due <= first.start_ms
|
|
71
|
+
emitted.concat(advance(due))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if @current && ordered.none? { |part| part.speaker == speaker }
|
|
75
|
+
emitted.concat(advance(first.start_ms, ordered.any? { |part| part.speaker == :user }))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
ordered.each { |part| emitted.concat(ingest(part)) }
|
|
79
|
+
emitted
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def advance(time_ms, incoming_user = false)
|
|
83
|
+
# Match #deadline exactly: subtracting fractional timings can round
|
|
84
|
+
# below the threshold and prevent the source-time loop from advancing.
|
|
85
|
+
if @current &&
|
|
86
|
+
@buffered &&
|
|
87
|
+
time_ms >= @current.end_ms +
|
|
88
|
+
@options.min_turn_separation_ms &&
|
|
89
|
+
!keep_backchannel?(time_ms)
|
|
90
|
+
@buffered = maybe_drop_backchannel(time_ms)
|
|
91
|
+
return promote if @buffered
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if speaker == :assistant && !incoming_user && time_ms >= @current.end_ms + @options.assistant_silence_ms
|
|
95
|
+
return finish_current(:inactivity)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
[]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def deadline
|
|
102
|
+
return unless @current
|
|
103
|
+
if @buffered
|
|
104
|
+
separation = @current.end_ms + @options.min_turn_separation_ms
|
|
105
|
+
if might_be_backchannel? && @buffered.can_drop && !user_continued? && !recent_assistant?
|
|
106
|
+
return [separation, @buffered.end_ms + @options.backchannel_isolation_ms].max
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
return separation
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
@current.end_ms + @options.assistant_silence_ms if speaker == :assistant
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def update_pending?
|
|
116
|
+
@current && @current.emitted_bytes && @current.text.bytesize > @current.emitted_bytes
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def flush_update
|
|
120
|
+
update_pending? ? emit(@current, true) : []
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def close(time_ms, reason)
|
|
124
|
+
pending = maybe_drop_backchannel(time_ms)
|
|
125
|
+
emitted = finish_current(reason)
|
|
126
|
+
@buffered = nil
|
|
127
|
+
if pending && !pending.text.empty?
|
|
128
|
+
emitted.concat(emit(pending)).concat(finish(pending, reason))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
@last_assistant_end = nil if reason == :timestamp_reset
|
|
132
|
+
emitted
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
def ingest(fragment)
|
|
138
|
+
unless @current
|
|
139
|
+
@current = new_turn(fragment)
|
|
140
|
+
return emit(@current)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
if fragment.speaker == speaker
|
|
144
|
+
append(@current, fragment, !@buffered.nil?)
|
|
145
|
+
return emit(@current)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
@buffered = nil if speaker == :user && user_continued?
|
|
149
|
+
if speaker == :assistant
|
|
150
|
+
buffer(fragment)
|
|
151
|
+
return promote
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
within_duration = fragment.end_ms -
|
|
155
|
+
(@buffered&.start_ms || fragment.start_ms) < @options.backchannel_max_duration_ms
|
|
156
|
+
ack = acknowledgment(fragment, within_duration)
|
|
157
|
+
normalized = ack.text if within_duration
|
|
158
|
+
if fragment.start_ms < @current.end_ms + @options.min_turn_separation_ms
|
|
159
|
+
can_drop = normalized &&
|
|
160
|
+
!normalized.empty? &&
|
|
161
|
+
@acknowledgments.any? { |phrase| phrase.start_with?(normalized) }
|
|
162
|
+
buffer(fragment, !!can_drop, ack)
|
|
163
|
+
return []
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
if normalized && @acknowledgments.include?(normalized)
|
|
167
|
+
buffer(fragment, !@buffered.nil?, ack)
|
|
168
|
+
return []
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
@buffered = maybe_drop_backchannel(nil, fragment)
|
|
172
|
+
emitted = finish_current(:speaker_change)
|
|
173
|
+
if @buffered
|
|
174
|
+
@current = @buffered
|
|
175
|
+
@buffered = nil
|
|
176
|
+
append(@current, fragment)
|
|
177
|
+
else
|
|
178
|
+
@current = new_turn(fragment)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
emitted.concat(emit(@current))
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def new_turn(fragment)
|
|
185
|
+
id = "#{@id_prefix}_#{@next_id}".freeze
|
|
186
|
+
@next_id += 1
|
|
187
|
+
Turn.new(id, nil, fragment.speaker, fragment.text.dup, fragment.start_ms, fragment.end_ms, nil, true, nil)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def append(turn, fragment, separate = false)
|
|
191
|
+
separator = separate && /[\p{L}\p{N}]\z/.match?(turn.text) && /\A[\p{L}\p{N}]/.match?(fragment.text) ? " " : ""
|
|
192
|
+
turn.text << separator << fragment.text
|
|
193
|
+
turn.end_ms = [turn.end_ms, fragment.end_ms].max
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def buffer(fragment, can_drop = nil, ack = nil)
|
|
197
|
+
if @buffered
|
|
198
|
+
append(@buffered, fragment)
|
|
199
|
+
else
|
|
200
|
+
@buffered = new_turn(fragment)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
@buffered.can_drop = can_drop unless can_drop.nil?
|
|
204
|
+
@buffered.acknowledgment = ack
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def promote
|
|
208
|
+
return [] unless @buffered
|
|
209
|
+
|
|
210
|
+
emitted = finish_current(:speaker_change)
|
|
211
|
+
@current = @buffered
|
|
212
|
+
@buffered = nil
|
|
213
|
+
emitted.concat(emit(@current))
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def finish_current(reason)
|
|
217
|
+
turn = @current
|
|
218
|
+
@current = nil
|
|
219
|
+
turn ? finish(turn, reason) : []
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def finish(turn, reason)
|
|
223
|
+
@last_assistant_end = turn.end_ms if turn.speaker == :assistant
|
|
224
|
+
turn.emitted_bytes ? emit(turn, true) + [Update.new(turn.snapshot, reason)] : []
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def emit(turn, force = false)
|
|
228
|
+
return [] if turn.text.empty?
|
|
229
|
+
bytes = turn.text.bytesize
|
|
230
|
+
if turn.emitted_bytes
|
|
231
|
+
return [] if bytes == turn.emitted_bytes || (!force && bytes < turn.emitted_bytes * 2)
|
|
232
|
+
else
|
|
233
|
+
turn.previous_id = @last_id
|
|
234
|
+
@last_id = turn.id
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
turn.emitted_bytes = bytes
|
|
238
|
+
|
|
239
|
+
[Update.new(turn.snapshot, nil)]
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def might_be_backchannel?
|
|
243
|
+
speaker == :user &&
|
|
244
|
+
@buffered&.speaker == :assistant &&
|
|
245
|
+
@buffered.end_ms -
|
|
246
|
+
@buffered.start_ms < @options.backchannel_max_duration_ms
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def user_continued?
|
|
250
|
+
might_be_backchannel? && @buffered.can_drop && @current.end_ms > @buffered.end_ms
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def recent_assistant?
|
|
254
|
+
@current &&
|
|
255
|
+
@buffered &&
|
|
256
|
+
@last_assistant_end &&
|
|
257
|
+
@buffered.start_ms < @last_assistant_end +
|
|
258
|
+
@options.backchannel_isolation_ms &&
|
|
259
|
+
@buffered.start_ms <= @current.start_ms
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def keep_backchannel?(time_ms)
|
|
263
|
+
might_be_backchannel? &&
|
|
264
|
+
@buffered.can_drop &&
|
|
265
|
+
!user_continued? &&
|
|
266
|
+
!recent_assistant? &&
|
|
267
|
+
time_ms < @buffered.end_ms +
|
|
268
|
+
@options.backchannel_isolation_ms
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def maybe_drop_backchannel(time_ms = nil, following = nil)
|
|
272
|
+
return @buffered unless might_be_backchannel?
|
|
273
|
+
return nil if user_continued?
|
|
274
|
+
return @buffered if recent_assistant?
|
|
275
|
+
return @buffered if following && following.start_ms < @buffered.end_ms + @options.backchannel_isolation_ms
|
|
276
|
+
return @buffered if !following && (!time_ms || time_ms < @buffered.end_ms + @options.backchannel_isolation_ms)
|
|
277
|
+
|
|
278
|
+
@buffered unless @buffered.can_drop
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def acknowledgment(fragment, within_duration)
|
|
282
|
+
previous = @buffered&.acknowledgment
|
|
283
|
+
previous_characters = previous&.characters || 0
|
|
284
|
+
characters = previous_characters
|
|
285
|
+
fragment.text.each_char do |character|
|
|
286
|
+
break if characters > @max_acknowledgment_length
|
|
287
|
+
|
|
288
|
+
characters += 1 unless SEPARATORS.include?(character) || character == "-"
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
text = if characters == previous_characters && previous&.text
|
|
292
|
+
previous.text
|
|
293
|
+
elsif within_duration && characters <= @max_acknowledgment_length
|
|
294
|
+
normalize((@buffered&.text || "") + fragment.text)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
Acknowledgment.new(characters, text)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def normalize(text)
|
|
301
|
+
text = text.gsub(FINAL_SIGMA) { "#{Regexp.last_match(1)}ς" }
|
|
302
|
+
text.downcase.tr("-", " ").gsub(TRIM, "").split(WHITESPACE).join(" ")
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
private_constant :Grouping
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|