Skip to content

Commit

Permalink
add EditHandler for 'edit' and 'insert' modes
Browse files Browse the repository at this point in the history
  • Loading branch information
beyang committed Dec 27, 2024
1 parent 60c6d7a commit 9ace49a
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 55 deletions.
4 changes: 2 additions & 2 deletions lib/shared/src/chat/transcript/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export interface ChatMessage extends Message {
model?: string

/* The detected intent of the message */
intent?: 'search' | 'chat' | undefined | null
manuallySelectedIntent?: 'search' | 'chat' | undefined | null
intent?: 'search' | 'chat' | 'edit' | 'insert' | undefined | null
manuallySelectedIntent?: 'search' | 'chat' | 'edit' | 'insert' | undefined | null
search?: ChatMessageSearch | undefined | null
}

Expand Down
2 changes: 2 additions & 0 deletions lib/shared/src/telemetry-v2/events/chat-question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ export const events = [
auto: 1,
chat: 2,
search: 3,
edit: 4,
insert: 5,
} satisfies Record<
typeof fallbackValue | 'auto' | Exclude<ChatMessage['intent'], null | undefined>,
number
Expand Down
8 changes: 5 additions & 3 deletions vscode/src/chat/chat-view/ChatController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,7 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv
if (!model) {
throw new Error('No model selected, and no default chat model is available')
}

this.chatBuilder.setSelectedModel(model)

const recorder = await OmniboxTelemetry.create({
Expand All @@ -794,12 +795,13 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv
signal,
})
signal.throwIfAborted()

this.chatBuilder.setLastMessageIntent(intent)
this.postEmptyMessageInProgress(model)

const agentName = intent === 'search' ? 'search' : model
this.postEmptyMessageInProgress(model)

