@@ -25,10 +25,12 @@ func TestMetrics_Interception(t *testing.T) {
2525 name string
2626 fixture []byte
2727 path string
28+ headers http.Header
2829 expectStatus string
2930 expectModel string
3031 expectRoute string
3132 expectProvider string
33+ expectClient aibridge.Client
3234 allowOverflow bool // error fixtures may cause retries
3335 }{
3436 {
@@ -39,72 +41,98 @@ func TestMetrics_Interception(t *testing.T) {
3941 expectModel : "claude-sonnet-4-0" ,
4042 expectRoute : "/v1/messages" ,
4143 expectProvider : config .ProviderAnthropic ,
44+ expectClient : aibridge .ClientUnknown ,
4245 },
4346 {
4447 name : "ant_error" ,
4548 fixture : fixtures .AntNonStreamError ,
4649 path : pathAnthropicMessages ,
50+ headers : http.Header {"User-Agent" : []string {"kilo-code/1.2.3" }},
4751 expectStatus : metrics .InterceptionCountStatusFailed ,
4852 expectModel : "claude-sonnet-4-0" ,
4953 expectRoute : "/v1/messages" ,
5054 expectProvider : config .ProviderAnthropic ,
55+ expectClient : aibridge .ClientKilo ,
5156 allowOverflow : true ,
5257 },
58+ {
59+ name : "ant_simple_claude_code" ,
60+ fixture : fixtures .AntSimple ,
61+ path : pathAnthropicMessages ,
62+ headers : http.Header {"User-Agent" : []string {"claude-code/1.0.0" }},
63+ expectStatus : metrics .InterceptionCountStatusCompleted ,
64+ expectModel : "claude-sonnet-4-0" ,
65+ expectRoute : "/v1/messages" ,
66+ expectProvider : config .ProviderAnthropic ,
67+ expectClient : aibridge .ClientClaudeCode ,
68+ },
5369 {
5470 name : "oai_chat_simple" ,
5571 fixture : fixtures .OaiChatSimple ,
5672 path : pathOpenAIChatCompletions ,
73+ headers : http.Header {"User-Agent" : []string {"copilot/1.0.0" }},
5774 expectStatus : metrics .InterceptionCountStatusCompleted ,
5875 expectModel : "gpt-4.1" ,
5976 expectRoute : "/v1/chat/completions" ,
6077 expectProvider : config .ProviderOpenAI ,
78+ expectClient : aibridge .ClientCopilotCLI ,
6179 },
6280 {
6381 name : "oai_chat_error" ,
6482 fixture : fixtures .OaiChatNonStreamError ,
6583 path : pathOpenAIChatCompletions ,
84+ headers : http.Header {"User-Agent" : []string {"githubcopilotchat/0.30.0" }},
6685 expectStatus : metrics .InterceptionCountStatusFailed ,
6786 expectModel : "gpt-4.1" ,
6887 expectRoute : "/v1/chat/completions" ,
6988 expectProvider : config .ProviderOpenAI ,
89+ expectClient : aibridge .ClientCopilotVSC ,
7090 allowOverflow : true ,
7191 },
7292 {
7393 name : "oai_responses_blocking_simple" ,
7494 fixture : fixtures .OaiResponsesBlockingSimple ,
7595 path : pathOpenAIResponses ,
96+ headers : http.Header {"X-Cursor-Client-Version" : []string {"0.50.0" }},
7697 expectStatus : metrics .InterceptionCountStatusCompleted ,
7798 expectModel : "gpt-4o-mini" ,
7899 expectRoute : "/v1/responses" ,
79100 expectProvider : config .ProviderOpenAI ,
101+ expectClient : aibridge .ClientCursor ,
80102 },
81103 {
82104 name : "oai_responses_blocking_error" ,
83105 fixture : fixtures .OaiResponsesBlockingHttpErr ,
84106 path : pathOpenAIResponses ,
107+ headers : http.Header {"User-Agent" : []string {"codex/1.0.0" }},
85108 expectStatus : metrics .InterceptionCountStatusFailed ,
86109 expectModel : "gpt-4o-mini" ,
87110 expectRoute : "/v1/responses" ,
88111 expectProvider : config .ProviderOpenAI ,
112+ expectClient : aibridge .ClientCodex ,
89113 allowOverflow : true ,
90114 },
91115 {
92116 name : "oai_responses_streaming_simple" ,
93117 fixture : fixtures .OaiResponsesStreamingSimple ,
94118 path : pathOpenAIResponses ,
119+ headers : http.Header {"User-Agent" : []string {"zed/0.200.0" }},
95120 expectStatus : metrics .InterceptionCountStatusCompleted ,
96121 expectModel : "gpt-4o-mini" ,
97122 expectRoute : "/v1/responses" ,
98123 expectProvider : config .ProviderOpenAI ,
124+ expectClient : aibridge .ClientZed ,
99125 },
100126 {
101127 name : "oai_responses_streaming_error" ,
102128 fixture : fixtures .OaiResponsesStreamingHttpErr ,
103129 path : pathOpenAIResponses ,
130+ headers : http.Header {"Originator" : []string {"roo-code" }},
104131 expectStatus : metrics .InterceptionCountStatusFailed ,
105132 expectModel : "gpt-4o-mini" ,
106133 expectRoute : "/v1/responses" ,
107134 expectProvider : config .ProviderOpenAI ,
135+ expectClient : aibridge .ClientRoo ,
108136 allowOverflow : true ,
109137 },
110138 }
@@ -125,12 +153,12 @@ func TestMetrics_Interception(t *testing.T) {
125153 withMetrics (m ),
126154 )
127155
128- resp := bridgeServer .makeRequest (t , http .MethodPost , tc .path , fix .Request ())
156+ resp := bridgeServer .makeRequest (t , http .MethodPost , tc .path , fix .Request (), tc . headers )
129157 _ , err := io .ReadAll (resp .Body )
130158 require .NoError (t , err )
131159
132160 count := promtest .ToFloat64 (m .InterceptionCount .WithLabelValues (
133- tc .expectProvider , tc .expectModel , tc .expectStatus , tc .expectRoute , "POST" , defaultActorID ))
161+ tc .expectProvider , tc .expectModel , tc .expectStatus , tc .expectRoute , "POST" , defaultActorID , string ( tc . expectClient ) ))
134162 require .Equal (t , 1.0 , count )
135163 require .Equal (t , 1 , promtest .CollectAndCount (m .InterceptionDuration ))
136164 require .Equal (t , 1 , promtest .CollectAndCount (m .InterceptionCount ))
@@ -229,16 +257,51 @@ func TestMetrics_PromptCount(t *testing.T) {
229257 withMetrics (m ),
230258 )
231259
232- resp := bridgeServer .makeRequest (t , http .MethodPost , pathOpenAIChatCompletions , fix .Request ())
260+ resp := bridgeServer .makeRequest (t , http .MethodPost , pathOpenAIChatCompletions , fix .Request (), http. Header { "User-Agent" : [] string { "claude-code/1.0.0" }} )
233261 require .Equal (t , http .StatusOK , resp .StatusCode )
234262 _ , err := io .ReadAll (resp .Body )
235263 require .NoError (t , err )
236264
237265 prompts := promtest .ToFloat64 (m .PromptCount .WithLabelValues (
238- config .ProviderOpenAI , "gpt-4.1" , defaultActorID ))
266+ config .ProviderOpenAI , "gpt-4.1" , defaultActorID , string ( aibridge . ClientClaudeCode ) ))
239267 require .Equal (t , 1.0 , prompts )
240268}
241269
270+ func TestMetrics_TokenUseCount (t * testing.T ) {
271+ t .Parallel ()
272+
273+ ctx , cancel := context .WithTimeout (t .Context (), time .Second * 30 )
274+ t .Cleanup (cancel )
275+
276+ fix := fixtures .Parse (t , fixtures .OaiResponsesBlockingCachedInputTokens )
277+ upstream := newMockUpstream (t , ctx , newFixtureResponse (fix ))
278+
279+ m := aibridge .NewMetrics (prometheus .NewRegistry ())
280+ bridgeServer := newBridgeTestServer (t , ctx , upstream .URL ,
281+ withMetrics (m ),
282+ )
283+
284+ resp := bridgeServer .makeRequest (t , http .MethodPost , pathOpenAIResponses , fix .Request (),
285+ http.Header {"User-Agent" : []string {"claude-code/1.0.0" }})
286+ require .Equal (t , http .StatusOK , resp .StatusCode )
287+ _ , _ = io .ReadAll (resp .Body )
288+
289+ clientLabel := string (aibridge .ClientClaudeCode )
290+ // Token metrics are recorded asynchronously; wait for them to appear.
291+ require .Eventually (t , func () bool {
292+ return promtest .ToFloat64 (m .TokenUseCount .WithLabelValues (
293+ config .ProviderOpenAI , "gpt-4.1" , "input" , defaultActorID , clientLabel )) > 0
294+ }, time .Second * 10 , time .Millisecond * 50 )
295+
296+ require .Equal (t , 129.0 , promtest .ToFloat64 (m .TokenUseCount .WithLabelValues (config .ProviderOpenAI , "gpt-4.1" , "input" , defaultActorID , clientLabel ))) // 12033 - 11904 (cached)
297+ require .Equal (t , 44.0 , promtest .ToFloat64 (m .TokenUseCount .WithLabelValues (config .ProviderOpenAI , "gpt-4.1" , "output" , defaultActorID , clientLabel )))
298+
299+ // ExtraTokenTypes
300+ require .Equal (t , 11904.0 , promtest .ToFloat64 (m .TokenUseCount .WithLabelValues (config .ProviderOpenAI , "gpt-4.1" , "input_cached" , defaultActorID , clientLabel )))
301+ require .Equal (t , 0.0 , promtest .ToFloat64 (m .TokenUseCount .WithLabelValues (config .ProviderOpenAI , "gpt-4.1" , "output_reasoning" , defaultActorID , clientLabel )))
302+ require .Equal (t , 12077.0 , promtest .ToFloat64 (m .TokenUseCount .WithLabelValues (config .ProviderOpenAI , "gpt-4.1" , "total_tokens" , defaultActorID , clientLabel )))
303+ }
304+
242305func TestMetrics_NonInjectedToolUseCount (t * testing.T ) {
243306 t .Parallel ()
244307
0 commit comments