From daeec69fa51dfb3779da68fc943b29f495ba2ea2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 12:39:10 +0000 Subject: [PATCH 1/9] Initial plan From 7d52f1292e2f11e81a6aab8e2a62b38bfd6eae86 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 12:52:01 +0000 Subject: [PATCH 2/9] Add sub-agent model attribution to token usage summary Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/token_usage.go | 185 +++++++++++++++++++++++++++++++++++- pkg/cli/token_usage_test.go | 44 +++++++++ 2 files changed, 227 insertions(+), 2 deletions(-) diff --git a/pkg/cli/token_usage.go b/pkg/cli/token_usage.go index 3a0b13883e3..d6004d731ab 100644 --- a/pkg/cli/token_usage.go +++ b/pkg/cli/token_usage.go @@ -5,8 +5,10 @@ import ( "encoding/json" "errors" "fmt" + "io" "os" "path/filepath" + "regexp" "sort" "strings" "time" @@ -61,6 +63,9 @@ type TokenUsageSummary struct { TotalEffectiveTokens int `json:"total_effective_tokens" console:"header:Effective Tokens,format:number"` AmbientContext *AmbientContextMetrics `json:"ambient_context,omitempty"` ByModel map[string]*ModelTokenUsage `json:"by_model"` + SubagentModelRequests []SubagentModelRequest `json:"subagent_model_requests,omitempty"` + SubagentModelActuals []SubagentModelActual `json:"subagent_model_actuals,omitempty"` + MismatchCount int `json:"mismatch_count,omitempty"` } // ModelTokenUsage contains per-model token usage statistics @@ -90,9 +95,29 @@ type ModelTokenUsageRow struct { AvgDuration string `json:"avg_duration" console:"header:Avg Duration"` } +// SubagentModelRequest captures requested/effective model attribution for a sub-agent. +type SubagentModelRequest struct { + AgentName string `json:"agent_name"` + RequestedModel string `json:"requested_model"` + InvocationCount int `json:"invocation_count"` + EffectiveModel string `json:"effective_model,omitempty"` + ReasonCode string `json:"reason_code,omitempty"` +} + +// SubagentModelActual captures model usage observed in token-usage logs. +type SubagentModelActual struct { + Model string `json:"model"` + Provider string `json:"provider,omitempty"` + Requests int `json:"requests"` +} + // tokenUsageJSONLPath is the relative path within the firewall logs directory const tokenUsageJSONLPath = "api-proxy-logs/token-usage.jsonl" const agentUsageJSONPath = "agent_usage.json" +const modelMismatchReasonTokenUsageMissing = "TOKEN_USAGE_MISSING" +const modelMismatchReasonModelNotObserved = "REQUESTED_MODEL_NOT_OBSERVED" + +var subagentDispatchPattern = regexp.MustCompile(`([A-Za-z0-9][A-Za-z0-9._-]*)\(([A-Za-z0-9][A-Za-z0-9._:-]*)\)`) // parseTokenUsageFile parses a token-usage.jsonl file and returns the aggregated summary. // Custom weights, when non-nil, override the built-in model multipliers and token class @@ -406,7 +431,12 @@ func analyzeTokenUsage(runDir string, verbose bool) (*TokenUsageSummary, error) // Try to load custom token weights from aw_info.json for this run customWeights := extractCustomTokenWeightsFromDir(runDir) - return parseTokenUsageFile(filePath, customWeights) + summary, err := parseTokenUsageFile(filePath, customWeights) + if err != nil || summary == nil { + return summary, err + } + augmentSubagentModelAttribution(runDir, summary) + return summary, nil } agentUsagePath := findAgentUsageFile(runDir) @@ -421,7 +451,158 @@ func analyzeTokenUsage(runDir string, verbose bool) (*TokenUsageSummary, error) } customWeights := extractCustomTokenWeightsFromDir(runDir) - return parseAgentUsageFile(agentUsagePath, customWeights) + summary, err := parseAgentUsageFile(agentUsagePath, customWeights) + if err != nil || summary == nil { + return summary, err + } + augmentSubagentModelAttribution(runDir, summary) + return summary, nil +} + +func augmentSubagentModelAttribution(runDir string, summary *TokenUsageSummary) { + if summary == nil { + return + } + + requests := extractSubagentModelRequests(runDir) + if len(requests) == 0 { + return + } + + actuals := make([]SubagentModelActual, 0, len(summary.ByModel)) + observedModels := make(map[string]string, len(summary.ByModel)) + for model, usage := range summary.ByModel { + if usage == nil || model == "" { + continue + } + actuals = append(actuals, SubagentModelActual{ + Model: model, + Provider: usage.Provider, + Requests: usage.Requests, + }) + observedModels[model] = usage.Provider + } + sort.SliceStable(actuals, func(i, j int) bool { + if actuals[i].Requests != actuals[j].Requests { + return actuals[i].Requests > actuals[j].Requests + } + return actuals[i].Model < actuals[j].Model + }) + summary.SubagentModelActuals = actuals + + var fallbackEffectiveModel string + if len(observedModels) == 1 { + for model := range observedModels { + fallbackEffectiveModel = model + } + } + + requestRows := make([]SubagentModelRequest, 0, len(requests)) + mismatchCount := 0 + for _, row := range requests { + if _, ok := observedModels[row.RequestedModel]; ok { + row.EffectiveModel = row.RequestedModel + } else { + row.EffectiveModel = fallbackEffectiveModel + if len(observedModels) == 0 { + row.ReasonCode = modelMismatchReasonTokenUsageMissing + } else { + row.ReasonCode = modelMismatchReasonModelNotObserved + } + mismatchCount += row.InvocationCount + } + requestRows = append(requestRows, row) + } + summary.SubagentModelRequests = requestRows + summary.MismatchCount = mismatchCount +} + +func extractSubagentModelRequests(runDir string) []SubagentModelRequest { + agentStdioPath := findAgentStdioFile(runDir) + if agentStdioPath == "" { + return nil + } + + file, err := os.Open(agentStdioPath) + if err != nil { + return nil + } + defer file.Close() + + type key struct { + agent string + model string + } + counts := make(map[key]int) + + reader := bufio.NewReader(file) + for { + line, readErr := reader.ReadString('\n') + line = strings.TrimSpace(line) + if line != "" { + matches := subagentDispatchPattern.FindAllStringSubmatch(line, -1) + for _, m := range matches { + if len(m) < 3 { + continue + } + agentName := strings.TrimSpace(m[1]) + requestedModel := strings.TrimSpace(m[2]) + if agentName == "" || requestedModel == "" || !strings.ContainsRune(requestedModel, '-') { + continue + } + counts[key{agent: agentName, model: requestedModel}]++ + } + } + + if readErr == io.EOF { + break + } + if readErr != nil { + return nil + } + } + + rows := make([]SubagentModelRequest, 0, len(counts)) + for k, n := range counts { + rows = append(rows, SubagentModelRequest{ + AgentName: k.agent, + RequestedModel: k.model, + InvocationCount: n, + }) + } + sort.SliceStable(rows, func(i, j int) bool { + if rows[i].AgentName != rows[j].AgentName { + return rows[i].AgentName < rows[j].AgentName + } + return rows[i].RequestedModel < rows[j].RequestedModel + }) + return rows +} + +func findAgentStdioFile(runDir string) string { + primary := filepath.Join(runDir, "agent-stdio.log") + if _, err := os.Stat(primary); err == nil { + return primary + } + + var found string + if walkErr := filepath.Walk(runDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return nil + } + if info == nil || info.IsDir() { + return nil + } + if info.Name() == "agent-stdio.log" { + found = path + return filepath.SkipAll + } + return nil + }); walkErr != nil && !errors.Is(walkErr, filepath.SkipAll) { + tokenUsageLog.Printf("findAgentStdioFile walk error: %v", walkErr) + } + + return found } // extractCustomTokenWeightsFromDir reads aw_info.json from a run directory and returns diff --git a/pkg/cli/token_usage_test.go b/pkg/cli/token_usage_test.go index e4378875edf..74cbcdcd847 100644 --- a/pkg/cli/token_usage_test.go +++ b/pkg/cli/token_usage_test.go @@ -355,6 +355,50 @@ func TestAnalyzeTokenUsage(t *testing.T) { require.Contains(t, summary.ByModel, "unknown", "unknown model bucket should be present") assert.Equal(t, 60, summary.ByModel["unknown"].EffectiveTokens, "per-model effective tokens should use custom weights") }) + + t.Run("records requested sub-agent models and mismatch when token logs do not show requested model", func(t *testing.T) { + tmpDir := testutil.TempDir(t, "analyze-subagent-model-mismatch") + logsDir := filepath.Join(tmpDir, "sandbox", "firewall", "logs", "api-proxy-logs") + require.NoError(t, os.MkdirAll(logsDir, 0o755)) + tokenFile := filepath.Join(logsDir, "token-usage.jsonl") + tokenContent := `{"timestamp":"2026-04-01T17:56:38.042Z","request_id":"1","provider":"anthropic","model":"claude-sonnet-4-6","path":"/v1/messages","status":200,"streaming":true,"input_tokens":100,"output_tokens":200,"cache_read_tokens":0,"cache_write_tokens":0,"duration_ms":2500,"response_bytes":1500}` + require.NoError(t, os.WriteFile(tokenFile, []byte(tokenContent+"\n"), 0o644)) + + agentLogContent := `● Agent-alpha(claude-haiku-4.5) Get model name +● Agent-beta(claude-haiku-4.5) Get model name +● Agent-gamma(claude-haiku-4.5) Get model name` + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "agent-stdio.log"), []byte(agentLogContent), 0o644)) + + summary, err := analyzeTokenUsage(tmpDir, false) + require.NoError(t, err) + require.NotNil(t, summary) + require.Len(t, summary.SubagentModelRequests, 3) + require.Len(t, summary.SubagentModelActuals, 1) + assert.Equal(t, 3, summary.MismatchCount) + assert.Equal(t, "claude-sonnet-4-6", summary.SubagentModelActuals[0].Model) + + for _, req := range summary.SubagentModelRequests { + assert.Equal(t, "claude-haiku-4.5", req.RequestedModel) + assert.Equal(t, 1, req.InvocationCount) + assert.Equal(t, "claude-sonnet-4-6", req.EffectiveModel) + assert.Equal(t, modelMismatchReasonModelNotObserved, req.ReasonCode) + } + }) + + t.Run("records token-usage-missing reason when sub-agent model request is present but no model actuals exist", func(t *testing.T) { + tmpDir := testutil.TempDir(t, "analyze-subagent-model-token-missing") + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "agent_usage.json"), []byte(`{}`), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "agent-stdio.log"), []byte(`● Agent-alpha(claude-haiku-4.5) Get model name`), 0o644)) + + summary, err := analyzeTokenUsage(tmpDir, false) + require.NoError(t, err) + require.NotNil(t, summary) + require.Len(t, summary.SubagentModelRequests, 1) + assert.Empty(t, summary.SubagentModelActuals) + assert.Equal(t, 1, summary.MismatchCount) + assert.Equal(t, modelMismatchReasonTokenUsageMissing, summary.SubagentModelRequests[0].ReasonCode) + assert.Empty(t, summary.SubagentModelRequests[0].EffectiveModel) + }) } func TestCorrelateToolCallsWithTokenDelta(t *testing.T) { From a11311cebb74a9eb00b19b9d71df239112d6469f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 12:55:57 +0000 Subject: [PATCH 3/9] Clarify sub-agent model filter rationale Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/token_usage.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/cli/token_usage.go b/pkg/cli/token_usage.go index d6004d731ab..0888ddcc977 100644 --- a/pkg/cli/token_usage.go +++ b/pkg/cli/token_usage.go @@ -547,6 +547,8 @@ func extractSubagentModelRequests(runDir string) []SubagentModelRequest { } agentName := strings.TrimSpace(m[1]) requestedModel := strings.TrimSpace(m[2]) + // Model identifiers are expected to include hyphens (e.g. claude-haiku-4.5). + // This trims false positives from other parenthesized log fragments. if agentName == "" || requestedModel == "" || !strings.ContainsRune(requestedModel, '-') { continue } From d06eb0b00b1aa7c8bf02496c38190666be525555 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 13:23:00 +0000 Subject: [PATCH 4/9] Flag stdio-derived attribution as partial data in token usage summary Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/skills/agentic-workflows/SKILL.md | 2 -- pkg/cli/token_usage.go | 15 +++++++++++++++ pkg/cli/token_usage_test.go | 2 ++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/skills/agentic-workflows/SKILL.md b/.github/skills/agentic-workflows/SKILL.md index b4505045ca5..6bba5fae3d6 100644 --- a/.github/skills/agentic-workflows/SKILL.md +++ b/.github/skills/agentic-workflows/SKILL.md @@ -12,7 +12,6 @@ This skill is a dispatcher: identify the task type, load the matching workflow p Read only the files you need: Load these files from `github/gh-aw` (they are not available locally). - `.github/aw/agentic-chat.md` -- `.github/aw/agentic-workflows-mcp.md` - `.github/aw/asciicharts.md` - `.github/aw/campaign.md` - `.github/aw/charts-trending.md` @@ -28,7 +27,6 @@ Load these files from `github/gh-aw` (they are not available locally). - `.github/aw/github-agentic-workflows.md` - `.github/aw/github-mcp-server.md` - `.github/aw/llms.md` -- `.github/aw/mcp-clis.md` - `.github/aw/memory.md` - `.github/aw/messages.md` - `.github/aw/network.md` diff --git a/pkg/cli/token_usage.go b/pkg/cli/token_usage.go index 0888ddcc977..2e03cbadf27 100644 --- a/pkg/cli/token_usage.go +++ b/pkg/cli/token_usage.go @@ -66,6 +66,7 @@ type TokenUsageSummary struct { SubagentModelRequests []SubagentModelRequest `json:"subagent_model_requests,omitempty"` SubagentModelActuals []SubagentModelActual `json:"subagent_model_actuals,omitempty"` MismatchCount int `json:"mismatch_count,omitempty"` + Warnings []string `json:"warnings,omitempty"` } // ModelTokenUsage contains per-model token usage statistics @@ -116,6 +117,7 @@ const tokenUsageJSONLPath = "api-proxy-logs/token-usage.jsonl" const agentUsageJSONPath = "agent_usage.json" const modelMismatchReasonTokenUsageMissing = "TOKEN_USAGE_MISSING" const modelMismatchReasonModelNotObserved = "REQUESTED_MODEL_NOT_OBSERVED" +const subagentStdioWarning = "partial or incorrect data: sub-agent model requests are inferred from agent-stdio.log; use token_usage.jsonl for reliable token consumption" var subagentDispatchPattern = regexp.MustCompile(`([A-Za-z0-9][A-Za-z0-9._-]*)\(([A-Za-z0-9][A-Za-z0-9._:-]*)\)`) @@ -468,6 +470,7 @@ func augmentSubagentModelAttribution(runDir string, summary *TokenUsageSummary) if len(requests) == 0 { return } + addTokenUsageWarning(summary, subagentStdioWarning) actuals := make([]SubagentModelActual, 0, len(summary.ByModel)) observedModels := make(map[string]string, len(summary.ByModel)) @@ -517,6 +520,18 @@ func augmentSubagentModelAttribution(runDir string, summary *TokenUsageSummary) summary.MismatchCount = mismatchCount } +func addTokenUsageWarning(summary *TokenUsageSummary, warning string) { + if summary == nil || warning == "" { + return + } + for _, existing := range summary.Warnings { + if existing == warning { + return + } + } + summary.Warnings = append(summary.Warnings, warning) +} + func extractSubagentModelRequests(runDir string) []SubagentModelRequest { agentStdioPath := findAgentStdioFile(runDir) if agentStdioPath == "" { diff --git a/pkg/cli/token_usage_test.go b/pkg/cli/token_usage_test.go index 74cbcdcd847..982b4159596 100644 --- a/pkg/cli/token_usage_test.go +++ b/pkg/cli/token_usage_test.go @@ -376,6 +376,7 @@ func TestAnalyzeTokenUsage(t *testing.T) { require.Len(t, summary.SubagentModelActuals, 1) assert.Equal(t, 3, summary.MismatchCount) assert.Equal(t, "claude-sonnet-4-6", summary.SubagentModelActuals[0].Model) + require.Contains(t, summary.Warnings, subagentStdioWarning) for _, req := range summary.SubagentModelRequests { assert.Equal(t, "claude-haiku-4.5", req.RequestedModel) @@ -398,6 +399,7 @@ func TestAnalyzeTokenUsage(t *testing.T) { assert.Equal(t, 1, summary.MismatchCount) assert.Equal(t, modelMismatchReasonTokenUsageMissing, summary.SubagentModelRequests[0].ReasonCode) assert.Empty(t, summary.SubagentModelRequests[0].EffectiveModel) + require.Contains(t, summary.Warnings, subagentStdioWarning) }) } From 5b9c157cdf8782343dd379753dfe5998bff1d7ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 13:30:23 +0000 Subject: [PATCH 5/9] Plan: validate subagent attribution against workflow artifacts Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/skills/agentic-workflows/SKILL.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/skills/agentic-workflows/SKILL.md b/.github/skills/agentic-workflows/SKILL.md index 6bba5fae3d6..b4505045ca5 100644 --- a/.github/skills/agentic-workflows/SKILL.md +++ b/.github/skills/agentic-workflows/SKILL.md @@ -12,6 +12,7 @@ This skill is a dispatcher: identify the task type, load the matching workflow p Read only the files you need: Load these files from `github/gh-aw` (they are not available locally). - `.github/aw/agentic-chat.md` +- `.github/aw/agentic-workflows-mcp.md` - `.github/aw/asciicharts.md` - `.github/aw/campaign.md` - `.github/aw/charts-trending.md` @@ -27,6 +28,7 @@ Load these files from `github/gh-aw` (they are not available locally). - `.github/aw/github-agentic-workflows.md` - `.github/aw/github-mcp-server.md` - `.github/aw/llms.md` +- `.github/aw/mcp-clis.md` - `.github/aw/memory.md` - `.github/aw/messages.md` - `.github/aw/network.md` From 504c8c5f47bf12bb79cbac3ed4679f243f65e634 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 13:32:20 +0000 Subject: [PATCH 6/9] Handle subagent model aliases in attribution extraction Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/token_usage.go | 20 +++++++++++++++++--- pkg/cli/token_usage_test.go | 20 ++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/pkg/cli/token_usage.go b/pkg/cli/token_usage.go index 2e03cbadf27..459744f3c2f 100644 --- a/pkg/cli/token_usage.go +++ b/pkg/cli/token_usage.go @@ -120,6 +120,11 @@ const modelMismatchReasonModelNotObserved = "REQUESTED_MODEL_NOT_OBSERVED" const subagentStdioWarning = "partial or incorrect data: sub-agent model requests are inferred from agent-stdio.log; use token_usage.jsonl for reliable token consumption" var subagentDispatchPattern = regexp.MustCompile(`([A-Za-z0-9][A-Za-z0-9._-]*)\(([A-Za-z0-9][A-Za-z0-9._:-]*)\)`) +var subagentModelAliases = map[string]struct{}{ + "small": {}, + "large": {}, + "inherited": {}, +} // parseTokenUsageFile parses a token-usage.jsonl file and returns the aggregated summary. // Custom weights, when non-nil, override the built-in model multipliers and token class @@ -562,9 +567,7 @@ func extractSubagentModelRequests(runDir string) []SubagentModelRequest { } agentName := strings.TrimSpace(m[1]) requestedModel := strings.TrimSpace(m[2]) - // Model identifiers are expected to include hyphens (e.g. claude-haiku-4.5). - // This trims false positives from other parenthesized log fragments. - if agentName == "" || requestedModel == "" || !strings.ContainsRune(requestedModel, '-') { + if agentName == "" || !isLikelyRequestedSubagentModel(requestedModel) { continue } counts[key{agent: agentName, model: requestedModel}]++ @@ -596,6 +599,17 @@ func extractSubagentModelRequests(runDir string) []SubagentModelRequest { return rows } +func isLikelyRequestedSubagentModel(model string) bool { + if model == "" { + return false + } + if _, ok := subagentModelAliases[strings.ToLower(model)]; ok { + return true + } + // Concrete model IDs typically contain separators (e.g. claude-haiku-4.5, gpt-5-mini). + return strings.ContainsRune(model, '-') || strings.ContainsRune(model, '.') || strings.ContainsRune(model, ':') +} + func findAgentStdioFile(runDir string) string { primary := filepath.Join(runDir, "agent-stdio.log") if _, err := os.Stat(primary); err == nil { diff --git a/pkg/cli/token_usage_test.go b/pkg/cli/token_usage_test.go index 982b4159596..b6cad6d907b 100644 --- a/pkg/cli/token_usage_test.go +++ b/pkg/cli/token_usage_test.go @@ -401,6 +401,26 @@ func TestAnalyzeTokenUsage(t *testing.T) { assert.Empty(t, summary.SubagentModelRequests[0].EffectiveModel) require.Contains(t, summary.Warnings, subagentStdioWarning) }) + + t.Run("captures alias-based sub-agent model requests used by workflow subagents", func(t *testing.T) { + tmpDir := testutil.TempDir(t, "analyze-subagent-model-alias") + logsDir := filepath.Join(tmpDir, "sandbox", "firewall", "logs", "api-proxy-logs") + require.NoError(t, os.MkdirAll(logsDir, 0o755)) + tokenFile := filepath.Join(logsDir, "token-usage.jsonl") + tokenContent := `{"timestamp":"2026-04-01T17:56:38.042Z","request_id":"1","provider":"openai","model":"gpt-5-mini","path":"/v1/messages","status":200,"streaming":true,"input_tokens":100,"output_tokens":200,"cache_read_tokens":0,"cache_write_tokens":0,"duration_ms":2500,"response_bytes":1500}` + require.NoError(t, os.WriteFile(tokenFile, []byte(tokenContent+"\n"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "agent-stdio.log"), []byte(`● workflow-characterizer(small) Classify`), 0o644)) + + summary, err := analyzeTokenUsage(tmpDir, false) + require.NoError(t, err) + require.NotNil(t, summary) + require.Len(t, summary.SubagentModelRequests, 1) + assert.Equal(t, "small", summary.SubagentModelRequests[0].RequestedModel) + assert.Equal(t, "gpt-5-mini", summary.SubagentModelRequests[0].EffectiveModel) + assert.Equal(t, modelMismatchReasonModelNotObserved, summary.SubagentModelRequests[0].ReasonCode) + assert.Equal(t, 1, summary.MismatchCount) + require.Contains(t, summary.Warnings, subagentStdioWarning) + }) } func TestCorrelateToolCallsWithTokenDelta(t *testing.T) { From 8c9d79224814b4e983abe1cd4d8bb8019c71495d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 13:33:17 +0000 Subject: [PATCH 7/9] Clarify separator examples in alias model parsing comment Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/token_usage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cli/token_usage.go b/pkg/cli/token_usage.go index 459744f3c2f..21e63367fbd 100644 --- a/pkg/cli/token_usage.go +++ b/pkg/cli/token_usage.go @@ -606,7 +606,7 @@ func isLikelyRequestedSubagentModel(model string) bool { if _, ok := subagentModelAliases[strings.ToLower(model)]; ok { return true } - // Concrete model IDs typically contain separators (e.g. claude-haiku-4.5, gpt-5-mini). + // Concrete model IDs typically contain separators (e.g. claude-haiku-4.5, gpt-5-mini, bedrock:claude-sonnet-4.5). return strings.ContainsRune(model, '-') || strings.ContainsRune(model, '.') || strings.ContainsRune(model, ':') } From a60b02316fb1f96a6cce85acb146b48c904d6964 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 13:49:35 +0000 Subject: [PATCH 8/9] Revert SKILL.md changes; remove hardcoded alias map and relax model filter to support dynamic aliases Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/token_usage.go | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/pkg/cli/token_usage.go b/pkg/cli/token_usage.go index 21e63367fbd..5226a0d6859 100644 --- a/pkg/cli/token_usage.go +++ b/pkg/cli/token_usage.go @@ -120,11 +120,6 @@ const modelMismatchReasonModelNotObserved = "REQUESTED_MODEL_NOT_OBSERVED" const subagentStdioWarning = "partial or incorrect data: sub-agent model requests are inferred from agent-stdio.log; use token_usage.jsonl for reliable token consumption" var subagentDispatchPattern = regexp.MustCompile(`([A-Za-z0-9][A-Za-z0-9._-]*)\(([A-Za-z0-9][A-Za-z0-9._:-]*)\)`) -var subagentModelAliases = map[string]struct{}{ - "small": {}, - "large": {}, - "inherited": {}, -} // parseTokenUsageFile parses a token-usage.jsonl file and returns the aggregated summary. // Custom weights, when non-nil, override the built-in model multipliers and token class @@ -600,14 +595,11 @@ func extractSubagentModelRequests(runDir string) []SubagentModelRequest { } func isLikelyRequestedSubagentModel(model string) bool { - if model == "" { - return false - } - if _, ok := subagentModelAliases[strings.ToLower(model)]; ok { - return true - } - // Concrete model IDs typically contain separators (e.g. claude-haiku-4.5, gpt-5-mini, bedrock:claude-sonnet-4.5). - return strings.ContainsRune(model, '-') || strings.ContainsRune(model, '.') || strings.ContainsRune(model, ':') + // Accept any non-empty identifier matched by subagentDispatchPattern. + // Model aliases are dynamic (e.g. "small", "large", "inherited", or user-defined), + // so we do not maintain a hardcoded list – the pattern already constrains matches + // to alphanumeric identifiers with optional separator characters. + return model != "" } func findAgentStdioFile(runDir string) string { From 521409c98b29be5797e6e57c502ca26761792a50 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 13:50:57 +0000 Subject: [PATCH 9/9] Inline model check and remove isLikelyRequestedSubagentModel helper function Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/token_usage.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/pkg/cli/token_usage.go b/pkg/cli/token_usage.go index 5226a0d6859..1e26dc4108d 100644 --- a/pkg/cli/token_usage.go +++ b/pkg/cli/token_usage.go @@ -562,7 +562,7 @@ func extractSubagentModelRequests(runDir string) []SubagentModelRequest { } agentName := strings.TrimSpace(m[1]) requestedModel := strings.TrimSpace(m[2]) - if agentName == "" || !isLikelyRequestedSubagentModel(requestedModel) { + if agentName == "" || requestedModel == "" { continue } counts[key{agent: agentName, model: requestedModel}]++ @@ -594,14 +594,6 @@ func extractSubagentModelRequests(runDir string) []SubagentModelRequest { return rows } -func isLikelyRequestedSubagentModel(model string) bool { - // Accept any non-empty identifier matched by subagentDispatchPattern. - // Model aliases are dynamic (e.g. "small", "large", "inherited", or user-defined), - // so we do not maintain a hardcoded list – the pattern already constrains matches - // to alphanumeric identifiers with optional separator characters. - return model != "" -} - func findAgentStdioFile(runDir string) string { primary := filepath.Join(runDir, "agent-stdio.log") if _, err := os.Stat(primary); err == nil {