Skip to content

Commit

Permalink
wip: add diagnostic context and edit mode
Browse files Browse the repository at this point in the history
This commit introduces the following changes:

-   Adds `DIAGNOSTIC_CONTEXT_MENTION_PROVIDER` to `lib/shared/src/mentions/api.ts`, enabling the retrieval of diagnostic information as context in chat.
-   Integrates diagnostic context into `vscode/src/chat/context/chatContext.ts` and `vscode/src/chat/chat-view/handlers/registry.ts`.
-   Adds a new `AgenticEditHandler` in `vscode/src/chat/chat-view/handlers/AgenticEditHandler.ts` to handle edit mode requests from the agent.
-   Implements `chatDiff` function in `vscode/src/non-stop/line-diff.ts` to display the diff in chat.
-   Updates `vscode/src/chat/agentic/DeepCody.ts` to include `OmniboxAgentResponse` and `OmniboxModes` for agent responses.
-   Updates `vscode/src/chat/chat-view/handlers/AgenticHandler.ts` to integrate the new `AgenticEditHandler` and handle edit mode.
-   Updates `vscode/webviews/chat/Transcript.tsx` to remove the `DeepCodyAgentID` check.
-   Adds `vscode/src/commands/context/diagnostic.ts` to retrieve context from diagnostics.
-   Adds the ability to show the diff in the chat UI.

This change enables the agent to provide code edits and use diagnostic information as context.

Test plan:

-   Verify that diagnostic information is correctly retrieved and displayed as context in chat.
-   Verify that the agent can successfully execute edit commands.
-   Verify that the diff is displayed correctly in the chat UI.
  • Loading branch information
abeatrix committed Feb 20, 2025
1 parent 3791ffc commit 28d2023
Show file tree
Hide file tree
Showing 10 changed files with 358 additions and 319 deletions.
1 change: 1 addition & 0 deletions lib/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ export {
openCtxProviderMetadata,
FILE_CONTEXT_MENTION_PROVIDER,
SYMBOL_CONTEXT_MENTION_PROVIDER,
DIAGNOSTIC_CONTEXT_MENTION_PROVIDER,
type ContextMentionProviderID,
type ContextMentionProviderMetadata,
} from './mentions/api'
Expand Down
19 changes: 16 additions & 3 deletions lib/shared/src/mentions/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,26 @@ export const SYMBOL_CONTEXT_MENTION_PROVIDER: ContextMentionProviderMetadata & {
emptyLabel: 'No symbols found',
}

export const DIAGNOSTIC_CONTEXT_MENTION_PROVIDER: ContextMentionProviderMetadata & { id: 'diagnostic' } =
{
id: 'diagnostic',
title: 'Problems',
queryLabel: 'Search for a problem...',
emptyLabel: 'No problem found',
}

export function mentionProvidersMetadata(options?: {
disableProviders: ContextMentionProviderID[]
}): Observable<ContextMentionProviderMetadata[]> {
return openCtxMentionProviders().map(providers =>
[...[FILE_CONTEXT_MENTION_PROVIDER, SYMBOL_CONTEXT_MENTION_PROVIDER], ...providers].filter(
provider => !options?.disableProviders.includes(provider.id)
)
[
...[
FILE_CONTEXT_MENTION_PROVIDER,
SYMBOL_CONTEXT_MENTION_PROVIDER,
DIAGNOSTIC_CONTEXT_MENTION_PROVIDER,
],
...providers,
].filter(provider => !options?.disableProviders.includes(provider.id))
)
}

Expand Down
105 changes: 61 additions & 44 deletions vscode/src/chat/agentic/DeepCody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ import { CodyToolProvider, type ToolStatusCallback } from './CodyToolProvider'
import type { ProcessManager } from './ProcessManager'
import { ACTIONS_TAGS, CODYAGENT_PROMPTS } from './prompts'

export interface OmniboxAgentResponse {
next?: OmniboxNextStep
contextItems?: ContextItem[]
error?: Error
abort?: boolean
}

