@@ -1954,6 +1954,179 @@ describe("GhostInlineCompletionProvider", () => {
19541954 } )
19551955 } )
19561956
1957+ describe ( "adaptive debounce delay" , ( ) => {
1958+ it ( "should start with initial debounce delay of 300ms" , async ( ) => {
1959+ let callCount = 0
1960+ vi . mocked ( mockModel . generateResponse ) . mockImplementation ( async ( ) => {
1961+ callCount ++
1962+ return {
1963+ cost : 0.01 ,
1964+ inputTokens : 100 ,
1965+ outputTokens : 50 ,
1966+ cacheWriteTokens : 0 ,
1967+ cacheReadTokens : 0 ,
1968+ }
1969+ } )
1970+
1971+ // First call - executes immediately (leading edge)
1972+ await provider . provideInlineCompletionItems ( mockDocument , mockPosition , mockContext , mockToken )
1973+ expect ( callCount ) . toBe ( 1 )
1974+
1975+ // Second call - should be debounced with initial 300ms delay
1976+ const mockDocument2 = new MockTextDocument ( vscode . Uri . file ( "/test2.ts" ) , "const a = 1\nconst b = 2" )
1977+ const mockPosition2 = new vscode . Position ( 0 , 11 )
1978+ const promise2 = provider . provideInlineCompletionItems ( mockDocument2 , mockPosition2 , mockContext , mockToken )
1979+
1980+ // Should not have called yet (debounced)
1981+ expect ( callCount ) . toBe ( 1 )
1982+
1983+ // Advance 200ms - should still be waiting
1984+ await vi . advanceTimersByTimeAsync ( 200 )
1985+ expect ( callCount ) . toBe ( 1 )
1986+
1987+ // Advance remaining 100ms to complete the 300ms debounce
1988+ await vi . advanceTimersByTimeAsync ( 100 )
1989+ await promise2
1990+ expect ( callCount ) . toBe ( 2 )
1991+ } )
1992+
1993+ it ( "should record latency and not update debounce delay until 10 samples collected" , ( ) => {
1994+ // Record 9 latencies - should not update debounce delay yet
1995+ for ( let i = 0 ; i < 9 ; i ++ ) {
1996+ provider . recordLatency ( 100 + i * 10 ) // 100, 110, 120, ..., 180
1997+ }
1998+
1999+ // Access private field via any cast for testing
2000+ const providerAny = provider as any
2001+ expect ( providerAny . latencyHistory . length ) . toBe ( 9 )
2002+ expect ( providerAny . debounceDelayMs ) . toBe ( 300 ) // Still initial value
2003+ } )
2004+
2005+ it ( "should update debounce delay to average after exceeding 10 samples" , ( ) => {
2006+ // Record 10 latencies of 200ms each - debounce delay not updated yet
2007+ for ( let i = 0 ; i < 10 ; i ++ ) {
2008+ provider . recordLatency ( 200 )
2009+ }
2010+
2011+ // Access private field via any cast for testing
2012+ const providerAny = provider as any
2013+ expect ( providerAny . latencyHistory . length ) . toBe ( 10 )
2014+ expect ( providerAny . debounceDelayMs ) . toBe ( 300 ) // Still initial value (not updated until > 10)
2015+
2016+ // Record 11th latency - now debounce delay is updated
2017+ provider . recordLatency ( 200 )
2018+ expect ( providerAny . latencyHistory . length ) . toBe ( 10 ) // Still 10 (oldest removed)
2019+ expect ( providerAny . debounceDelayMs ) . toBe ( 200 ) // Now updated to average
2020+ } )
2021+
2022+ it ( "should maintain rolling window of 10 latencies" , ( ) => {
2023+ // Record 15 latencies
2024+ for ( let i = 0 ; i < 15 ; i ++ ) {
2025+ provider . recordLatency ( 100 + i * 10 ) // 100, 110, 120, ..., 240
2026+ }
2027+
2028+ // Access private field via any cast for testing
2029+ const providerAny = provider as any
2030+ expect ( providerAny . latencyHistory . length ) . toBe ( 10 ) // Only last 10 kept
2031+
2032+ // Last 10 values should be 150, 160, 170, 180, 190, 200, 210, 220, 230, 240
2033+ // Average = (150+160+170+180+190+200+210+220+230+240) / 10 = 195
2034+ expect ( providerAny . debounceDelayMs ) . toBe ( 195 )
2035+ } )
2036+
2037+ it ( "should update debounce delay on each new latency after exceeding 10 samples" , ( ) => {
2038+ // Record 11 latencies of 200ms each (need > 10 to trigger update)
2039+ for ( let i = 0 ; i < 11 ; i ++ ) {
2040+ provider . recordLatency ( 200 )
2041+ }
2042+
2043+ const providerAny = provider as any
2044+ expect ( providerAny . debounceDelayMs ) . toBe ( 200 )
2045+
2046+ // Add one more latency of 300ms
2047+ // New average = (200*9 + 300) / 10 = 210
2048+ provider . recordLatency ( 300 )
2049+ expect ( providerAny . debounceDelayMs ) . toBe ( 210 )
2050+
2051+ // Add another latency of 400ms
2052+ // New average = (200*8 + 300 + 400) / 10 = 230
2053+ provider . recordLatency ( 400 )
2054+ expect ( providerAny . debounceDelayMs ) . toBe ( 230 )
2055+ } )
2056+
2057+ it ( "should use adaptive debounce delay after collecting enough samples" , async ( ) => {
2058+ let callCount = 0
2059+ vi . mocked ( mockModel . generateResponse ) . mockImplementation ( async ( ) => {
2060+ callCount ++
2061+ return {
2062+ cost : 0.01 ,
2063+ inputTokens : 100 ,
2064+ outputTokens : 50 ,
2065+ cacheWriteTokens : 0 ,
2066+ cacheReadTokens : 0 ,
2067+ }
2068+ } )
2069+
2070+ // Record 11 latencies of 150ms each to set debounce delay to 150ms
2071+ // (need > 10 to trigger update)
2072+ for ( let i = 0 ; i < 11 ; i ++ ) {
2073+ provider . recordLatency ( 150 )
2074+ }
2075+
2076+ const providerAny = provider as any
2077+ expect ( providerAny . debounceDelayMs ) . toBe ( 150 )
2078+
2079+ // First call - executes immediately (leading edge)
2080+ await provider . provideInlineCompletionItems ( mockDocument , mockPosition , mockContext , mockToken )
2081+ expect ( callCount ) . toBe ( 1 )
2082+
2083+ // Second call - should be debounced with adaptive 150ms delay
2084+ const mockDocument2 = new MockTextDocument ( vscode . Uri . file ( "/test2.ts" ) , "const a = 1\nconst b = 2" )
2085+ const mockPosition2 = new vscode . Position ( 0 , 11 )
2086+ const promise2 = provider . provideInlineCompletionItems ( mockDocument2 , mockPosition2 , mockContext , mockToken )
2087+
2088+ // Should not have called yet (debounced)
2089+ expect ( callCount ) . toBe ( 1 )
2090+
2091+ // Advance 100ms - should still be waiting (150ms debounce)
2092+ await vi . advanceTimersByTimeAsync ( 100 )
2093+ expect ( callCount ) . toBe ( 1 )
2094+
2095+ // Advance remaining 50ms to complete the 150ms debounce
2096+ await vi . advanceTimersByTimeAsync ( 50 )
2097+ await promise2
2098+ expect ( callCount ) . toBe ( 2 )
2099+ } )
2100+
2101+ it ( "should record latency from LLM requests" , async ( ) => {
2102+ // Mock the model to simulate a delay
2103+ vi . mocked ( mockModel . generateResponse ) . mockImplementation ( async ( _sys , _user , onChunk ) => {
2104+ // Simulate some processing time
2105+ if ( onChunk ) {
2106+ onChunk ( { type : "text" , text : "<COMPLETION>" } )
2107+ onChunk ( { type : "text" , text : "console.log('test');" } )
2108+ onChunk ( { type : "text" , text : "</COMPLETION>" } )
2109+ }
2110+ return {
2111+ cost : 0.01 ,
2112+ inputTokens : 100 ,
2113+ outputTokens : 50 ,
2114+ cacheWriteTokens : 0 ,
2115+ cacheReadTokens : 0 ,
2116+ }
2117+ } )
2118+
2119+ const providerAny = provider as any
2120+ expect ( providerAny . latencyHistory . length ) . toBe ( 0 )
2121+
2122+ // Make a request that will record latency
2123+ await provider . provideInlineCompletionItems ( mockDocument , mockPosition , mockContext , mockToken )
2124+
2125+ // Latency should have been recorded
2126+ expect ( providerAny . latencyHistory . length ) . toBe ( 1 )
2127+ } )
2128+ } )
2129+
19572130 describe ( "telemetry tracking" , ( ) => {
19582131 beforeEach ( ( ) => {
19592132 vi . mocked ( telemetry . captureAcceptSuggestion ) . mockClear ( )
0 commit comments