Class: Silas::AgentSdk::StreamParser

Inherits:
Object
  • Object
show all
Defined in:
lib/silas/agent_sdk/stream_parser.rb

Overview

Tolerant NDJSON parser for claude -p --output-format stream-json. The event schema is under-documented and shifts between versions, so unknown lines/types must never raise. Yields Silas::Event objects; accumulates the terminal Result (final text, tool_use blocks, usage, session_id).

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeStreamParser

Returns a new instance of StreamParser.



12
13
14
15
16
17
18
19
# File 'lib/silas/agent_sdk/stream_parser.rb', line 12

def initialize
  @blocks = []
  @tool_calls = []
  @final_text = nil
  @usage = nil
  @session_id = nil
  @stop_reason = nil
end

Instance Attribute Details

#blocksObject (readonly)

Returns the value of attribute blocks.



10
11
12
# File 'lib/silas/agent_sdk/stream_parser.rb', line 10

def blocks
  @blocks
end

#final_textObject (readonly)

Returns the value of attribute final_text.



10
11
12
# File 'lib/silas/agent_sdk/stream_parser.rb', line 10

def final_text
  @final_text
end

#session_idObject (readonly)

Returns the value of attribute session_id.



10
11
12
# File 'lib/silas/agent_sdk/stream_parser.rb', line 10

def session_id
  @session_id
end

#stop_reasonObject (readonly)

Returns the value of attribute stop_reason.



10
11
12
# File 'lib/silas/agent_sdk/stream_parser.rb', line 10

def stop_reason
  @stop_reason
end

#tool_callsObject (readonly)

Returns the value of attribute tool_calls.



10
11
12
# File 'lib/silas/agent_sdk/stream_parser.rb', line 10

def tool_calls
  @tool_calls
end

#usageObject (readonly)

Returns the value of attribute usage.



10
11
12
# File 'lib/silas/agent_sdk/stream_parser.rb', line 10

def usage
  @usage
end

Instance Method Details

#ingest(line) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/silas/agent_sdk/stream_parser.rb', line 21

def ingest(line)
  line = line.to_s.strip
  return if line.empty?

  event = JSON.parse(line)
  @session_id ||= event["session_id"] if event["session_id"]

  case event["type"]
  when "system"
    @session_id ||= event["session_id"]
    yield Event.new(type: :"system.#{event['subtype']}", payload: symbolize(event))
  when "assistant"
    ingest_assistant(event) { |e| yield e }
  when "user"
    yield Event.new(type: :tool_result, payload: symbolize(event))
  when "result"
    @final_text = event["result"]
    @stop_reason = event["subtype"]
    @usage = extract_usage(event)
    # The result text usually repeats the last assistant text — only add a
    # block if it isn't already captured.
    if @final_text && @blocks.none? { |b| b["type"] == "text" && b["text"] == @final_text }
      @blocks << { "type" => "text", "text" => @final_text }
    end
    yield Event.new(type: :result, payload: symbolize(event))
  else
    yield Event.new(type: :unknown, payload: symbolize(event))
  end
rescue JSON::ParserError
  # A non-JSON line (banner, warning) — ignore.
end

#to_resultObject

tool_calls stays [] on the Result: in the engine-owned path Claude Code already executed the tools (through our MCP endpoint), so the framework loop must not try to run them again.



56
57
58
# File 'lib/silas/agent_sdk/stream_parser.rb', line 56

def to_result
  Engines::Result.new(blocks: @blocks, tool_calls: [], stop_reason: @stop_reason || "end_turn", usage: @usage)
end