interface OmniboxNextStep {
mode: OmniboxModes
query?: string
}

export type OmniboxModes = 'edit' | 'search' | 'chat'

/**
* A DeepCodyAgent handles advanced context retrieval and analysis for chat interactions.
* It uses a multi-step process to:
Expand Down Expand Up @@ -61,7 +75,7 @@ export class DeepCodyAgent {
*/
private stats = { context: 0, loop: 0 }

public nextActionMode = { mode: 'chat', query: '' }
private nextStep: OmniboxNextStep = { mode: 'chat', query: undefined }

constructor(
protected readonly chatBuilder: ChatBuilder,
Expand Down Expand Up @@ -89,6 +103,17 @@ export class DeepCodyAgent {
}
}

public async start(
requestID: string,
chatAbortSignal: AbortSignal,
context: ContextItem[]
): Promise<OmniboxAgentResponse> {
return {
next: this.nextStep,
contextItems: await this.getContext(requestID, chatAbortSignal, context),
}
}

/**
* Register the tools with the multiplexer.
*/
Expand Down Expand Up @@ -143,46 +168,37 @@ export class DeepCodyAgent {
maxLoops = 2
): Promise<ContextItem[]> {
this.context = context
return wrapInActiveSpan('DeepCody.getContext', span =>
this._getContext(requestID, span, chatAbortSignal, maxLoops)
)
}