const agentName = ['search', 'edit', 'insert'].includes(intent ?? '')
? (intent as string)
: model
const agent = getAgent(agentName, {
contextRetriever: this.contextRetriever,
editor: this.editor,
Expand Down
104 changes: 54 additions & 50 deletions vscode/src/chat/chat-view/handlers/ChatHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export class ChatHandler implements AgentHandler {

// Overridable by subclasses that want to customize context computation
protected async computeContext(
requestID: string,
_requestID: string,
{ text, mentions }: HumanInput,
editorState: SerializedPromptEditorState | null,
_chatBuilder: ChatBuilder,
Expand All @@ -227,7 +227,9 @@ export class ChatHandler implements AgentHandler {
}> {
try {
return wrapInActiveSpan('chat.computeContext', async span => {
const contextAlternatives = await this.computeContextAlternatives(
const contextAlternatives = await computeContextAlternatives(
this.contextRetriever,
this.editor,
{ text, mentions },
editorState,
span,
Expand All @@ -239,57 +241,59 @@ export class ChatHandler implements AgentHandler {
return { error: new Error(`Unexpected error computing context, no context was used: ${e}`) }
}
}
}

private async computeContextAlternatives(
{ text, mentions }: HumanInput,
editorState: SerializedPromptEditorState | null,
span: Span,
signal?: AbortSignal
): Promise<RankedContext[]> {
// Remove context chips (repo, @-mentions) from the input text for context retrieval.
const inputTextWithoutContextChips = editorState
? PromptString.unsafe_fromUserQuery(
inputTextWithoutContextChipsFromPromptEditorState(editorState)
)
: text
const structuredMentions = toStructuredMentions(mentions)
const retrievedContextPromise = this.contextRetriever.retrieveContext(
structuredMentions,
inputTextWithoutContextChips,
span,
signal
)
const priorityContextPromise = retrievedContextPromise
.then(p => getPriorityContext(text, this.editor, p))
.catch(() => getPriorityContext(text, this.editor, []))
const openCtxContextPromise = getContextForChatMessage(text.toString(), signal)
const [priorityContext, retrievedContext, openCtxContext] = await Promise.all([
priorityContextPromise,
retrievedContextPromise.catch(e => {
throw new Error(`Failed to retrieve search context: ${e}`)
}),
openCtxContextPromise,
])
export async function computeContextAlternatives(
contextRetriever: Pick<ContextRetriever, 'retrieveContext'>,
editor: ChatControllerOptions['editor'],
{ text, mentions }: HumanInput,
editorState: SerializedPromptEditorState | null,
span: Span,
signal?: AbortSignal
): Promise<RankedContext[]> {
// Remove context chips (repo, @-mentions) from the input text for context retrieval.
const inputTextWithoutContextChips = editorState
? PromptString.unsafe_fromUserQuery(

Check failure on line 256 in vscode/src/chat/chat-view/handlers/ChatHandler.ts

View workflow job for this annotation

GitHub Actions / safe-prompts-lint

New `unsafe_fromUserQuery` invocation found. This is not safe. Please use one of the PromptString helpers instead.
inputTextWithoutContextChipsFromPromptEditorState(editorState)
)
: text
const structuredMentions = toStructuredMentions(mentions)
const retrievedContextPromise = contextRetriever.retrieveContext(
structuredMentions,
inputTextWithoutContextChips,
span,
signal
)
const priorityContextPromise = retrievedContextPromise
.then(p => getPriorityContext(text, editor, p))
.catch(() => getPriorityContext(text, editor, []))
const openCtxContextPromise = getContextForChatMessage(text.toString(), signal)
const [priorityContext, retrievedContext, openCtxContext] = await Promise.all([
priorityContextPromise,
retrievedContextPromise.catch(e => {
throw new Error(`Failed to retrieve search context: ${e}`)
}),
openCtxContextPromise,
])

const resolvedExplicitMentionsPromise = resolveContextItems(
this.editor,
[structuredMentions.symbols, structuredMentions.files, structuredMentions.openCtx].flat(),
text,
signal
)
const resolvedExplicitMentionsPromise = resolveContextItems(
editor,
[structuredMentions.symbols, structuredMentions.files, structuredMentions.openCtx].flat(),
text,
signal
)

return [
{
strategy: 'local+remote',
items: combineContext(
await resolvedExplicitMentionsPromise,
openCtxContext,
priorityContext,
retrievedContext
),
},
]
}
return [
{
strategy: 'local+remote',
items: combineContext(
await resolvedExplicitMentionsPromise,
openCtxContext,
priorityContext,
retrievedContext
),
},
]
}

// This is the manual ordering of the different retrieved and explicit context sources
Expand Down
119 changes: 119 additions & 0 deletions vscode/src/chat/chat-view/handlers/EditHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import {
DefaultEditCommands,
PromptString,
inputTextWithoutContextChipsFromPromptEditorState,
} from '@sourcegraph/cody-shared'
import { executeCodyCommand } from '../../../commands/CommandsController'
import type { ChatControllerOptions } from '../ChatController'
import type { ContextRetriever } from '../ContextRetriever'
import { computeContextAlternatives } from './ChatHandler'
import type { AgentHandler, AgentHandlerDelegate, AgentRequest } from './interfaces'

export class EditHandler implements AgentHandler {
constructor(
private mode: 'edit' | 'insert',
private contextRetriever: Pick<ContextRetriever, 'retrieveContext'>,
private readonly editor: ChatControllerOptions['editor']
) {}

async handle(
{
requestID,
inputText,
mentions,
editorState,
span,
signal,
chatBuilder,
recorder,
}: AgentRequest,
delegate: AgentHandlerDelegate
): Promise<void> {
const contextAlternatives = await computeContextAlternatives(
this.contextRetriever,
this.editor,
{ text: inputText, mentions },
editorState,
span,
signal
)
signal.throwIfAborted()
const context = contextAlternatives[0].items

chatBuilder.setLastMessageContext(context, contextAlternatives)

const inputTextWithoutContextChips = editorState
? PromptString.unsafe_fromUserQuery(

Check failure on line 46 in vscode/src/chat/chat-view/handlers/EditHandler.ts

View workflow job for this annotation

GitHub Actions / safe-prompts-lint

New `unsafe_fromUserQuery` invocation found. This is not safe. Please use one of the PromptString helpers instead.
inputTextWithoutContextChipsFromPromptEditorState(editorState)
)
: inputText

recorder.recordChatQuestionExecuted(context, { addMetadata: true, current: span })

const result = await executeCodyCommand(DefaultEditCommands.Edit, {
requestID,
runInChatMode: true,
userContextFiles: context,
configuration: {
instruction: inputTextWithoutContextChips,
mode: this.mode,
// Only document code uses non-edit (insert mode), set doc intent for Document code prompt
// to specialize cody command runner for document code case.
intent: this.mode === 'edit' ? 'edit' : 'doc',
},
})
if (result?.type !== 'edit' || !result.task) {
delegate.postError(new Error('Failed to execute edit command'), 'transcript')
delegate.postDone()
return
}

const task = result.task

let responseMessage = `Here is the response for the ${task.intent} instruction:\n`

if (!task.diff && task.replacement) {
task.diff = [
{
type: 'insertion',
text: task.replacement,
range: task.originalRange,
},
]
}

task.diff?.map(diff => {
responseMessage += '\n```diff\n'
if (diff.type === 'deletion') {
responseMessage += task.document
.getText(diff.range)
.split('\n')
.map(line => `- ${line}`)
.join('\n')
}
if (diff.type === 'decoratedReplacement') {
responseMessage += diff.oldText
.split('\n')
.map(line => `- ${line}`)
.join('\n')
responseMessage += diff.text
.split('\n')
.map(line => `+ ${line}`)
.join('\n')
}
if (diff.type === 'insertion') {
responseMessage += diff.text
.split('\n')
.map(line => `+ ${line}`)
.join('\n')
}
responseMessage += '\n```'
})

delegate.postMessageInProgress({
speaker: 'assistant',
text: PromptString.unsafe_fromLLMResponse(responseMessage),

Check failure on line 115 in vscode/src/chat/chat-view/handlers/EditHandler.ts

View workflow job for this annotation

GitHub Actions / safe-prompts-lint

New `unsafe_fromLLMResponse` invocation found. This is not safe. Please use one of the PromptString helpers instead.
})
delegate.postDone()
}
}
11 changes: 11 additions & 0 deletions vscode/src/chat/chat-view/handlers/registry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ChatHandler } from './ChatHandler'
import { DeepCodyHandler } from './DeepCodyHandler'
import { EditHandler } from './EditHandler'
import { SearchHandler } from './SearchHandler'
import type { AgentHandler, AgentTools } from './interfaces'

Expand Down Expand Up @@ -28,3 +29,13 @@ registerAgent(
new DeepCodyHandler(id, contextRetriever, editor, chatClient, codyToolProvider)
)
registerAgent('search', (_id: string, _tools: AgentTools) => new SearchHandler())
registerAgent(
'edit',
(_id: string, { contextRetriever, editor }: AgentTools) =>
new EditHandler('edit', contextRetriever, editor)
)
registerAgent(
'insert',
(_id: string, { contextRetriever, editor }: AgentTools) =>
new EditHandler('insert', contextRetriever, editor)
)

0 comments on commit 9ace49a

Please sign in to comment.