@@ -6,6 +6,7 @@ import (
7 "charm.land/catwalk/pkg/catwalk"
7 "charm.land/catwalk/pkg/catwalk"
8 "charm.land/fantasy"
8 "charm.land/fantasy"
9+ "github.com/charmbracelet/crush/internal/message"
9 "github.com/charmbracelet/crush/internal/session"
10 "github.com/charmbracelet/crush/internal/session"
10 "github.com/stretchr/testify/require"
11 "github.com/stretchr/testify/require"
@@ -232,7 +233,7 @@ func TestUpdateSessionUsageKeepsCountersForZeroUsage(t *testing.T) {
232 require.Equal(t, int64(456), currentSession.CompletionTokens)
233 require.Equal(t, int64(456), currentSession.CompletionTokens)
235−func TestUpdateSessionUsageReplacesCountersForPartialUsage(t *testing.T) {
236+func TestUpdateSessionUsagePreservesOmittedCountersForPartialUsage(t *testing.T) {
236 t.Parallel()
237 t.Parallel()
238 agent := &sessionAgent{}
239 agent := &sessionAgent{}
@@ -247,7 +248,77 @@ func TestUpdateSessionUsageReplacesCountersForPartialUsage(t *testing.T) {
247 agent.updateSessionUsage(model, currentSession, usage, nil, false)
248 agent.updateSessionUsage(model, currentSession, usage, nil, false)
249 require.Equal(t, int64(789), currentSession.PromptTokens)
250 require.Equal(t, int64(789), currentSession.PromptTokens)
250− require.Zero(t, currentSession.CompletionTokens)
251+ require.Equal(t, int64(456), currentSession.CompletionTokens)
254+func TestUpdateSessionUsagePreservesCountersForTotalOnlyUsage(t *testing.T) {
257+ agent := &sessionAgent{}
258+ currentSession := &session.Session{
261+ CompletionTokens: 456,
263+ model := Model{CatwalkCfg: catwalk.Model{CostPer1MIn: 10, CostPer1MOut: 20}}
264+ usage := fantasy.Usage{TotalTokens: 100}
266+ agent.updateSessionUsage(model, currentSession, usage, nil, false)
268+ require.Equal(t, int64(123), currentSession.PromptTokens)
269+ require.Equal(t, int64(456), currentSession.CompletionTokens)
272+func TestUpdateSessionUsagePreservesPromptForOutputOnlyUsage(t *testing.T) {
275+ agent := &sessionAgent{}
276+ currentSession := &session.Session{
279+ CompletionTokens: 456,
281+ model := Model{CatwalkCfg: catwalk.Model{CostPer1MIn: 10, CostPer1MOut: 20}}
282+ usage := fantasy.Usage{OutputTokens: 50}
284+ agent.updateSessionUsage(model, currentSession, usage, nil, false)
286+ require.Equal(t, int64(123), currentSession.PromptTokens)
287+ require.Equal(t, int64(50), currentSession.CompletionTokens)
290+func TestUpdateSessionUsageKeepsCountersForEstimatedZeroUsage(t *testing.T) {
293+ agent := &sessionAgent{}
294+ currentSession := &session.Session{
297+ CompletionTokens: 456,
300+ model := Model{CatwalkCfg: catwalk.Model{CostPer1MIn: 10, CostPer1MOut: 20}}
302+ agent.updateSessionUsage(model, currentSession, fantasy.Usage{}, nil, true)
304+ require.Equal(t, 1.25, currentSession.Cost)
305+ require.Equal(t, int64(123), currentSession.PromptTokens)
306+ require.Equal(t, int64(456), currentSession.CompletionTokens)
309+func TestSummaryCompletionTokens(t *testing.T) {
312+ summaryMessage := message.Message{
313+ Parts: []message.ContentPart{
314+ message.TextContent{Text: "summary text"},
315+ message.ReasoningContent{Thinking: "reasoning text"},
319+ require.Equal(t, int64(42), summaryCompletionTokens(fantasy.Usage{OutputTokens: 42}, summaryMessage))
320+ require.Equal(t, approxTokenCount("summary text")+approxTokenCount("reasoning text"), summaryCompletionTokens(fantasy.Usage{}, summaryMessage))
321+ require.Zero(t, summaryCompletionTokens(fantasy.Usage{}, message.Message{}))
253func TestUpdateSessionUsageAddsProviderCost(t *testing.T) {
324func TestUpdateSessionUsageAddsProviderCost(t *testing.T) {