diff --git a/server/pkg/agent/cursor.go b/server/pkg/agent/cursor.go index 6a4aecedd..68846607a 100644 --- a/server/pkg/agent/cursor.go +++ b/server/pkg/agent/cursor.go @@ -665,7 +665,15 @@ type cursorStreamEvent struct { // tool_result fields Output string `json:"output,omitempty"` - // result fields + // result fields. + // + // The result event reports token counts only; cursor-agent's stream-json + // carries no per-turn cost (no `total_cost_usd`, no per-step `cost`). This + // was verified against the real CLI (2026.07.20, both stream-json and json + // output) and Cursor's CLI docs. So — unlike Grok, whose turn cost xAI + // reports authoritatively — Cursor spend is estimated downstream from the + // static rate table, never carried through here. Add a cost field only + // when a real payload proves the CLI emits one. ResultText string `json:"result,omitempty"` IsError bool `json:"is_error,omitempty"` InputTokens int64 `json:"inputTokens,omitempty"` @@ -673,7 +681,6 @@ type cursorStreamEvent struct { CacheReadTokens int64 `json:"cacheReadTokens,omitempty"` CacheWriteTokens int64 `json:"cacheWriteTokens,omitempty"` Usage *cursorUsage `json:"usage,omitempty"` - TotalCost float64 `json:"total_cost_usd,omitempty"` // error fields ErrorMsg string `json:"error,omitempty"` @@ -763,6 +770,9 @@ type cursorTextPart struct { Text string `json:"text"` } +// cursorStepFinishPart carries per-step token counts. cursor-agent does not +// report a per-step cost (see cursorStreamEvent's result-fields note), so only +// tokens are parsed here. type cursorStepFinishPart struct { Tokens struct { Input int `json:"input"` @@ -771,7 +781,6 @@ type cursorStepFinishPart struct { Read int `json:"read"` } `json:"cache"` } `json:"tokens"` - Cost float64 `json:"cost"` } // ── Helpers ── diff --git a/server/pkg/agent/cursor_test.go b/server/pkg/agent/cursor_test.go index 803a70958..a14494882 100644 --- a/server/pkg/agent/cursor_test.go +++ b/server/pkg/agent/cursor_test.go @@ -446,6 +446,8 @@ func TestCursorStepFinishParsing(t *testing.T) { t.Parallel() part := cursorStepFinishPart{} + // The trailing "cost" key is ignored: cursor-agent does not report per-step + // cost, and unknown keys must not break token parsing. data := `{"tokens":{"input":500,"output":200,"cache":{"read":100}},"cost":0.01}` if err := json.Unmarshal([]byte(data), &part); err != nil { t.Fatalf("unmarshal: %v", err)