0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-08 17:53:55 +08:00
discourse/plugins/discourse-ai/lib/completions/json_streaming_tracker.rb
Sam c6d0e5409d
FIX: Preserve whitespace in streamed JSON (#42339)
Escape control characters only within JSON string values so
pretty-printed formatting remains intact. Retain lexical state across
streaming chunks to correctly handle split strings, escapes, and control
characters.
2026-08-05 17:31:29 +10:00

57 lines
1.3 KiB
Ruby
Vendored

# frozen_string_literal: true
module DiscourseAi
module Completions
class JsonStreamingTracker
attr_reader :stream_consumer
def initialize(stream_consumer)
@stream_consumer = stream_consumer
@escaped_buffer = +""
@completer = JsonCompleter.new
@broken = false
@last_notified = {}
@control_character_escaper = DiscourseAi::Utils::JsonControlCharacterEscaper.new
end
def broken?
@broken
end
def <<(raw_json)
return if @broken
if !raw_json.is_a?(String)
@broken = true
return
end
@escaped_buffer << @control_character_escaper.escape(raw_json)
parsed =
begin
@completer.parse(@escaped_buffer)
rescue JsonCompleter::ParseError
@broken = true
return
end
notify_changes(parsed) if parsed.is_a?(Hash)
end
private
def notify_changes(parsed)
parsed.each do |key, value|
next if value.nil?
next if @last_notified[key] == value
# the completer mutates parsed containers in place between calls, so
# compare against a snapshot
@last_notified[key] = value.deep_dup
stream_consumer.notify_progress(key, value)
end
end
end
end
end