fix(pi review): report cumulative token usage · Entire

fix(pi review): report cumulative token usage

14a45e8→main·· dipree·2w ago·2 files·+78 added/-8 removed

Sessions

3f7ff1353636View transcript

[?
Merge Main and Resolve Token Usage ConflictsPi·GPT-5.5·2 steps](/content/gh/entireio/cli/session/019f125d-fea3-7997-8e34-5fe72b63e5ef#timeline-3f7ff1353636/index.html)

Changes

2

49 unmodified lines

50
51
52
53
54
55
56
57
22 unmodified lines

80
81
82
81
83
84
85
86
12 unmodified lines

99
100
101
100
102
103
104
105
66 unmodified lines

172
173
174
173
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
175
191
192
177
178
179
180
193
194
195
196
197
198
199
200
201
202

49 unmodified lines

scanner := bufio.NewScanner(r)
        scanner.Buffer(make([]byte, min(1024*1024, piReviewMaxScannerBuf)), piReviewMaxScannerBuf)
        messageIDsWithTextDelta := map[string]struct{}{}
        messageIDsWithUsage := map[string]struct{}{}
        tokens := reviewtypes.Tokens{}
        finished := false
        success := true

22 unmodified lines

success = false
        }
        if env.Message.Usage != nil {
            out <- piReviewTokens(env.Message.Usage)
            emitPiReviewTokens(out, env, &tokens, messageIDsWithUsage)
        }
        if _, sawDelta := messageIDsWithTextDelta[env.MessageID()]; !sawDelta {
            if text := piReviewMessageText(env.Message.Content); text != "" {
12 unmodified lines

success = false
        }
        if env.Message.Usage != nil {
            out <- piReviewTokens(env.Message.Usage)
            emitPiReviewTokens(out, env, &tokens, messageIDsWithUsage)
        }
        case "agent_end":
            finished = true
66 unmodified lines

CacheWrite int `json:"cacheWrite"`
}

func piReviewTokens(usage *piReviewUsage) reviewtypes.Tokens {
func emitPiReviewTokens(out chan<- reviewtypes.Event, env piReviewEnvelope, total *reviewtypes.Tokens, seen map[string]struct{}) {
    if env.Message.Usage == nil || total == nil {
        return
    }
    if key := env.MessageID(); key != "" {
        if _, ok := seen[key]; ok {
            return
        }
        seen[key] = struct{}{}
    }
    *total = addPiReviewTokens(*total, env.Message.Usage)
    out <- *total
}

func addPiReviewTokens(total reviewtypes.Tokens, usage *piReviewUsage) reviewtypes.Tokens {
    if usage == nil {
        return reviewtypes.Tokens{}
        return total
    }
    return reviewtypes.Tokens{
        In:  usage.Input + usage.CacheRead + usage.CacheWrite,
        Out: usage.Output,
    }
total.In += usage.Input + usage.CacheRead + usage.CacheWrite
    total.Out += usage.Output
    return total
}

func piReviewTokens(usage *piReviewUsage) reviewtypes.Tokens {
    return addPiReviewTokens(reviewtypes.Tokens{}, usage)
}

func piReviewJSONArg(raw json.RawMessage) string {

Mcmd/entire/cli/agent/pi/reviewer.go+27/-8

117 unmodified lines

118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174

117 unmodified lines

}
}

func TestPiReviewer_ParseTokensAreCumulative(t *testing.T) {
t.Parallel()
    input := strings.Join([]string{
        `{"type":"agent_start"}`,
        `{"type":"message_end","id":"m1","message":{"id":"m1","role":"assistant","usage":{"input":100,"output":50,"cacheRead":10,"cacheWrite":5},"stopReason":"toolUse"}}`,
        `{"type":"message_end","id":"m2","message":{"id":"m2","role":"assistant","usage":{"input":200,"output":30,"cacheRead":0,"cacheWrite":0},"stopReason":"stop"}}`,
        `{"type":"agent_end"}`,
    }, "\n")

events := collectPiReviewEvents(input)
    var tokens []reviewtypes.Tokens
    for _, ev := range events {
        if tok, ok := ev.(reviewtypes.Tokens); ok {
            tokens = append(tokens, tok)
        }
    }
    if len(tokens) != 2 {
        t.Fatalf("token events = %d, want 2: %#v", len(tokens), events)
    }
    if got := tokens[0]; got.In != 115 || got.Out != 50 {
        t.Fatalf("first Tokens = %#v, want In=115 Out=50", got)
    }
    if got := tokens[1]; got.In != 315 || got.Out != 80 {
        t.Fatalf("final Tokens = %#v, want In=315 Out=80", got)
    }
}

func TestPiReviewer_ParseTokensDedupesTurnEndForSameMessage(t *testing.T) {
t.Parallel()
    input := strings.Join([]string{
        `{"type":"agent_start"}`,
        `{"type":"message_end","id":"m1","message":{"id":"m1","role":"assistant","usage":{"input":10,"output":5},"stopReason":"stop"}}`,
        `{"type":"turn_end","id":"m1","message":{"id":"m1","role":"assistant","usage":{"input":10,"output":5},"stopReason":"stop"}}`,
        `{"type":"agent_end"}`,
    }, "\n")

events := collectPiReviewEvents(input)
    var tokens []reviewtypes.Tokens
    for _, ev := range events {
        if tok, ok := ev.(reviewtypes.Tokens); ok {
            tokens = append(tokens, tok)
        }
    }
    if len(tokens) != 1 {
        t.Fatalf("token events = %d, want 1: %#v", len(tokens), events)
    }
    if got := tokens[0]; got.In != 10 || got.Out != 5 {
        t.Fatalf("Tokens = %#v, want In=10 Out=5", got)
    }
}

func collectPiReviewEvents(input string) []reviewtypes.Event {
    ch := parsePiReviewOutput(strings.NewReader(input))
    var events []reviewtypes.Event