private async _getContext(
requestID: string,
span: Span,
chatAbortSignal: AbortSignal,
maxLoops = 2
): Promise<ContextItem[]> {
span.setAttribute('sampled', true)
const startTime = performance.now()
await this.reviewLoop(requestID, span, chatAbortSignal, maxLoops)
telemetryRecorder.recordEvent('cody.deep-cody.context', 'reviewed', {
privateMetadata: {
requestID,
model: DeepCodyAgent.model,
traceId: span.spanContext().traceId,
chatAgent: 'deep-cody',
},
metadata: {
loop: this.stats.loop, // Number of loops run.
fetched: this.stats.context, // Number of context fetched.
context: this.context.length, // Number of context used.
durationMs: performance.now() - startTime,
},
billingMetadata: {
product: 'cody',
category: 'billable',
},
})
const knownModes = ['search', 'edit']
if (knownModes.includes(this.nextActionMode.mode)) {
this.statusCallback.onStream({
title: `Switch to ${this.nextActionMode.mode} mode`,
content: 'New intent detected: ' + this.nextActionMode.mode,
return wrapInActiveSpan('DeepCody.getContext', async span => {
span.setAttribute('sampled', true)
const startTime = performance.now()
await this.reviewLoop(requestID, span, chatAbortSignal, maxLoops)
telemetryRecorder.recordEvent('cody.deep-cody.context', 'reviewed', {
privateMetadata: {
requestID,
model: DeepCodyAgent.model,
traceId: span.spanContext().traceId,
chatAgent: 'deep-cody',
},
metadata: {
loop: this.stats.loop, // Number of loops run.
fetched: this.stats.context, // Number of context fetched.
context: this.context.length, // Number of context used.
durationMs: performance.now() - startTime,
},
billingMetadata: {
product: 'cody',
category: 'billable',
},
})
}
return this.context
const knownModes = ['search', 'edit']
if (knownModes.includes(this.nextStep.mode)) {
this.statusCallback.onStream({
title: `Switch to ${this.nextStep.mode} mode`,
content: 'New intent detected: ' + this.nextStep.mode,
})
}
return this.context
})
}

private async reviewLoop(
Expand Down Expand Up @@ -234,10 +250,11 @@ export class DeepCodyAgent {

const nextActionRes = nextMode(res)[0] || ''
const [mode, query] = nextActionRes.split(':')
if (mode) {
this.nextActionMode.mode = mode
this.nextActionMode.query = query || ''
if (mode === 'search') {
const validatedMode = mode === 'edit' ? 'edit' : mode === 'search' ? 'search' : undefined
if (validatedMode) {
this.nextStep.mode = validatedMode
this.nextStep.query = query
if (validatedMode === 'search') {
return []
}
}
Expand Down
112 changes: 112 additions & 0 deletions vscode/src/chat/chat-view/handlers/AgenticEditHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { type ContextItem, PromptString, ps } from '@sourcegraph/cody-shared'
import * as vscode from 'vscode'
import { getDiagnosticsTextBlock, getUpdatedDiagnostics } from '../../../commands/context/diagnostic'
import { executeEdit } from '../../../edit/execute'
import { getEditor } from '../../../editor/active-editor'
import { chatDiff } from '../../../non-stop/line-diff'
import type { AgentHandler, AgentHandlerDelegate, AgentRequest } from './interfaces'

export class AgenticEditHandler implements AgentHandler {
constructor(protected modelId: string) {}

public async handle(
req: AgentRequest,
delegate: AgentHandlerDelegate,
context?: ContextItem[]
): Promise<void> {
const editor = getEditor()?.active
if (!editor?.document) {
delegate.postError(new Error('No active editor'), 'transcript')
delegate.postDone()
return
}
const abortSignal = req.signal
const postProgressToWebview = (msgs: string[]) => {
const message = msgs.join('\n\n')
delegate.postMessageInProgress({
speaker: 'assistant',
text: PromptString.unsafe_fromLLMResponse(message),

Check failure on line 28 in vscode/src/chat/chat-view/handlers/AgenticEditHandler.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.
model: this.modelId,
})
}

const document = editor.document
const fullRange = document.validateRange(new vscode.Range(0, 0, document.lineCount, 0))
let currentDiagnostics = vscode.languages.getDiagnostics()

let attempts = 0
const MAX_ATTEMPTS = 5
let currentInstruction = req.inputText

const messageInProgress = []

while (attempts < MAX_ATTEMPTS) {
abortSignal.throwIfAborted()
attempts++
const task = await executeEdit({
configuration: {
document,
range: fullRange,
userContextFiles: context,
instruction: currentInstruction,
mode: 'edit',
intent: currentInstruction?.includes(ps`unit test`) ? 'edit' : 'edit',
},
})

if (!task) {
delegate.postError(new Error('Failed to execute edit command'), 'transcript')
delegate.postDone()
return
}

const diffs =
task.diff ||
(task.replacement
? [
{
type: 'insertion',
text: task.replacement,
range: task.originalRange,
},
]
: [])

messageInProgress.push(chatDiff(diffs, document, { showFullFile: false }))
postProgressToWebview(messageInProgress)

abortSignal.throwIfAborted()

// We need to give it time for the
const latestDiagnostics = vscode.languages.getDiagnostics()
const problems = getUpdatedDiagnostics(currentDiagnostics, latestDiagnostics)

if (!problems.length) {
break // Success! No more problems
}

if (attempts < MAX_ATTEMPTS) {
const problemText = getDiagnosticsTextBlock(problems)
const diagnosticsBlock = PromptString.unsafe_fromLLMResponse(problemText)

Check failure on line 90 in vscode/src/chat/chat-view/handlers/AgenticEditHandler.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.
const retryMessage = `Attempt ${attempts}/${MAX_ATTEMPTS}: Found issues, trying to fix:\n${problemText}`
messageInProgress.push(retryMessage)
postProgressToWebview(messageInProgress)

// Update instruction with current problems for next attempt
currentInstruction = currentInstruction.concat(
ps`\nPrevious attempt resulted in these issues:\n${diagnosticsBlock}`
)
currentDiagnostics = latestDiagnostics
}
}

if (attempts === MAX_ATTEMPTS) {
messageInProgress.push(
`Reached maximum number of attempts (${MAX_ATTEMPTS}). Some issues may remain.`
)
}

postProgressToWebview(messageInProgress)
delegate.postDone()
}
}
Loading

0 comments on commit 28d2023

Please sign in to comment.