module ServerSentEvents

Incrementally decodes Codex Responses API server-sent events.

Definitions

def each(body, &block)

Decode an HTTP body as server-sent events.

Signature

parameter body Enumerable(String)

The streamed event body.

yields {|event| ...}

Each decoded event.

parameter event Hash

The event data.

returns Enumerator | Nil

An enumerator without a block, otherwise nil.

Implementation

def each(body, &block)
	return enum_for(__method__, body) unless block
	
	buffer = String.new
	data = []
	event_name = nil
	
	body.each do |chunk|
		buffer << chunk
		while newline = buffer.index("\n")
			line = buffer.slice!(0, newline + 1).delete_suffix("\n").delete_suffix("\r")
			
			if line.empty?
				yield_event(event_name, data, &block) unless data.empty?
				data.clear
				event_name = nil
			elsif line.start_with?("data:")
				data << line.delete_prefix("data:").sub(/\A /, "")
			elsif line.start_with?("event:")
				event_name = line.delete_prefix("event:").strip
			end
		end
	end
	
	unless buffer.empty?
		line = buffer.delete_suffix("\r")
		data << line.delete_prefix("data:").sub(/\A /, "") if line.start_with?("data:")
		event_name = line.delete_prefix("event:").strip if line.start_with?("event:")
	end
	
	yield_event(event_name, data, &block) unless data.empty?
end

def parse(body)

Collect all decoded events from a body.

Signature

parameter body Enumerable(String)

The event body.

returns Array(Hash)

The decoded events.

Implementation

def parse(body)
	events = []
	each(body) {|event| events << event}
	return events
end

def yield_event(event_name, data)

Parse event data and yield a non-terminal event.

Signature

parameter event_name String | Nil

The optional event name.

parameter data Array(String)

The event data lines.

yields {|event| ...}

The parsed event object.

parameter event Hash

The decoded JSON data.

Implementation

def yield_event(event_name, data)
	joined = data.join("\n")
	return if joined == "[DONE]"
	
	event = JSON.parse(joined)
	event["type"] ||= event_name if event_name
	yield event
end