@@ -22,29 +22,45 @@ const (
2222
2323// StreamItem represents a single item in the output stream
2424type StreamItem struct {
25- Type StreamItemType
26- SessionID string // which session this belongs to
27- AgentID string // empty for main session, "abc123" for subagents
28- AgentName string // human-readable name derived from agent type or ID
29- Timestamp time.Time
30- Content string
31- ToolName string // for tool_input/tool_output
32- ToolID string // to correlate input with output
25+ Type StreamItemType
26+ SessionID string // which session this belongs to
27+ AgentID string // empty for main session, "abc123" for subagents
28+ AgentName string // human-readable name derived from agent type or ID
29+ Timestamp time.Time
30+ Content string
31+ ToolName string // for tool_input/tool_output
32+ ToolID string // to correlate input with output
33+ DurationMs int64 // tool execution duration in ms (0 = not available)
34+ InputTokens int64 // usage.input_tokens from assistant messages
35+ OutputTokens int64 // usage.output_tokens from assistant messages
3336}
3437
3538// RawMessage represents a line from the JSONL file
3639type RawMessage struct {
37- Type string `json:"type"`
38- AgentID string `json:"agentId,omitempty"`
39- SessionID string `json:"sessionId"`
40- Timestamp string `json:"timestamp"`
41- Message json.RawMessage `json:"message"`
40+ Type string `json:"type"`
41+ AgentID string `json:"agentId,omitempty"`
42+ SessionID string `json:"sessionId"`
43+ Timestamp string `json:"timestamp"`
44+ Message json.RawMessage `json:"message"`
45+ ToolUseResult json.RawMessage `json:"toolUseResult,omitempty"`
46+ }
47+
48+ // RawToolUseResult represents the toolUseResult field on user messages
49+ type RawToolUseResult struct {
50+ DurationMs int64 `json:"durationMs"`
4251}
4352
4453// AssistantMessage represents the message field for assistant responses
4554type AssistantMessage struct {
4655 Role string `json:"role"`
4756 Content []ContentBlock `json:"content"`
57+ Usage * UsageInfo `json:"usage,omitempty"`
58+ }
59+
60+ // UsageInfo represents token usage from assistant messages
61+ type UsageInfo struct {
62+ InputTokens int64 `json:"input_tokens"`
63+ OutputTokens int64 `json:"output_tokens"`
4864}
4965
5066// ContentBlock represents a single content item in assistant response
@@ -159,6 +175,12 @@ func parseAssistantMessage(raw RawMessage, timestamp time.Time) []StreamItem {
159175 }
160176 }
161177
178+ // Attach token usage to the first item only
179+ if len (items ) > 0 && msg .Usage != nil {
180+ items [0 ].InputTokens = msg .Usage .InputTokens
181+ items [0 ].OutputTokens = msg .Usage .OutputTokens
182+ }
183+
162184 return items
163185}
164186
@@ -171,6 +193,15 @@ func parseUserMessage(raw RawMessage, timestamp time.Time) []StreamItem {
171193 return nil
172194 }
173195
196+ // Parse toolUseResult for duration
197+ var durationMs int64
198+ if len (raw .ToolUseResult ) > 0 {
199+ var tur RawToolUseResult
200+ if err := json .Unmarshal (raw .ToolUseResult , & tur ); err == nil {
201+ durationMs = tur .DurationMs
202+ }
203+ }
204+
174205 var items []StreamItem
175206 agentName := "Main"
176207 if raw .AgentID != "" {
@@ -180,12 +211,13 @@ func parseUserMessage(raw RawMessage, timestamp time.Time) []StreamItem {
180211 for _ , result := range results {
181212 if result .Type == "tool_result" {
182213 items = append (items , StreamItem {
183- Type : TypeToolOutput ,
184- AgentID : raw .AgentID ,
185- AgentName : agentName ,
186- Timestamp : timestamp ,
187- Content : extractToolResultContent (result .Content ),
188- ToolID : result .ToolUseID ,
214+ Type : TypeToolOutput ,
215+ AgentID : raw .AgentID ,
216+ AgentName : agentName ,
217+ Timestamp : timestamp ,
218+ Content : extractToolResultContent (result .Content ),
219+ ToolID : result .ToolUseID ,
220+ DurationMs : durationMs ,
189221 })
190222 }
191223 }
0 commit comments