1- import * as vscode from 'vscode' ;
1+ import * as vscode from 'vscode' ;
22import * as fs from 'fs' ;
33import * as path from 'path' ;
44import * as os from 'os' ;
@@ -64,6 +64,7 @@ import { VisualStudioDataAccess } from './visualstudio';
6464import { ContinueDataAccess } from './continue' ;
6565import { ClaudeCodeDataAccess } from './claudecode' ;
6666import { ClaudeDesktopCoworkDataAccess } from './claudedesktop' ;
67+ import { MistralVibeDataAccess } from './mistralvibe' ;
6768import {
6869 estimateTokensFromText as _estimateTokensFromText ,
6970 estimateTokensFromJsonlSession as _estimateTokensFromJsonlSession ,
@@ -168,10 +169,11 @@ class CopilotTokenTracker implements vscode.Disposable {
168169 private continue_ : ContinueDataAccess ;
169170 private claudeCode : ClaudeCodeDataAccess ;
170171 private claudeDesktopCowork : ClaudeDesktopCoworkDataAccess ;
172+ private mistralVibe : MistralVibeDataAccess ;
171173 private cacheManager : CacheManager ;
172174
173175 private get usageAnalysisDeps ( ) : UsageAnalysisDeps {
174- return { warn : ( m : string ) => this . warn ( m ) , openCode : this . openCode , crush : this . crush , visualStudio : this . visualStudio , continue_ : this . continue_ , claudeCode : this . claudeCode , claudeDesktopCowork : this . claudeDesktopCowork , tokenEstimators : this . tokenEstimators , modelPricing : this . modelPricing , toolNameMap : this . toolNameMap } ;
176+ return { warn : ( m : string ) => this . warn ( m ) , openCode : this . openCode , crush : this . crush , visualStudio : this . visualStudio , continue_ : this . continue_ , claudeCode : this . claudeCode , claudeDesktopCowork : this . claudeDesktopCowork , mistralVibe : this . mistralVibe , tokenEstimators : this . tokenEstimators , modelPricing : this . modelPricing , toolNameMap : this . toolNameMap } ;
175177 }
176178 private sessionDiscovery : SessionDiscovery ;
177179 private statusBarItem : vscode . StatusBarItem ;
@@ -324,6 +326,9 @@ class CopilotTokenTracker implements vscode.Disposable {
324326 if ( this . crush . isCrushSessionFile ( sessionFile ) ) {
325327 return this . crush . statSessionFile ( sessionFile ) ;
326328 }
329+ if ( this . mistralVibe . isVibeSessionFile ( sessionFile ) ) {
330+ return fs . promises . stat ( sessionFile ) ;
331+ }
327332 return this . openCode . statSessionFile ( sessionFile ) ;
328333 }
329334
@@ -835,6 +840,7 @@ class CopilotTokenTracker implements vscode.Disposable {
835840 this . visualStudio = new VisualStudioDataAccess ( ) ;
836841 this . claudeCode = new ClaudeCodeDataAccess ( ) ;
837842 this . claudeDesktopCowork = new ClaudeDesktopCoworkDataAccess ( ) ;
843+ this . mistralVibe = new MistralVibeDataAccess ( ) ;
838844 this . cacheManager = new CacheManager ( context , { log : ( m : string ) => this . log ( m ) , warn : ( m : string ) => this . warn ( m ) , error : ( m : string ) => this . error ( m ) } , CopilotTokenTracker . CACHE_VERSION ) ;
839845 this . sessionDiscovery = new SessionDiscovery ( {
840846 log : ( m ) => this . log ( m ) ,
@@ -846,6 +852,7 @@ class CopilotTokenTracker implements vscode.Disposable {
846852 continue_ : this . continue_ ,
847853 claudeCode : this . claudeCode ,
848854 claudeDesktopCowork : this . claudeDesktopCowork ,
855+ mistralVibe : this . mistralVibe ,
849856 sampleDataDirectoryOverride : ( ) => this . localRegressionSampleDataDir ,
850857 } ) ;
851858 this . context = context ;
@@ -2458,6 +2465,11 @@ class CopilotTokenTracker implements vscode.Disposable {
24582465 return this . claudeCode . countClaudeCodeInteractions ( sessionFile ) ;
24592466 }
24602467
2468+ // Handle Mistral Vibe sessions
2469+ if ( this . mistralVibe . isVibeSessionFile ( sessionFile ) ) {
2470+ return this . mistralVibe . countInteractions ( sessionFile ) ;
2471+ }
2472+
24612473 const fileContent = preloadedContent ?? await fs . promises . readFile ( sessionFile , 'utf8' ) ;
24622474
24632475 // Check if this is a UUID-only file (new Copilot CLI format)
@@ -2709,6 +2721,16 @@ class CopilotTokenTracker implements vscode.Disposable {
27092721 } ;
27102722 }
27112723
2724+ // Handle Mistral Vibe sessions
2725+ if ( this . mistralVibe . isVibeSessionFile ( sessionFile ) ) {
2726+ const meta = this . mistralVibe . getSessionMeta ( sessionFile ) ;
2727+ return {
2728+ title : meta ?. title ,
2729+ firstInteraction : meta ?. firstInteraction || null ,
2730+ lastInteraction : meta ?. lastInteraction || null ,
2731+ } ;
2732+ }
2733+
27122734 const fileContent = preloadedContent ?? await fs . promises . readFile ( sessionFile , 'utf8' ) ;
27132735
27142736 // Check if this is a UUID-only file (new Copilot CLI format)
@@ -2946,6 +2968,11 @@ class CopilotTokenTracker implements vscode.Disposable {
29462968 details . editorName = 'Claude Code' ;
29472969 return ;
29482970 }
2971+ if ( this . mistralVibe . isVibeSessionFile ( sessionFile ) ) {
2972+ details . editorRoot = this . mistralVibe . getSessionLogDir ( ) ;
2973+ details . editorName = 'Mistral Vibe' ;
2974+ return ;
2975+ }
29492976 try {
29502977 const parts = sessionFile . split ( / [ / \\ ] / ) ;
29512978 const userIdx = parts . findIndex ( p => p . toLowerCase ( ) === 'user' ) ;
@@ -3858,6 +3885,84 @@ class CopilotTokenTracker implements vscode.Disposable {
38583885 } ;
38593886 }
38603887
3888+ // Handle Mistral Vibe sessions
3889+ if ( this . mistralVibe . isVibeSessionFile ( sessionFile ) ) {
3890+ const messages = this . mistralVibe . readSessionMessages ( sessionFile ) ;
3891+ const sessionMeta = this . mistralVibe . getSessionMeta ( sessionFile ) ;
3892+ const tokenData = this . mistralVibe . getTokensFromSession ( sessionFile ) ;
3893+ const model : string = sessionMeta . model || 'devstral' ;
3894+
3895+ // Collect non-injected user messages as turn boundaries
3896+ const userMsgIndices : number [ ] = [ ] ;
3897+ for ( let i = 0 ; i < messages . length ; i ++ ) {
3898+ if ( messages [ i ] . role === 'user' && messages [ i ] . injected !== true ) {
3899+ userMsgIndices . push ( i ) ;
3900+ }
3901+ }
3902+ // Mistral Vibe only has session-level token counts (not per-turn)
3903+ // Set per-turn estimates to 0; actual totals go in actualTokens
3904+
3905+ for ( let t = 0 ; t < userMsgIndices . length ; t ++ ) {
3906+ const userIdx = userMsgIndices [ t ] ;
3907+ const nextUserIdx = t + 1 < userMsgIndices . length ? userMsgIndices [ t + 1 ] : messages . length ;
3908+ const userMsg = messages [ userIdx ] ;
3909+ const userText = typeof userMsg . content === 'string' ? userMsg . content : '' ;
3910+ let assistantText = '' ;
3911+ const toolCalls : { toolName : string ; arguments ?: string ; result ?: string } [ ] = [ ] ;
3912+
3913+ // Collect all messages between this user message and the next non-injected user message
3914+ for ( let j = userIdx + 1 ; j < nextUserIdx ; j ++ ) {
3915+ const msg = messages [ j ] ;
3916+ if ( msg . role === 'assistant' ) {
3917+ if ( typeof msg . content === 'string' ) { assistantText += msg . content ; }
3918+ if ( Array . isArray ( msg . tool_calls ) ) {
3919+ for ( const tc of msg . tool_calls ) {
3920+ toolCalls . push ( {
3921+ toolName : tc . function ?. name || tc . name || 'unknown' ,
3922+ arguments : tc . function ?. arguments ? JSON . stringify ( tc . function . arguments ) : undefined
3923+ } ) ;
3924+ }
3925+ }
3926+ } else if ( msg . role === 'tool' ) {
3927+ // Attach tool result to last tool call
3928+ const last = toolCalls [ toolCalls . length - 1 ] ;
3929+ if ( last ) { last . result = typeof msg . content === 'string' ? msg . content : undefined ; }
3930+ }
3931+ }
3932+
3933+ turns . push ( {
3934+ turnNumber : t + 1 ,
3935+ timestamp : sessionMeta . firstInteraction ,
3936+ mode : 'agent' ,
3937+ userMessage : userText ,
3938+ assistantResponse : assistantText ,
3939+ model,
3940+ toolCalls,
3941+ contextReferences : _createEmptyContextRefs ( ) ,
3942+ mcpTools : [ ] ,
3943+ inputTokensEstimate : 0 ,
3944+ outputTokensEstimate : 0 ,
3945+ thinkingTokensEstimate : 0
3946+ } ) ;
3947+ }
3948+
3949+ return {
3950+ file : details . file ,
3951+ title : details . title || null ,
3952+ editorSource : details . editorSource ,
3953+ editorName : 'Mistral Vibe' ,
3954+ size : details . size ,
3955+ modified : details . modified ,
3956+ interactions : details . interactions ,
3957+ contextReferences : details . contextReferences ,
3958+ firstInteraction : details . firstInteraction ,
3959+ lastInteraction : details . lastInteraction ,
3960+ turns,
3961+ actualTokens : tokenData . tokens ,
3962+ usageAnalysis : undefined
3963+ } ;
3964+ }
3965+
38613966 const fileContent = await fs . promises . readFile ( sessionFile , 'utf8' ) ;
38623967
38633968 // Check if this is a UUID-only file (new Copilot CLI format)
@@ -4348,6 +4453,12 @@ class CopilotTokenTracker implements vscode.Disposable {
43484453 return { ...result , actualTokens : result . tokens } ;
43494454 }
43504455
4456+ // Handle Mistral Vibe sessions - actual token counts from meta.json stats
4457+ if ( this . mistralVibe . isVibeSessionFile ( sessionFilePath ) ) {
4458+ const result = this . mistralVibe . getTokensFromSession ( sessionFilePath ) ;
4459+ return { ...result , actualTokens : result . tokens } ;
4460+ }
4461+
43514462 const fileContent = preloadedContent ?? await fs . promises . readFile ( sessionFilePath , 'utf8' ) ;
43524463
43534464 // Check if this is a UUID-only file (new Copilot CLI format)
0 commit comments