@@ -19,6 +19,8 @@ import (
1919 "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore"
2020 "github.com/wavetermdev/waveterm/pkg/aiusechat/openai"
2121 "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
22+ "github.com/wavetermdev/waveterm/pkg/telemetry"
23+ "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
2224 "github.com/wavetermdev/waveterm/pkg/util/utilfn"
2325 "github.com/wavetermdev/waveterm/pkg/waveobj"
2426 "github.com/wavetermdev/waveterm/pkg/web/sse"
@@ -232,8 +234,14 @@ func processToolResults(stopReason *uctypes.WaveStopReason, chatOpts uctypes.Wav
232234 }
233235}
234236
235- func RunAIChat (ctx context.Context , sseHandler * sse.SSEHandlerCh , chatOpts uctypes.WaveChatOpts ) error {
237+ func RunAIChat (ctx context.Context , sseHandler * sse.SSEHandlerCh , chatOpts uctypes.WaveChatOpts ) ( * uctypes. AIMetrics , error ) {
236238 log .Printf ("RunAIChat\n " )
239+ metrics := & uctypes.AIMetrics {
240+ Usage : uctypes.AIUsage {
241+ APIType : chatOpts .Config .APIType ,
242+ Model : chatOpts .Config .Model ,
243+ },
244+ }
237245 firstStep := true
238246 var cont * uctypes.WaveContinueResponse
239247 for {
@@ -245,14 +253,28 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, chatOpts uctyp
245253 }
246254 }
247255 stopReason , rtnMessage , err := runAIChatStep (ctx , sseHandler , chatOpts , cont )
256+ metrics .RequestCount ++
257+ if chatOpts .Config .IsPremiumModel () {
258+ metrics .PremiumReqCount ++
259+ }
260+ if chatOpts .Config .IsWaveProxy () {
261+ metrics .ProxyReqCount ++
262+ }
248263 if len (rtnMessage ) > 0 {
249264 usage := getUsage (rtnMessage )
250265 log .Printf ("usage: input=%d output=%d\n " , usage .InputTokens , usage .OutputTokens )
266+ metrics .Usage .InputTokens += usage .InputTokens
267+ metrics .Usage .OutputTokens += usage .OutputTokens
268+ if usage .Model != "" && metrics .Usage .Model != usage .Model {
269+ metrics .Usage .Model = "mixed"
270+ }
251271 }
252272 if firstStep && err != nil {
253- return fmt .Errorf ("failed to stream %s chat: %w" , chatOpts .Config .APIType , err )
273+ metrics .HadError = true
274+ return metrics , fmt .Errorf ("failed to stream %s chat: %w" , chatOpts .Config .APIType , err )
254275 }
255276 if err != nil {
277+ metrics .HadError = true
256278 _ = sseHandler .AiMsgError (err .Error ())
257279 _ = sseHandler .AiMsgFinish ("" , nil )
258280 break
@@ -274,6 +296,7 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, chatOpts uctyp
274296 continue
275297 }
276298 if stopReason != nil && stopReason .Kind == uctypes .StopKindToolUse {
299+ metrics .ToolUseCount += len (stopReason .ToolCalls )
277300 processToolResults (stopReason , chatOpts , sseHandler )
278301
279302 var messageID string
@@ -290,7 +313,7 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, chatOpts uctyp
290313 }
291314 break
292315 }
293- return nil
316+ return metrics , nil
294317}
295318
296319// ResolveToolCall resolves a single tool call and returns an AIToolResult
@@ -359,6 +382,7 @@ func ResolveToolCall(toolCall uctypes.WaveToolCall, chatOpts uctypes.WaveChatOpt
359382
360383func WaveAIPostMessageWrap (ctx context.Context , sseHandler * sse.SSEHandlerCh , message * uctypes.AIMessage , chatOpts uctypes.WaveChatOpts ) error {
361384 log .Printf ("WaveAIPostMessageWrap\n " )
385+ startTime := time .Now ()
362386
363387 // Convert AIMessage to Anthropic chat message
364388 var convertedMessage uctypes.GenAIMessage
@@ -383,7 +407,51 @@ func WaveAIPostMessageWrap(ctx context.Context, sseHandler *sse.SSEHandlerCh, me
383407 return fmt .Errorf ("failed to store message: %w" , err )
384408 }
385409
386- return RunAIChat (ctx , sseHandler , chatOpts )
410+ metrics , err := RunAIChat (ctx , sseHandler , chatOpts )
411+ if metrics != nil {
412+ metrics .RequestDuration = int (time .Since (startTime ).Milliseconds ())
413+ for _ , part := range message .Parts {
414+ if part .Type == uctypes .AIMessagePartTypeText {
415+ metrics .TextLen += len (part .Text )
416+ } else if part .Type == uctypes .AIMessagePartTypeFile {
417+ mimeType := strings .ToLower (part .MimeType )
418+ if strings .HasPrefix (mimeType , "image/" ) {
419+ metrics .ImageCount ++
420+ } else if mimeType == "application/pdf" {
421+ metrics .PDFCount ++
422+ } else {
423+ metrics .TextDocCount ++
424+ }
425+ }
426+ }
427+ log .Printf ("metrics: requests=%d tools=%d premium=%d proxy=%d images=%d pdfs=%d textdocs=%d textlen=%d duration=%dms error=%v\n " ,
428+ metrics .RequestCount , metrics .ToolUseCount , metrics .PremiumReqCount , metrics .ProxyReqCount ,
429+ metrics .ImageCount , metrics .PDFCount , metrics .TextDocCount , metrics .TextLen , metrics .RequestDuration , metrics .HadError )
430+
431+ sendAIMetricsTelemetry (ctx , metrics )
432+ }
433+ return err
434+ }
435+
436+ func sendAIMetricsTelemetry (ctx context.Context , metrics * uctypes.AIMetrics ) {
437+ event := telemetrydata .MakeTEvent ("waveai:post" , telemetrydata.TEventProps {
438+ WaveAIAPIType : metrics .Usage .APIType ,
439+ WaveAIModel : metrics .Usage .Model ,
440+ WaveAIInputTokens : metrics .Usage .InputTokens ,
441+ WaveAIOutputTokens : metrics .Usage .OutputTokens ,
442+ WaveAIRequestCount : metrics .RequestCount ,
443+ WaveAIToolUseCount : metrics .ToolUseCount ,
444+ WaveAIPremiumReq : metrics .PremiumReqCount ,
445+ WaveAIProxyReq : metrics .ProxyReqCount ,
446+ WaveAIHadError : metrics .HadError ,
447+ WaveAIImageCount : metrics .ImageCount ,
448+ WaveAIPDFCount : metrics .PDFCount ,
449+ WaveAITextDocCount : metrics .TextDocCount ,
450+ WaveAITextLen : metrics .TextLen ,
451+ WaveAIFirstByteMs : metrics .FirstByteLatency ,
452+ WaveAIRequestDurMs : metrics .RequestDuration ,
453+ })
454+ _ = telemetry .RecordTEvent (ctx , event )
387455}
388456
389457// PostMessageRequest represents the request body for posting a message
0 commit comments