merge: run reporting — token accounting + populate empty result
This commit is contained in:
@@ -44,6 +44,14 @@ public sealed class StreamAnalyzer
|
|||||||
_structuredOutputJson = structuredProp.ToString();
|
_structuredOutputJson = structuredProp.ToString();
|
||||||
if (root.TryGetProperty("session_id", out var sessionProp))
|
if (root.TryGetProperty("session_id", out var sessionProp))
|
||||||
_sessionId = sessionProp.GetString();
|
_sessionId = sessionProp.GetString();
|
||||||
|
// Authoritative token totals live on the result event.
|
||||||
|
if (root.TryGetProperty("usage", out var resultUsage))
|
||||||
|
{
|
||||||
|
if (resultUsage.TryGetProperty("input_tokens", out var inp))
|
||||||
|
_tokensIn = inp.GetInt32();
|
||||||
|
if (resultUsage.TryGetProperty("output_tokens", out var outp))
|
||||||
|
_tokensOut = outp.GetInt32();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "assistant":
|
case "assistant":
|
||||||
@@ -66,7 +74,7 @@ public sealed class StreamAnalyzer
|
|||||||
|
|
||||||
public StreamResult GetResult() => new()
|
public StreamResult GetResult() => new()
|
||||||
{
|
{
|
||||||
ResultMarkdown = _resultMarkdown,
|
ResultMarkdown = FallbackResult(),
|
||||||
StructuredOutputJson = _structuredOutputJson,
|
StructuredOutputJson = _structuredOutputJson,
|
||||||
SessionId = _sessionId,
|
SessionId = _sessionId,
|
||||||
TurnCount = _turnCount,
|
TurnCount = _turnCount,
|
||||||
@@ -75,6 +83,20 @@ public sealed class StreamAnalyzer
|
|||||||
ApiRetryCount = _apiRetryCount,
|
ApiRetryCount = _apiRetryCount,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private string? FallbackResult()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_resultMarkdown)) return _resultMarkdown;
|
||||||
|
if (_structuredOutputJson is null) return _resultMarkdown;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var doc = JsonDocument.Parse(_structuredOutputJson);
|
||||||
|
if (doc.RootElement.TryGetProperty("summary", out var s))
|
||||||
|
return s.GetString();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return _structuredOutputJson;
|
||||||
|
}
|
||||||
|
|
||||||
private void TryAccumulateUsage(JsonElement root)
|
private void TryAccumulateUsage(JsonElement root)
|
||||||
{
|
{
|
||||||
if (!root.TryGetProperty("event", out var eventProp)) return;
|
if (!root.TryGetProperty("event", out var eventProp)) return;
|
||||||
|
|||||||
@@ -79,4 +79,45 @@ public sealed class StreamAnalyzerTests
|
|||||||
Assert.Null(result.ResultMarkdown);
|
Assert.Null(result.ResultMarkdown);
|
||||||
Assert.Null(result.SessionId);
|
Assert.Null(result.SessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Token_Usage_From_Result_Event()
|
||||||
|
{
|
||||||
|
var analyzer = new StreamAnalyzer();
|
||||||
|
analyzer.ProcessLine("""{"type":"result","result":"done","session_id":"s1","usage":{"input_tokens":150,"output_tokens":75,"cache_read_input_tokens":0}}""");
|
||||||
|
var result = analyzer.GetResult();
|
||||||
|
Assert.Equal(150, result.TokensIn);
|
||||||
|
Assert.Equal(75, result.TokensOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Result_Usage_Overrides_Stream_Event_Accumulation()
|
||||||
|
{
|
||||||
|
var analyzer = new StreamAnalyzer();
|
||||||
|
analyzer.ProcessLine("""{"type":"stream_event","event":{"type":"message_start","message":{"usage":{"input_tokens":10,"output_tokens":5}}}}""");
|
||||||
|
analyzer.ProcessLine("""{"type":"result","result":"done","session_id":"s1","usage":{"input_tokens":200,"output_tokens":90}}""");
|
||||||
|
var result = analyzer.GetResult();
|
||||||
|
Assert.Equal(200, result.TokensIn);
|
||||||
|
Assert.Equal(90, result.TokensOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Empty_Result_Falls_Back_To_Structured_Output_Summary()
|
||||||
|
{
|
||||||
|
var analyzer = new StreamAnalyzer();
|
||||||
|
analyzer.ProcessLine("""{"type":"result","result":"","structured_output":{"summary":"Task completed successfully.","data":{}},"session_id":"s1"}""");
|
||||||
|
var result = analyzer.GetResult();
|
||||||
|
Assert.Equal("Task completed successfully.", result.ResultMarkdown);
|
||||||
|
Assert.Contains("summary", result.StructuredOutputJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Empty_Result_Falls_Back_To_Full_Json_When_No_Summary()
|
||||||
|
{
|
||||||
|
var analyzer = new StreamAnalyzer();
|
||||||
|
analyzer.ProcessLine("""{"type":"result","result":"","structured_output":{"output":"42"},"session_id":"s1"}""");
|
||||||
|
var result = analyzer.GetResult();
|
||||||
|
Assert.Contains("output", result.ResultMarkdown);
|
||||||
|
Assert.Contains("42", result.ResultMarkdown);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user