Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autocomplete: Surface rate limit and other errors #851

Merged
merged 11 commits into from
Aug 31, 2023
24 changes: 24 additions & 0 deletions lib/shared/src/sourcegraph-api/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export class RateLimitError extends Error {
constructor(
message: string,
public limit?: number,
public retryAfter?: Date
) {
super(message)
Object.setPrototypeOf(this, RateLimitError.prototype)
}
}

export function isAbortError(error: Error): boolean {
return (
// http module
error.message === 'aborted' ||
// fetch
error.message.includes('The operation was aborted') ||
error.message.includes('The user aborted a request')
)
}

export function isRateLimitError(error: Error): boolean {
return error instanceof RateLimitError
}
3 changes: 3 additions & 0 deletions vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Starting from `0.2.0`, Cody is using `major.EVEN_NUMBER.patch` for release versi

- New button in Chat UI to export chat history to a JSON file. [pull/829](https://github.com/sourcegraph/cody/pull/829)
- Rank autocomplete suggestion with tree-sitter when `cody.autocomplete.experimental.syntacticPostProcessing` is enabled. [pull/837](https://github.com/sourcegraph/cody/pull/837)
- Rate limit during autocomplete will now surface to the user through the status bar item. [pull/851](https://github.com/sourcegraph/cody/pull/851)

### Fixed

Expand All @@ -21,6 +22,8 @@ Starting from `0.2.0`, Cody is using `major.EVEN_NUMBER.patch` for release versi

### Changed

- Errors are now always logged to the output console, even if the debug mode is not enabled. [pull/851](https://github.com/sourcegraph/cody/pull/851)

## [0.8.0]

### Added
Expand Down
8 changes: 4 additions & 4 deletions vscode/src/chat/ChatViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CodyPrompt, CodyPromptType } from '@sourcegraph/cody-shared/src/chat/pr
import { ChatMessage, UserLocalHistory } from '@sourcegraph/cody-shared/src/chat/transcript/messages'

import { View } from '../../webviews/NavBar'
import { debug } from '../log'
import { logDebug } from '../log'

import { MessageProvider, MessageProviderOptions } from './MessageProvider'
import { ExtensionMessage, WebviewMessage } from './protocol'
Expand Down Expand Up @@ -34,7 +34,7 @@ export class ChatViewProvider extends MessageProvider implements vscode.WebviewV
await this.authProvider.announceNewAuthStatus()
break
case 'initialized':
debug('ChatViewProvider:onDidReceiveMessage:initialized', '')
logDebug('ChatViewProvider:onDidReceiveMessage:initialized', '')
await this.init()
break
case 'submit':
Expand Down Expand Up @@ -127,7 +127,7 @@ export class ChatViewProvider extends MessageProvider implements vscode.WebviewV
}

private async onHumanMessageSubmitted(text: string, submitType: 'user' | 'suggestion' | 'example'): Promise<void> {
debug('ChatViewProvider:onHumanMessageSubmitted', '', { verbose: { text, submitType } })
logDebug('ChatViewProvider:onHumanMessageSubmitted', '', { verbose: { text, submitType } })
this.telemetryService.log('CodyVSCodeExtension:chat:submitted', { source: 'sidebar' })
if (submitType === 'suggestion') {
this.telemetryService.log('CodyVSCodeExtension:chatPredictions:used')
Expand All @@ -148,7 +148,7 @@ export class ChatViewProvider extends MessageProvider implements vscode.WebviewV
*/
private async onCustomPromptClicked(title: string, commandType: CodyPromptType = 'user'): Promise<void> {
this.telemetryService.log('CodyVSCodeExtension:command:customMenu:clicked')
debug('ChatViewProvider:onCustomPromptClicked', title)
logDebug('ChatViewProvider:onCustomPromptClicked', title)
if (!this.isCustomCommandAction(title)) {
await this.setWebviewView('chat')
}
Expand Down
6 changes: 3 additions & 3 deletions vscode/src/chat/ContextProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { convertGitCloneURLToCodebaseName, isError } from '@sourcegraph/cody-sha
import { getFullConfig } from '../configuration'
import { VSCodeEditor } from '../editor/vscode-editor'
import { PlatformContext } from '../extension.common'
import { debug } from '../log'
import { logDebug } from '../log'
import { getRerankWithLog } from '../logged-rerank'
import { repositoryRemoteUrl } from '../repository/repositoryHelpers'
import { AuthProvider } from '../services/AuthProvider'
Expand Down Expand Up @@ -99,7 +99,7 @@ export class ContextProvider implements vscode.Disposable {
}

public onConfigurationChange(newConfig: Config): void {
debug('ContextProvider:onConfigurationChange', '')
logDebug('ContextProvider:onConfigurationChange', '')
this.config = newConfig
const authStatus = this.authProvider.getAuthStatus()
if (authStatus.endpoint) {
Expand Down Expand Up @@ -220,7 +220,7 @@ export class ContextProvider implements vscode.Disposable {
// update codebase context on configuration change
await this.updateCodebaseContext()
await this.webview?.postMessage({ type: 'config', config: configForWebview, authStatus })
debug('Cody:publishConfig', 'configForWebview', { verbose: configForWebview })
logDebug('Cody:publishConfig', 'configForWebview', { verbose: configForWebview })
}

await send()
Expand Down
16 changes: 8 additions & 8 deletions vscode/src/chat/MessageProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { TelemetryService } from '@sourcegraph/cody-shared/src/telemetry'

import { VSCodeEditor } from '../editor/vscode-editor'
import { PlatformContext } from '../extension.common'
import { debug } from '../log'
import { logDebug, logError } from '../log'
import { FixupTask } from '../non-stop/FixupTask'
import { AuthProvider, isNetworkError } from '../services/AuthProvider'
import { LocalStorage } from '../services/LocalStorageProvider'
Expand Down Expand Up @@ -229,7 +229,7 @@ export abstract class MessageProvider extends MessageHandler implements vscode.D
},
onError: (err, statusCode) => {
// TODO notify the multiplexer of the error
debug('ChatViewProvider:onError', err)
logError('ChatViewProvider:onError', err)

if (isAbortError(err)) {
this.isMessageInProgress = false
Expand All @@ -246,7 +246,7 @@ export abstract class MessageProvider extends MessageHandler implements vscode.D
this.contextProvider.config.customHeaders
)
.catch(error => console.error(error))
debug('ChatViewProvider:onError:unauthUser', err, { verbose: { statusCode } })
logError('ChatViewProvider:onError:unauthUser', err, { verbose: { statusCode } })
}

if (isNetworkError(err)) {
Expand Down Expand Up @@ -356,11 +356,11 @@ export abstract class MessageProvider extends MessageHandler implements vscode.D
humanChatInput = command?.text
recipeId = command?.recipeId

debug('ChatViewProvider:executeRecipe', recipeId, { verbose: humanChatInput })
logDebug('ChatViewProvider:executeRecipe', recipeId, { verbose: humanChatInput })

const recipe = this.getRecipe(recipeId)
if (!recipe) {
debug('ChatViewProvider:executeRecipe', 'no recipe found')
logDebug('ChatViewProvider:executeRecipe', 'no recipe found')
return
}

Expand Down Expand Up @@ -549,7 +549,7 @@ export abstract class MessageProvider extends MessageHandler implements vscode.D
// Get prompt details from controller by title then execute prompt's command
const promptText = this.editor.controllers.command?.find(title)
await this.editor.controllers.command?.get('command')
debug('executeCustomCommand:starting', title)
logDebug('executeCustomCommand:starting', title)
if (!promptText) {
return
}
Expand Down Expand Up @@ -711,7 +711,7 @@ export abstract class MessageProvider extends MessageHandler implements vscode.D
}
})
} catch (error) {
debug('MessageProvider:exportHistory', 'Failed to export chat history', error)
logError('MessageProvider:exportHistory', 'Failed to export chat history', error)
}
}

Expand Down Expand Up @@ -752,7 +752,7 @@ export abstract class MessageProvider extends MessageHandler implements vscode.D
if (this.contextProvider.context.checkEmbeddingsConnection() && searchErrors) {
this.transcript.addErrorAsAssistantResponse(searchErrors)
this.handleTranscriptErrors(true)
debug('ChatViewProvider:onLogEmbeddingsErrors', '', { verbose: searchErrors })
logError('ChatViewProvider:onLogEmbeddingsErrors', '', { verbose: searchErrors })
}
}

Expand Down
9 changes: 8 additions & 1 deletion vscode/src/completions/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
CompletionParameters,
CompletionResponse,
} from '@sourcegraph/cody-shared/src/sourcegraph-api/completions/types'
import { RateLimitError } from '@sourcegraph/cody-shared/src/sourcegraph-api/errors'

