openai 0.115.0 → 0.116.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.
@@ -146,6 +146,7 @@ module OpenAI
146
146
  @in_flight = false
147
147
  @pending_steers = pending_steers
148
148
  @response_id = nil
149
+ @required_input = {}
149
150
  @changed = Async::Condition.new
150
151
  @accumulator = SessionAccumulator.new(session.limits.max_response_bytes)
151
152
  end
@@ -169,7 +170,9 @@ module OpenAI
169
170
  end
170
171
 
171
172
  pending = @pending_steers[@response_id]
172
- if @in_flight || queued_response || (pending && pending[:count].positive?)
173
+ if @in_flight ||
174
+ queued_response ||
175
+ (pending && pending[:count].positive? && !required_input_continuation?(data))
173
176
  raise SessionError, "Consume the current response before sending another create on this lane."
174
177
  end
175
178
 
@@ -244,9 +247,15 @@ module OpenAI
244
247
  @in_flight = true
245
248
  @pending_steers.delete(@response_id) if @response_id != event.response.id
246
249
  @response_id = event.response.id.dup.freeze
250
+ @required_input.clear
247
251
  elsif %w[response.completed response.failed response.incomplete error].include?(event.type.to_s)
248
252
  @in_flight = false
249
- @response_id = event.response.id.dup.freeze unless event.type.to_s == "error"
253
+ if event.type.to_s == "error"
254
+ @required_input.clear
255
+ else
256
+ @required_input = required_input_keys(@accumulator.final_response&.output || [])
257
+ @response_id = event.response.id.dup.freeze
258
+ end
250
259
  end
251
260
  end
252
261
 
@@ -262,6 +271,69 @@ module OpenAI
262
271
  end
263
272
  end
264
273
 
274
+ private def required_input_continuation?(data)
275
+ return false unless data[:previous_response_id] == @response_id
276
+
277
+ input = data[:input]
278
+ return false unless input.is_a?(Array) && !input.empty?
279
+
280
+ keys = input.map do |item|
281
+ return false unless item.is_a?(Hash)
282
+
283
+ required_input_key(item)
284
+ end
285
+
286
+ keys.uniq.length == keys.length && keys.all? { |key| @required_input.key?(key) }
287
+ end
288
+
289
+ private def required_input_key(data)
290
+ type = data[:type].to_s
291
+ id = case type
292
+ when
293
+ "function_call_output",
294
+ "custom_tool_call_output",
295
+ "computer_call_output",
296
+ "shell_call_output",
297
+ "apply_patch_call_output"
298
+ data[:call_id]
299
+ when "tool_search_output"
300
+ data[:call_id] if data[:execution].to_s == "client"
301
+ when "mcp_approval_response"
302
+ data[:approval_request_id]
303
+ end
304
+
305
+ [type, id] if id.is_a?(String) && !id.empty?
306
+ end
307
+
308
+ private def required_input_keys(items)
309
+ required = {}
310
+ items.each do |item|
311
+ data = item.to_h
312
+ type = data[:type].to_s
313
+ next if type != "mcp_approval_request" && data[:status] && data[:status].to_s != "completed"
314
+
315
+ result_type = case type
316
+ when "function_call", "custom_tool_call", "computer_call", "apply_patch_call"
317
+ "#{type}_output"
318
+ when "shell_call"
319
+ "shell_call_output" if data[:environment].to_h[:type].to_s == "local"
320
+ when "tool_search_call"
321
+ "tool_search_output" if data[:execution].to_s == "client"
322
+ when "mcp_approval_request"
323
+ "mcp_approval_response"
324
+ end
325
+
326
+ next unless result_type
327
+
328
+ id = data[type == "mcp_approval_request" ? :id : :call_id]
329
+ # Retain only detached IDs when get_final_response releases its collector.
330
+ required[[result_type, id.dup.freeze]] = true if id.is_a?(String) && !id.empty?
331
+ end
332
+
333
+ items.each { |item| required.delete(required_input_key(item.to_h)) }
334
+ required
335
+ end
336
+
265
337
  def get_final_response
266
338
  @session.assert_owner!
267
339
  loop do
@@ -306,6 +378,7 @@ module OpenAI
306
378
  @queue.clear
307
379
  @bytes = 0
308
380
  @accumulator = SessionAccumulator.new(@session.limits.max_response_bytes)
381
+ @required_input.clear
309
382
  @changed.signal
310
383
  end
311
384