@@ -183,19 +183,32 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
183183 * @returns A promise resolving to the token count
184184 */
185185 override async countTokens ( content : Array < Anthropic . Messages . ContentBlockParam > ) : Promise < number > {
186- // Convert Anthropic content blocks to a string for VSCode LM token counting
187- let textContent = ""
188-
189- for ( const block of content ) {
190- if ( block . type === "text" ) {
191- textContent += block . text || ""
192- } else if ( block . type === "image" ) {
193- // VSCode LM doesn't support images directly, so we'll just use a placeholder
194- textContent += "[IMAGE]"
186+ try {
187+ // Convert Anthropic content blocks to a string for VSCode LM token counting
188+ let textContent = ""
189+
190+ for ( const block of content ) {
191+ if ( block . type === "text" ) {
192+ textContent += block . text || ""
193+ } else if ( block . type === "image" ) {
194+ // VSCode LM doesn't support images directly, so we'll just use a placeholder
195+ textContent += "[IMAGE]"
196+ }
195197 }
196- }
197198
198- return this . internalCountTokens ( textContent )
199+ const tokenCount = await this . internalCountTokens ( textContent )
200+
201+ // If VSCode API returns 0 or fails, fall back to tiktoken
202+ if ( tokenCount === 0 && textContent . length > 0 ) {
203+ console . debug ( "Roo Code <Language Model API>: Falling back to tiktoken for token counting" )
204+ return super . countTokens ( content )
205+ }
206+
207+ return tokenCount
208+ } catch ( error ) {
209+ console . warn ( "Roo Code <Language Model API>: Error in countTokens, falling back to tiktoken:" , error )
210+ return super . countTokens ( content )
211+ }
199212 }
200213
201214 /**
@@ -204,12 +217,24 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
204217 private async internalCountTokens ( text : string | vscode . LanguageModelChatMessage ) : Promise < number > {
205218 // Check for required dependencies
206219 if ( ! this . client ) {
207- console . warn ( "Roo Code <Language Model API>: No client available for token counting" )
220+ console . warn (
221+ "Roo Code <Language Model API>: No client available for token counting, using tiktoken fallback" ,
222+ )
223+ // Fall back to tiktoken for string inputs
224+ if ( typeof text === "string" ) {
225+ return this . fallbackToTiktoken ( text )
226+ }
208227 return 0
209228 }
210229
211230 if ( ! this . currentRequestCancellation ) {
212- console . warn ( "Roo Code <Language Model API>: No cancellation token available for token counting" )
231+ console . warn (
232+ "Roo Code <Language Model API>: No cancellation token available for token counting, using tiktoken fallback" ,
233+ )
234+ // Fall back to tiktoken for string inputs
235+ if ( typeof text === "string" ) {
236+ return this . fallbackToTiktoken ( text )
237+ }
213238 return 0
214239 }
215240
@@ -240,14 +265,30 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
240265 // Validate the result
241266 if ( typeof tokenCount !== "number" ) {
242267 console . warn ( "Roo Code <Language Model API>: Non-numeric token count received:" , tokenCount )
268+ // Fall back to tiktoken for string inputs
269+ if ( typeof text === "string" ) {
270+ return this . fallbackToTiktoken ( text )
271+ }
243272 return 0
244273 }
245274
246275 if ( tokenCount < 0 ) {
247276 console . warn ( "Roo Code <Language Model API>: Negative token count received:" , tokenCount )
277+ // Fall back to tiktoken for string inputs
278+ if ( typeof text === "string" ) {
279+ return this . fallbackToTiktoken ( text )
280+ }
248281 return 0
249282 }
250283
284+ // If we get 0 tokens but have content, fall back to tiktoken
285+ if ( tokenCount === 0 && typeof text === "string" && text . length > 0 ) {
286+ console . debug (
287+ "Roo Code <Language Model API>: VSCode API returned 0 tokens for non-empty text, using tiktoken fallback" ,
288+ )
289+ return this . fallbackToTiktoken ( text )
290+ }
291+
251292 return tokenCount
252293 } catch ( error ) {
253294 // Handle specific error types
@@ -257,17 +298,42 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
257298 }
258299
259300 const errorMessage = error instanceof Error ? error . message : "Unknown error"
260- console . warn ( "Roo Code <Language Model API>: Token counting failed:" , errorMessage )
301+ console . warn ( "Roo Code <Language Model API>: Token counting failed, using tiktoken fallback :" , errorMessage )
261302
262303 // Log additional error details if available
263304 if ( error instanceof Error && error . stack ) {
264305 console . debug ( "Token counting error stack:" , error . stack )
265306 }
266307
308+ // Fall back to tiktoken for string inputs
309+ if ( typeof text === "string" ) {
310+ return this . fallbackToTiktoken ( text )
311+ }
312+
267313 return 0 // Fallback to prevent stream interruption
268314 }
269315 }
270316
317+ /**
318+ * Fallback to tiktoken for token counting when VSCode API is unavailable or returns invalid results
319+ */
320+ private async fallbackToTiktoken ( text : string ) : Promise < number > {
321+ try {
322+ // Convert text to Anthropic content blocks format for base provider
323+ const content : Anthropic . Messages . ContentBlockParam [ ] = [
324+ {
325+ type : "text" ,
326+ text : text ,
327+ } ,
328+ ]
329+ return super . countTokens ( content )
330+ } catch ( error ) {
331+ console . error ( "Roo Code <Language Model API>: Tiktoken fallback failed:" , error )
332+ // Last resort: estimate based on character count (rough approximation)
333+ return Math . ceil ( text . length / 4 )
334+ }
335+ }
336+
271337 private async calculateTotalInputTokens (
272338 systemPrompt : string ,
273339 vsCodeLmMessages : vscode . LanguageModelChatMessage [ ] ,
@@ -363,6 +429,8 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
363429
364430 // Accumulate the text and count at the end of the stream to reduce token counting overhead.
365431 let accumulatedText : string = ""
432+ let lastTokenUpdateLength : number = 0
433+ const TOKEN_UPDATE_INTERVAL = 100 // Update tokens every 100 characters for more responsive UI
366434
367435 try {
368436 // Create the response stream with minimal required options
@@ -393,6 +461,17 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
393461 type : "text" ,
394462 text : chunk . value ,
395463 }
464+
465+ // Provide more frequent token updates during streaming
466+ if ( accumulatedText . length - lastTokenUpdateLength >= TOKEN_UPDATE_INTERVAL ) {
467+ const currentOutputTokens = await this . internalCountTokens ( accumulatedText )
468+ yield {
469+ type : "usage" ,
470+ inputTokens : totalInputTokens ,
471+ outputTokens : currentOutputTokens ,
472+ }
473+ lastTokenUpdateLength = accumulatedText . length
474+ }
396475 } else if ( chunk instanceof vscode . LanguageModelToolCallPart ) {
397476 try {
398477 // Validate tool call parameters
0 commit comments