Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

Add basic completions #49669

Merged
merged 6 commits into from
Mar 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions client/cody/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@
{
"command": "cody.delete-access-token",
"title": "Cody: Delete Access Token"
},
{
"command": "cody.experimental.suggest",
"title": "Ask Cody: View Suggestions"
}
],
"keybindings": [
Expand Down Expand Up @@ -218,8 +222,18 @@
"blended"
],
"default": "embeddings"
},
"cody.experimental.suggestions": {
"type": "boolean",
"default": false
},
"cody.experimental.keys.openai": {
"type": "string"
}
}
}
},
"dependencies": {
"openai": "^3.2.1"
}
}
22 changes: 22 additions & 0 deletions client/cody/src/command/CommandsProvider.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as openai from 'openai'
import * as vscode from 'vscode'

import { ChatViewProvider } from '../chat/ChatViewProvider'
import { CodyCompletionItemProvider } from '../completions'
import { CompletionsDocumentProvider } from '../completions/docprovider'
import { getConfiguration } from '../configuration'
import { ExtensionApi } from '../extension-api'

Expand Down Expand Up @@ -96,6 +99,25 @@ export const CommandsProvider = async (context: vscode.ExtensionContext): Promis
)
)

if (config.experimentalSuggest && config.openaiKey) {
const configuration = new openai.Configuration({
apiKey: config.openaiKey,
})
const openaiApi = new openai.OpenAIApi(configuration)
const docprovider = new CompletionsDocumentProvider()
vscode.workspace.registerTextDocumentContentProvider('cody', docprovider)

const completionsProvider = new CodyCompletionItemProvider(openaiApi, docprovider)
context.subscriptions.push(
vscode.commands.registerCommand('cody.experimental.suggest', async () => {
await completionsProvider.fetchAndShowCompletions()
})
)
context.subscriptions.push(
vscode.languages.registerInlineCompletionItemProvider({ scheme: 'file' }, completionsProvider)
)
}

// Watch all relevant configuration and secrets for changes.
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration(async event => {
Expand Down
104 changes: 104 additions & 0 deletions client/cody/src/completions/docprovider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as openai from 'openai'
import * as vscode from 'vscode'

// FIXME: When OpenAI's logit_bias uses a more precise type than 'object',
// specify JSON-able objects as { [prop: string]: JSONSerialiable | undefined }
export type JSONSerializable = null | string | number | boolean | object | JSONSerializable[]

interface Meta {
elapsedMillis: number
prompt: string
suffix: string
llmOptions: JSONSerializable
}

export interface CompletionGroup {
lang: string
prefixText: string
completions: openai.CreateChatCompletionResponse
meta?: Meta
}

export class CompletionsDocumentProvider implements vscode.TextDocumentContentProvider {
private completionsByUri: { [uri: string]: CompletionGroup[] } = {}

private isDebug(): boolean {
return vscode.workspace.getConfiguration().get<boolean>('cody.debug') === true
}

private fireDocumentChanged(uri: vscode.Uri): void {
this.onDidChangeEmitter.fire(uri)
}

public clearCompletions(uri: vscode.Uri): void {
delete this.completionsByUri[uri.toString()]
this.fireDocumentChanged(uri)
}

public addCompletions(
uri: vscode.Uri,
lang: string,
prefixText: string,
completions: openai.CreateChatCompletionResponse,
debug?: Meta
): void {
if (!this.completionsByUri[uri.toString()]) {
this.completionsByUri[uri.toString()] = []
}

this.completionsByUri[uri.toString()].push({
lang,
prefixText,
completions,
meta: debug,
})
this.fireDocumentChanged(uri)
}

public onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>()
public onDidChange = this.onDidChangeEmitter.event

public provideTextDocumentContent(uri: vscode.Uri): string {
const completionGroups = this.completionsByUri[uri.toString()]
if (!completionGroups) {
return 'Loading...'
}

return completionGroups
.map(({ completions, lang, prefixText, meta }) =>
completions.choices
.map(({ message, finish_reason }, index) => {
if (!message?.content) {
return undefined
}

let completionText = `\`\`\`${lang}\n${prefixText}${message.content}\n\`\`\``
if (this.isDebug() && meta) {
completionText =
`\`\`\`\n${meta.prompt}\n\`\`\`` +
'\n' +
completionText +
'\n' +
`\`\`\`\n${meta.suffix}\n\`\`\``
}
const headerComponents = [`${index + 1} / ${completions.choices.length}`]
if (finish_reason) {
headerComponents.push(`finish_reason:${finish_reason}`)
}
return headerize(headerComponents.join(', '), 80) + '\n' + completionText
})
.filter(t => t)
.join('\n\n')
)
.join('\n\n')
}
}

function headerize(label: string, width: number): string {
const prefix = '# ======= '
let buffer = width - label.length - prefix.length - 1
if (buffer < 0) {
buffer = 0
}
return `${prefix}${label} ${'='.repeat(buffer)}`
}
Loading