Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/patch-consolidate-format-number.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions pkg/cli/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ func generateAuditReport(processedRun ProcessedRun, metrics LogMetrics) string {
// Metrics
report.WriteString("## Metrics\n\n")
if run.TokenUsage > 0 {
report.WriteString(fmt.Sprintf("- **Token Usage**: %s\n", formatNumber(run.TokenUsage)))
report.WriteString(fmt.Sprintf("- **Token Usage**: %s\n", console.FormatNumber(run.TokenUsage)))
}
if run.EstimatedCost > 0 {
report.WriteString(fmt.Sprintf("- **Estimated Cost**: $%.3f\n", run.EstimatedCost))
Expand Down Expand Up @@ -521,11 +521,11 @@ func generateAuditReport(processedRun ProcessedRun, metrics LogMetrics) string {
tool := toolStats[name]
inputStr := "N/A"
if tool.MaxInputSize > 0 {
inputStr = formatNumber(tool.MaxInputSize)
inputStr = console.FormatNumber(tool.MaxInputSize)
}
outputStr := "N/A"
if tool.MaxOutputSize > 0 {
outputStr = formatNumber(tool.MaxOutputSize)
outputStr = console.FormatNumber(tool.MaxOutputSize)
}
durationStr := "N/A"
if tool.MaxDuration > 0 {
Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/audit_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,11 +492,11 @@ func renderToolUsageTable(toolUsage []ToolUsageInfo) {
for _, tool := range toolUsage {
inputStr := "N/A"
if tool.MaxInputSize > 0 {
inputStr = formatNumber(tool.MaxInputSize)
inputStr = console.FormatNumber(tool.MaxInputSize)
}
outputStr := "N/A"
if tool.MaxOutputSize > 0 {
outputStr = formatNumber(tool.MaxOutputSize)
outputStr = console.FormatNumber(tool.MaxOutputSize)
}
durationStr := "N/A"
if tool.MaxDuration != "" {
Expand Down
47 changes: 2 additions & 45 deletions pkg/cli/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1723,7 +1723,7 @@ func displayLogsOverview(processedRuns []ProcessedRun, verbose bool) {
// Format tokens
tokensStr := ""
if run.TokenUsage > 0 {
tokensStr = formatNumber(run.TokenUsage)
tokensStr = console.FormatNumber(run.TokenUsage)
totalTokens += run.TokenUsage
}

Expand Down Expand Up @@ -1799,7 +1799,7 @@ func displayLogsOverview(processedRuns []ProcessedRun, verbose bool) {
"",
"",
timeutil.FormatDuration(totalDuration),
formatNumber(totalTokens),
console.FormatNumber(totalTokens),
fmt.Sprintf("%.3f", totalCost),
fmt.Sprintf("%d", totalTurns),
fmt.Sprintf("%d", totalErrors),
Expand Down Expand Up @@ -1838,49 +1838,6 @@ func ExtractLogMetricsFromRun(processedRun ProcessedRun) workflow.LogMetrics {
return metrics
}

// formatNumber formats large numbers in a human-readable way (e.g., "1k", "1.2k", "1.12M")
func formatNumber(n int) string {
if n == 0 {
return "0"
}

f := float64(n)

if f < 1000 {
return fmt.Sprintf("%d", n)
} else if f < 1000000 {
// Format as thousands (k)
k := f / 1000
if k >= 100 {
return fmt.Sprintf("%.0fk", k)
} else if k >= 10 {
return fmt.Sprintf("%.1fk", k)
} else {
return fmt.Sprintf("%.2fk", k)
}
} else if f < 1000000000 {
// Format as millions (M)
m := f / 1000000
if m >= 100 {
return fmt.Sprintf("%.0fM", m)
} else if m >= 10 {
return fmt.Sprintf("%.1fM", m)
} else {
return fmt.Sprintf("%.2fM", m)
}
} else {
// Format as billions (B)
b := f / 1000000000
if b >= 100 {
return fmt.Sprintf("%.0fB", b)
} else if b >= 10 {
return fmt.Sprintf("%.1fB", b)
} else {
return fmt.Sprintf("%.2fB", b)
}
}
}

// findAgentOutputFile searches for a file named agent_output.json within the logDir tree.
// Returns the first path found (depth-first) and a boolean indicating success.
func findAgentOutputFile(logDir string) (string, bool) {
Expand Down
5 changes: 3 additions & 2 deletions pkg/cli/logs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"testing"

"github.com/githubnext/gh-aw/pkg/console"
"github.com/githubnext/gh-aw/pkg/workflow"
"github.com/githubnext/gh-aw/pkg/workflow/pretty"
)
Expand Down Expand Up @@ -68,9 +69,9 @@ func TestFormatNumber(t *testing.T) {
}

for _, test := range tests {
result := formatNumber(test.input)
result := console.FormatNumber(test.input)
if result != test.expected {
t.Errorf("formatNumber(%d) = %s, expected %s", test.input, result, test.expected)
t.Errorf("console.FormatNumber(%d) = %s, expected %s", test.input, result, test.expected)
}
}
}
Expand Down
50 changes: 33 additions & 17 deletions pkg/console/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,22 +437,22 @@ func formatFieldValueWithTag(val reflect.Value, tag consoleTag) string {
if val.CanInterface() {
switch v := val.Interface().(type) {
case int:
return formatNumberForDisplay(v)
return FormatNumber(v)
case int64:
return formatNumberForDisplay(int(v))
return FormatNumber(int(v))
case int32:
return formatNumberForDisplay(int(v))
return FormatNumber(int(v))
case uint:
return formatNumberForDisplay(int(v))
return FormatNumber(int(v))
case uint64:
return formatNumberForDisplay(int(v))
return FormatNumber(int(v))
case uint32:
return formatNumberForDisplay(int(v))
return FormatNumber(int(v))
}
}
// Fallback: try to parse from baseValue if it's an integer
if val.Kind() >= reflect.Int && val.Kind() <= reflect.Uint64 {
return formatNumberForDisplay(int(val.Int()))
return FormatNumber(int(val.Int()))
}
case "cost":
// Format as currency with $ prefix
Expand Down Expand Up @@ -539,8 +539,8 @@ func formatFileSize(size int64) string {
return fmt.Sprintf("%.1f %s", float64(size)/float64(div), units[exp])
}

// formatNumberForDisplay formats large numbers in a human-readable way (e.g., "1k", "1.2M")
func formatNumberForDisplay(n int) string {
// FormatNumber formats large numbers in a human-readable way (e.g., "1k", "1.2k", "1.12M")
func FormatNumber(n int) string {
if n == 0 {
return "0"
}
Expand All @@ -551,17 +551,33 @@ func formatNumberForDisplay(n int) string {
return fmt.Sprintf("%d", n)
} else if f < 1000000 {
// Format as thousands (k)
if f < 10000 {
return fmt.Sprintf("%.1fk", f/1000)
k := f / 1000
if k >= 100 {
return fmt.Sprintf("%.0fk", k)
} else if k >= 10 {
return fmt.Sprintf("%.1fk", k)
} else {
return fmt.Sprintf("%.2fk", k)
}
return fmt.Sprintf("%.0fk", f/1000)
} else if f < 1000000000 {
// Format as millions (M)
if f < 10000000 {
return fmt.Sprintf("%.2fM", f/1000000)
m := f / 1000000
if m >= 100 {
return fmt.Sprintf("%.0fM", m)
} else if m >= 10 {
return fmt.Sprintf("%.1fM", m)
} else {
return fmt.Sprintf("%.2fM", m)
}
} else {
// Format as billions (B)
b := f / 1000000000
if b >= 100 {
return fmt.Sprintf("%.0fB", b)
} else if b >= 10 {
return fmt.Sprintf("%.1fB", b)
} else {
return fmt.Sprintf("%.2fB", b)
}
return fmt.Sprintf("%.1fM", f/1000000)
}
// Format as billions (B)
return fmt.Sprintf("%.2fB", f/1000000000)
}
8 changes: 4 additions & 4 deletions pkg/console/render_formatting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,13 @@ func TestFormatFieldValueWithTag_NumberFormat(t *testing.T) {
expected string
}{
{"int - small", 500, "500"},
{"int - 1k", 1000, "1.0k"},
{"int - 1.5k", 1500, "1.5k"},
{"int - 1k", 1000, "1.00k"},
{"int - 1.5k", 1500, "1.50k"},
{"int - 1M", 1000000, "1.00M"},
{"int - 5M", 5000000, "5.00M"},
{"int64", int64(250000), "250k"},
{"int32", int32(1500), "1.5k"},
{"uint", uint(2000), "2.0k"},
{"int32", int32(1500), "1.50k"},
{"uint", uint(2000), "2.00k"},
{"uint64", uint64(3500000), "3.50M"},
{"uint32", uint32(750), "750"},
}
Expand Down
59 changes: 57 additions & 2 deletions pkg/console/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,62 @@ func TestFormatTag_InTable(t *testing.T) {
}

// Should format small output size
if !strings.Contains(output, "1.5k") {
t.Errorf("Output should contain formatted number '1.5k', got:\n%s", output)
if !strings.Contains(output, "1.50k") {
t.Errorf("Output should contain formatted number '1.50k', got:\n%s", output)
}
}

func TestFormatNumber(t *testing.T) {
tests := []struct {
input int
expected string
}{
// Zero
{0, "0"},
// Small numbers (under 1000)
{5, "5"},
{42, "42"},
{999, "999"},
// Thousands (1k-999k)
{1000, "1.00k"}, // Under 10k: 2 decimal places
{1200, "1.20k"},
{1234, "1.23k"},
{9999, "10.00k"}, // 9999/1000 = 9.999 -> formats as 10.00k
{10000, "10.0k"}, // 10k-99k: 1 decimal place
{12000, "12.0k"},
{12300, "12.3k"},
{99999, "100.0k"}, // 99999/1000 = 99.999 -> formats as 100.0k
{100000, "100k"}, // 100k+: no decimal places
{123000, "123k"},
{999999, "1000k"}, // 999999/1000 = 999.999 -> formats as 1000k
// Millions (1M-999M)
{1000000, "1.00M"}, // Under 10M: 2 decimal places
{1200000, "1.20M"},
{1234567, "1.23M"},
{9999999, "10.00M"}, // 9999999/1000000 = 9.999999 -> formats as 10.00M
{10000000, "10.0M"}, // 10M-99M: 1 decimal place
{12000000, "12.0M"},
{12300000, "12.3M"},
{99999999, "100.0M"}, // 99999999/1000000 = 99.999999 -> formats as 100.0M
{100000000, "100M"}, // 100M+: no decimal places
{123000000, "123M"},
{999999999, "1000M"}, // 999999999/1000000 = 999.999999 -> formats as 1000M
// Billions (1B+)
{1000000000, "1.00B"}, // Under 10B: 2 decimal places
{1200000000, "1.20B"},
{1234567890, "1.23B"},
{9999999999, "10.00B"}, // 9999999999/1000000000 = 9.999999999 -> formats as 10.00B
{10000000000, "10.0B"}, // 10B-99B: 1 decimal place
{12000000000, "12.0B"},
{99999999999, "100.0B"}, // 99999999999/1000000000 = 99.999999999 -> formats as 100.0B
{100000000000, "100B"}, // 100B+: no decimal places
{123000000000, "123B"},
}

for _, test := range tests {
result := FormatNumber(test.input)
if result != test.expected {
t.Errorf("FormatNumber(%d) = %s, expected %s", test.input, result, test.expected)
}
}
}
Loading