export type CodeCompletionsParams = Omit<CompletionParameters, 'fast'>

Expand Down Expand Up @@ -63,7 +64,13 @@ export function createClient(

// When rate-limiting occurs, the response is an error message
if (response.status === 429) {
throw new Error(await response.text())
const retryAfter = response.headers.get('retry-after')
const limit = response.headers.get('x-ratelimit-limit')
throw new RateLimitError(
await response.text(),
limit ? parseInt(limit, 10) : undefined,
retryAfter ? new Date(retryAfter) : undefined
)
}

if (response.body === null) {
Expand Down
7 changes: 4 additions & 3 deletions vscode/src/completions/getInlineCompletions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import * as vscode from 'vscode'
import { URI } from 'vscode-uri'

import { CodebaseContext } from '@sourcegraph/cody-shared/src/codebase-context'
import { isAbortError } from '@sourcegraph/cody-shared/src/sourcegraph-api/errors'

import { debug } from '../log'
import { logError } from '../log'

import { GetContextOptions, GetContextResult } from './context/context'
import { DocumentHistory } from './context/history'
Expand All @@ -15,7 +16,7 @@ import { RequestManager, RequestParams } from './request-manager'
import { reuseLastCandidate } from './reuse-last-candidate'
import { ProvideInlineCompletionsItemTraceData } from './tracer'
import { InlineCompletionItem } from './types'
import { isAbortError, SNIPPET_WINDOW_SIZE } from './utils'
import { SNIPPET_WINDOW_SIZE } from './utils'

export interface InlineCompletionsParams {
// Context
Expand Down Expand Up @@ -121,7 +122,7 @@ export async function getInlineCompletions(params: InlineCompletionsParams): Pro
const error = unknownError instanceof Error ? unknownError : new Error(unknownError as any)

params.tracer?.({ error: error.toString() })
debug('getInlineCompletions:error', error.message, { verbose: error })
logError('getInlineCompletions:error', error.message, error.stack, { verbose: { params, error } })
CompletionLogger.logError(error)

if (isAbortError(error)) {
Expand Down
2 changes: 1 addition & 1 deletion vscode/src/completions/logger.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { LRUCache } from 'lru-cache'
import * as vscode from 'vscode'

import { isAbortError, isRateLimitError } from '@sourcegraph/cody-shared/src/sourcegraph-api/errors'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💜

import { TelemetryEventProperties } from '@sourcegraph/cody-shared/src/telemetry'

import { logEvent } from '../services/EventLogger'

import { ContextSummary } from './context/context'
import { InlineCompletionItem } from './types'
import { isAbortError, isRateLimitError } from './utils'

export interface CompletionEvent {
params: {
Expand Down
15 changes: 9 additions & 6 deletions vscode/src/completions/providers/createProvider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Configuration } from '@sourcegraph/cody-shared/src/configuration'
import { FeatureFlag, FeatureFlagProvider } from '@sourcegraph/cody-shared/src/experimentation/FeatureFlagProvider'

import { debug } from '../../log'
import { logError } from '../../log'
import { CodeCompletionsClient } from '../client'

import { createProviderConfig as createAnthropicProviderConfig } from './anthropic'
Expand All @@ -25,7 +25,7 @@ export async function createProviderConfig(
})
}

debug(
logError(
'createProviderConfig',
'Provider `unstable-codegen` can not be used without configuring `cody.autocomplete.advanced.serverEndpoint`.'
)
Expand All @@ -39,23 +39,23 @@ export async function createProviderConfig(
})
}

debug(
logError(
'createProviderConfig',
'Provider `unstable-huggingface` can not be used without configuring `cody.autocomplete.advanced.serverEndpoint`.'
)
return null
}
case 'unstable-azure-openai': {
if (config.autocompleteAdvancedServerEndpoint === null) {
debug(
logError(
'createProviderConfig',
'Provider `unstable-azure-openai` can not be used without configuring `cody.autocomplete.advanced.serverEndpoint`.'
)
return null
}

if (config.autocompleteAdvancedAccessToken === null) {
debug(
logError(
'createProviderConfig',
'Provider `unstable-azure-openai` can not be used without configuring `cody.autocomplete.advanced.accessToken`.'
)
Expand All @@ -80,7 +80,10 @@ export async function createProviderConfig(
})
}
default:
debug('createProviderConfig', `Unrecognized provider '${config.autocompleteAdvancedProvider}' configured.`)
logError(
'createProviderConfig',
`Unrecognized provider '${config.autocompleteAdvancedProvider}' configured.`
)
return null
}
}
Expand Down
3 changes: 2 additions & 1 deletion vscode/src/completions/providers/unstable-codegen.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import fetch from 'isomorphic-fetch'

import { isAbortError } from '@sourcegraph/cody-shared/src/sourcegraph-api/errors'

import { logger } from '../../log'
import { Completion, ContextSnippet } from '../types'
import { isAbortError } from '../utils'

import { Provider, ProviderConfig, ProviderOptions } from './provider'

Expand Down
3 changes: 2 additions & 1 deletion vscode/src/completions/providers/unstable-huggingface.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import fetch from 'isomorphic-fetch'

import { isAbortError } from '@sourcegraph/cody-shared/src/sourcegraph-api/errors'

import { logger } from '../../log'
import { getLanguageConfig } from '../language'
import { Completion, ContextSnippet } from '../types'
import { isAbortError } from '../utils'

import { Provider, ProviderConfig, ProviderOptions } from './provider'

Expand Down
14 changes: 0 additions & 14 deletions vscode/src/completions/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,6 @@ export function lastNLines(text: string, n: number): string {
return lines.slice(Math.max(0, lines.length - n)).join('\n')
}

export function isAbortError(error: Error): boolean {
return (
// http module
error.message === 'aborted' ||
// fetch
error.message.includes('The operation was aborted') ||
error.message.includes('The user aborted a request')
)
}

export function isRateLimitError(error: Error): boolean {
return error.message.includes('you exceeded the rate limit')
}

/**
* Creates a new signal that forks a parent signal. When the parent signal is aborted, the forked
* signal will be aborted as well. This allows propagating abort signals across asynchronous
Expand Down
Loading