How Token Counting Estimates AI Usage and Costs in gh-aw
gh-aw estimates AI usage and costs by applying a 4-characters-per-token heuristic for rapid guardrail checks and parsing JSON usage metadata for precise per-engine token and cost extraction.
The github/gh-aw CLI tool monitors AI-assisted workflow runs by converting raw log output into measurable token counts and monetary costs. Understanding how token counting estimates AI usage and costs helps developers audit spending and prevent runaway token consumption through automated guardrails.
Two-Stage Token Estimation Pipeline
The implementation uses a dual-layer approach: a fast character-based estimate for safety checks, followed by precise JSON parsing for accurate billing metrics.
Rough Character-Based Estimation (Guardrail)
For rapid size validation before deep parsing, the estimateTokens helper in pkg/cli/mcp_logs_guardrail.go applies the industry-standard OpenAI approximation:
const CharsPerToken = 4 // ≈4 characters ≈ 1 token
func estimateTokens(text string) int {
return len(text) / CharsPerToken
}
This logic appears at lines 44-48. When the estimated count exceeds DefaultMaxMCPLogsOutputTokens (12,000), the guardrail triggers and returns a truncated JSON payload instead of processing the full log.
Precise JSON Parsing Per Engine
For accurate accounting, engine-specific parsers extract exact token counts from structured JSON embedded in logs. Each provider reports usage differently, requiring dedicated parsers in the pkg/workflow/ directory.
Copilot logs are processed by pkg/workflow/copilot_logs.go, which unmarshals JSON entries and sums InputTokens and OutputTokens from the Usage field:
totalTokenUsage := 0
for _, entry := range jsonEntries {
totalTokenUsage += entry.Usage.InputTokens + entry.Usage.OutputTokens
}
metrics.TokenUsage = totalTokenUsage
Claude logs use pkg/workflow/claude_logs.go, which handles dynamic JSON structures by checking for the usage key and extracting input_tokens and output_tokens:
if usage, ok := jsonData["usage"]; ok {
if m, ok := usage.(map[string]any); ok {
in := toInt(m["input_tokens"])
out := toInt(m["output_tokens"])
metrics.TokenUsage += in + out
}
}
Cost Extraction from Provider Logs
After determining token counts, gh-aw extracts monetary costs using the generic ExtractJSONCost function defined in pkg/workflow/metrics.go at lines 71-88. This helper searches for common cost field names across different AI providers:
func ExtractJSONCost(data map[string]any) float64 {
// Prefer explicit total_cost_usd
if v, ok := data["total_cost_usd"]; ok {
if c := ConvertToFloat(v); c > 0 {
return c
}
}
// Fallback to common aliases
costFields := []string{"cost", "price", "amount", "total_cost", "estimated_cost"}
for _, f := range costFields {
if v, ok := data[f]; ok {
if c := ConvertToFloat(v); c > 0 {
return c
}
}
}
return 0
}
Engine-specific parsers invoke this helper after unmarshaling JSON blocks, adding the result to the running EstimatedCost total.
Aggregation and Guardrail Enforcement
All per-engine metrics converge in the MetricsData struct defined in pkg/workflow/metrics.go (lines 29-41):
type MetricsData struct {
TokenUsage int
EstimatedCost float64
Turns int
// …other fields like ToolCalls, Errors
}
The guardrail logic in pkg/cli/mcp_logs_guardrail.go uses the rough token estimate to protect against oversized responses. When estimateTokens returns a value exceeding the configurable max_tokens limit (defaulting to 12,000), the system returns a guardrail JSON payload containing the token count and schema information rather than processing the full log content.
Practical Implementation Examples
Checking Log Size with the Guardrail
Use the CheckMCPLogSize wrapper to validate log size before processing:
import "github.com/github/gh-aw/pkg/cli"
func main() {
rawLog := fetchMCPLog() // string from MCP server
trimmed, guarded := cli.CheckMCPLogSize(rawLog, 0) // 0 uses default limit (12,000)
if guarded {
fmt.Println("Guardrail triggered:")
fmt.Println(trimmed) // JSON with token count & schema
return
}
fmt.Println("Log size OK, proceed with normal parsing")
}
Extracting Metrics from Copilot Logs
Parse Copilot-specific logs to extract precise token and cost metrics:
import "github.com/github/gh-aw/pkg/workflow"
func main() {
// logContent is raw text from Copilot MCP tool
metrics := workflow.ParseCopilotLog(logContent, true) // true enables verbose parsing
fmt.Printf("Tokens used: %d\n", metrics.TokenUsage)
fmt.Printf("Estimated cost: $%.4f\n", metrics.EstimatedCost)
}
CLI Workflow with JSON Output
The complete pipeline from command invocation to metric extraction:
# Fetch logs and output structured metrics
gh aw logs --workflow my-workflow --run 123456 --json
This command executes:
- Guardrail validation using
estimateTokensagainst the 12,000 token default - Engine detection and specific parsing (Copilot, Claude, etc.)
- Cost extraction via
ExtractJSONCost - JSON serialization of the
MetricsDatastruct containingtoken_usageandestimated_cost
Key Implementation Files
| Purpose | File Path |
|---|---|
| Rough token estimate & guardrail logic | pkg/cli/mcp_logs_guardrail.go |
| Generic cost extraction helper | pkg/workflow/metrics.go |
| Copilot log parsing | pkg/workflow/copilot_logs.go |
| Claude log parsing | pkg/workflow/claude_logs.go |
| Metrics aggregation struct | pkg/workflow/metrics.go |
| CLI guardrail integration | pkg/cli/logs_report.go |
| Copilot token extraction tests | pkg/workflow/copilot_token_parsing_test.go |
| Claude cost handling tests | pkg/workflow/claude_logs_test.go |
Summary
- gh-aw employs a two-stage approach to estimate AI usage: a fast character-based heuristic (4 characters per token) for guardrail checks, and precise JSON parsing for accurate billing metrics.
- The guardrail in
pkg/cli/mcp_logs_guardrail.goprevents processing logs exceeding 12,000 tokens by default, protecting against runaway costs. - Engine-specific parsers in
pkg/workflow/copilot_logs.goandpkg/workflow/claude_logs.goextract exactinput_tokensandoutput_tokensfrom structured JSON logs. - The cost extraction helper
ExtractJSONCostinpkg/workflow/metrics.gonormalizes provider-specific cost fields into a unified USD value. - All metrics aggregate into the MetricsData struct, exposing
TokenUsageandEstimatedCostto CLI commands and audit tools.
Frequently Asked Questions
How accurate is the 4-characters-per-token estimate?
The 4-characters-per-token heuristic in estimateTokens follows the OpenAI rule of thumb for English text. While sufficient for guardrail protection against oversized logs, it typically overestimates actual token counts for code or structured JSON. For billing accuracy, gh-aw relies on provider-reported JSON usage fields rather than this rough estimate.
What happens when the token limit is exceeded?
When CheckMCPLogSize detects that the estimated token count exceeds the configurable max_tokens limit (defaulting to 12,000), it immediately returns a guardrail JSON payload containing the token count and schema metadata. The caller receives this truncated response instead of the full log content, preventing excessive API charges and memory consumption.
Which AI providers does gh-aw support for cost tracking?
Currently, gh-aw implements dedicated parsers for GitHub Copilot and Claude, located in pkg/workflow/copilot_logs.go and pkg/workflow/claude_logs.go respectively. The generic ExtractJSONCost helper supports any provider embedding standard cost fields like total_cost_usd, cost, or price in JSON log fragments, making the system extensible to additional engines.
Can I adjust the token limit for the guardrail?
Yes, the CheckMCPLogSize function accepts a maxTokens parameter. Passing 0 uses the default limit of 12,000 tokens defined as DefaultMaxMCPLogsOutputTokens in pkg/cli/mcp_logs_guardrail.go. You can specify any positive integer to customize the threshold based on your infrastructure constraints or cost sensitivity.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →