@@ -499,6 +499,9 @@ let messages = []; // running conversation state (Chat Completions style)
499499
500500const MAX_TOOL_RESULT_CHARS = 0 ;
501501const MAX_HISTORY_MESSAGES = 40 ;
502+ const ESTIMATED_CHARS_PER_TOKEN = 4 ;
503+ const MAX_ESTIMATED_TOKENS_PER_MINUTE = 20000 ;
504+ const minuteTokenMap = new Map ( ) ;
502505
503506function truncateToolResult ( text ) {
504507 if ( MAX_TOOL_RESULT_CHARS == 0 || text . length <= MAX_TOOL_RESULT_CHARS ) return text ;
@@ -518,23 +521,50 @@ function trimHistory() {
518521 }
519522}
520523
524+ function getEstimatedTokenMinuteLog ( firstIterationMinuteBucket ) {
525+ return Array . from ( minuteTokenMap . entries ( ) )
526+ . filter ( ( [ minuteBucket ] ) => minuteBucket >= firstIterationMinuteBucket )
527+ . sort ( ( [ leftMinuteBucket ] , [ rightMinuteBucket ] ) => leftMinuteBucket - rightMinuteBucket )
528+ . map ( ( [ minuteBucket , estimatedTokens ] ) => ( {
529+ timestamp : new Date ( minuteBucket * 60000 ) . toISOString ( ) ,
530+ estimated_tokens : estimatedTokens ,
531+ } ) ) ;
532+ }
533+
521534async function runAgentTurn ( userText ) {
522535 const apiKey = apiKeyEl . value . trim ( ) ;
523536 if ( ! apiKey ) throw new Error ( "Missing API key" ) ;
524537
525538 const provider = PROVIDERS [ getProviderValue ( ) ] ;
526539 const { chat } = provider . api ;
527540 const baseURL = provider . baseUrlDefault ? baseUrlEl . value . trim ( ) : undefined ;
541+ let firstIterationMinuteBucket = null ;
528542
529543 messages . push ( { role : "user" , content : userText } ) ;
530544 trimHistory ( ) ;
531545
532546 for ( let i = 0 ; i < 64 ; i ++ ) {
547+ const messages_with_system = [ { role : "system" , content : SYSTEM_INSTRUCTIONS } , ...messages ] ;
548+ const estimatedTokens = Math . max ( 1 , Math . ceil ( JSON . stringify ( messages_with_system ) . length / ESTIMATED_CHARS_PER_TOKEN ) ) ;
549+ const now = Date . now ( ) ;
550+ let currentMinuteBucket = Math . floor ( now / 60000 ) ;
551+ if ( firstIterationMinuteBucket === null ) {
552+ firstIterationMinuteBucket = currentMinuteBucket ;
553+ }
554+ const estimateTokenUsage = ( minuteTokenMap . get ( currentMinuteBucket ) ?? 0 ) + estimatedTokens ;
555+
556+ if ( estimateTokenUsage > MAX_ESTIMATED_TOKENS_PER_MINUTE ) {
557+ currentMinuteBucket += 1 ;
558+ await new Promise ( ( resolve ) => setTimeout ( ( ) => resolve ( ) , 60000 ) ) ;
559+ }
560+
561+ minuteTokenMap . set ( currentMinuteBucket , ( minuteTokenMap . get ( currentMinuteBucket ) ?? 0 ) + estimatedTokens ) ;
562+
533563 const response = await chat ( {
534564 apiKey,
535565 baseURL,
536566 model : modelEl . value ,
537- messages : [ { role : "system" , content : SYSTEM_INSTRUCTIONS } , ... messages ] ,
567+ messages : messages_with_system ,
538568 tools,
539569 } ) ;
540570
@@ -546,7 +576,10 @@ async function runAgentTurn(userText) {
546576 if ( message . content ) addMessage ( "assistant" , message . content ) ;
547577
548578 const calls = message . tool_calls ?? [ ] ;
549- if ( calls . length === 0 ) return ;
579+ if ( calls . length === 0 ) {
580+ console . log ( "Estimated token usage by minute" , getEstimatedTokenMinuteLog ( firstIterationMinuteBucket ) ) ;
581+ return ;
582+ }
550583
551584 for ( const call of calls ) {
552585 let args = { } ;
0 commit comments