Skip to content

Commit 1260dce

Browse files
authored
fix: add grepSearch implementation (#1359)
* fix: add grepSearch implementation * fix: update UI to match mock
1 parent f930297 commit 1260dce

File tree

3 files changed

+614
-14
lines changed

3 files changed

+614
-14
lines changed

server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ import { FsWrite, FsWriteParams } from './tools/fsWrite'
109109
import { ExecuteBash, ExecuteBashParams } from './tools/executeBash'
110110
import { ExplanatoryParams, ToolApprovalException } from './tools/toolShared'
111111
import { FileSearch, FileSearchParams } from './tools/fileSearch'
112+
import { GrepSearch, SanitizedRipgrepOutput } from './tools/grepSearch'
112113
import { loggingUtils } from '@aws/lsp-core'
113114
import { diffLines } from 'diff'
114115
import {
@@ -917,13 +918,15 @@ export class AgenticChatController implements ChatHandlers {
917918
case 'fsRead':
918919
case 'listDirectory':
919920
case 'fileSearch':
921+
case 'grepSearch':
920922
case 'fsWrite':
921923
case 'executeBash': {
922924
const toolMap = {
923925
fsRead: { Tool: FsRead },
924926
listDirectory: { Tool: ListDirectory },
925927
fsWrite: { Tool: FsWrite },
926928
executeBash: { Tool: ExecuteBash },
929+
grepSearch: { Tool: GrepSearch },
927930
fileSearch: { Tool: FileSearch },
928931
}
929932

@@ -1033,8 +1036,11 @@ export class AgenticChatController implements ChatHandlers {
10331036
// no need to write tool result for listDir and fsRead into chat stream
10341037
// executeBash will stream the output instead of waiting until the end
10351038
break
1036-
case 'codeSearch':
1037-
// no need to write tool result for code search.
1039+
case 'grepSearch':
1040+
const grepSearchResult = this.#processGrepSearchResult(toolUse, result, chatResultStream)
1041+
if (grepSearchResult) {
1042+
await chatResultStream.writeResultBlock(grepSearchResult)
1043+
}
10381044
break
10391045
case 'fsWrite':
10401046
const input = toolUse.input as unknown as FsWriteParams
@@ -1707,6 +1713,72 @@ export class AgenticChatController implements ChatHandlers {
17071713
}
17081714
}
17091715

1716+
/**
1717+
* Process grep search results and format them for display in the chat UI
1718+
*/
1719+
#processGrepSearchResult(
1720+
toolUse: ToolUse,
1721+
result: any,
1722+
chatResultStream: AgenticChatResultStream
1723+
): ChatMessage | undefined {
1724+
if (toolUse.name !== 'grepSearch') {
1725+
return undefined
1726+
}
1727+
1728+
let messageIdToUpdate = toolUse.toolUseId!
1729+
const currentId = chatResultStream.getMessageIdToUpdateForTool(toolUse.name!)
1730+
1731+
if (currentId) {
1732+
messageIdToUpdate = currentId
1733+
} else {
1734+
chatResultStream.setMessageIdToUpdateForTool(toolUse.name!, messageIdToUpdate)
1735+
}
1736+
1737+
// Extract search results from the tool output
1738+
const output = result.output.content as SanitizedRipgrepOutput
1739+
if (!output || !output.fileMatches || !Array.isArray(output.fileMatches)) {
1740+
return {
1741+
type: 'tool',
1742+
messageId: messageIdToUpdate,
1743+
body: 'No search results found.',
1744+
}
1745+
}
1746+
1747+
// Process the matches into a structured format
1748+
const matches = output.fileMatches
1749+
const fileDetails: Record<string, FileDetails> = {}
1750+
1751+
// Create file details directly from matches
1752+
for (const match of matches) {
1753+
const filePath = match.filePath
1754+
if (!filePath) continue
1755+
1756+
fileDetails[`${filePath} (${match.matches.length} ${match.matches.length <= 1 ? 'result' : 'results'})`] = {
1757+
description: filePath,
1758+
lineRanges: [{ first: -1, second: -1 }],
1759+
}
1760+
}
1761+
1762+
// Create sorted array of file paths
1763+
const sortedFilePaths = Object.keys(fileDetails)
1764+
1765+
// Create the context list for display
1766+
const query = (toolUse.input as any)?.query || 'search term'
1767+
1768+
const contextList: FileList = {
1769+
rootFolderTitle: `Grepped for "${query}", ${output.matchCount} ${output.matchCount <= 1 ? 'result' : 'results'} found`,
1770+
filePaths: sortedFilePaths,
1771+
details: fileDetails,
1772+
}
1773+
1774+
return {
1775+
type: 'tool',
1776+
fileList: contextList,
1777+
messageId: messageIdToUpdate,
1778+
body: '',
1779+
}
1780+
}
1781+
17101782
/**
17111783
* Updates the request input with tool results for the next iteration
17121784
*/
@@ -1724,7 +1796,7 @@ export class AgenticChatController implements ChatHandlers {
17241796
updatedRequestInput.conversationState!.currentMessage!.userInputMessage!.content = content
17251797

17261798
for (const toolResult of toolResults) {
1727-
this.#debug(`ToolResult: ${JSON.stringify(toolResult)}`)
1799+
this.#debug(`ToolResult: ${JSON.stringify(toolResult)} `)
17281800
updatedRequestInput.conversationState!.currentMessage!.userInputMessage!.userInputMessageContext!.toolResults.push(
17291801
{
17301802
...toolResult,
@@ -1854,31 +1926,33 @@ export class AgenticChatController implements ChatHandlers {
18541926
}
18551927

18561928
if (authFollowType) {
1857-
this.#log(`Q auth error: ${getErrorMessage(err)}`)
1929+
this.#log(`Q auth error: ${getErrorMessage(err)} `)
18581930

18591931
return createAuthFollowUpResult(authFollowType)
18601932
}
18611933

18621934
if (customerFacingErrorCodes.includes(err.code)) {
1863-
this.#features.logging.error(`${loggingUtils.formatErr(err)}`)
1935+
this.#features.logging.error(`${loggingUtils.formatErr(err)} `)
18641936
if (err.code === 'InputTooLong') {
18651937
// Clear the chat history in the database for this tab
18661938
this.#chatHistoryDb.clearTab(tabId)
18671939
}
18681940

18691941
const errorBody =
1870-
err.code === 'QModelResponse' && requestID ? `${err.message}\n\nRequest ID: ${requestID}` : err.message
1942+
err.code === 'QModelResponse' && requestID
1943+
? `${err.message} \n\nRequest ID: ${requestID} `
1944+
: err.message
18711945
return new ResponseError<ChatResult>(LSPErrorCodes.RequestFailed, err.message, {
18721946
type: 'answer',
18731947
body: errorBody,
18741948
messageId: errorMessageId,
18751949
buttons: [],
18761950
})
18771951
}
1878-
this.#features.logging.error(`Unknown Error: ${loggingUtils.formatErr(err)}`)
1952+
this.#features.logging.error(`Unknown Error: ${loggingUtils.formatErr(err)} `)
18791953
return new ResponseError<ChatResult>(LSPErrorCodes.RequestFailed, err.message, {
18801954
type: 'answer',
1881-
body: requestID ? `${genericErrorMsg}\n\nRequest ID: ${requestID}` : genericErrorMsg,
1955+
body: requestID ? `${genericErrorMsg} \n\nRequest ID: ${requestID} ` : genericErrorMsg,
18821956
messageId: errorMessageId,
18831957
buttons: [],
18841958
})
@@ -1914,10 +1988,10 @@ export class AgenticChatController implements ChatHandlers {
19141988
this.#log('Response for inline chat', JSON.stringify(response.$metadata), JSON.stringify(response))
19151989
} catch (err) {
19161990
if (err instanceof AmazonQServicePendingSigninError || err instanceof AmazonQServicePendingProfileError) {
1917-
this.#log(`Q Inline Chat SSO Connection error: ${getErrorMessage(err)}`)
1991+
this.#log(`Q Inline Chat SSO Connection error: ${getErrorMessage(err)} `)
19181992
return new ResponseError<ChatResult>(LSPErrorCodes.RequestFailed, err.message)
19191993
}
1920-
this.#log(`Q api request error ${err instanceof Error ? JSON.stringify(err) : 'unknown'}`)
1994+
this.#log(`Q api request error ${err instanceof Error ? JSON.stringify(err) : 'unknown'} `)
19211995
return new ResponseError<ChatResult>(
19221996
LSPErrorCodes.RequestFailed,
19231997
err instanceof Error ? err.message : 'Unknown request error'
@@ -1963,7 +2037,7 @@ export class AgenticChatController implements ChatHandlers {
19632037
if (!params.code) missingParams.push('code')
19642038

19652039
this.#log(
1966-
`Q Chat server failed to insert code. Missing required parameters for insert code: ${missingParams.join(', ')}`
2040+
`Q Chat server failed to insert code.Missing required parameters for insert code: ${missingParams.join(', ')} `
19672041
)
19682042

19692043
return
@@ -2030,7 +2104,7 @@ export class AgenticChatController implements ChatHandlers {
20302104
this.#telemetryController.enqueueCodeDiffEntry({ ...params, code: textWithIndent })
20312105
} else {
20322106
this.#log(
2033-
`Q Chat server failed to insert code: ${applyResult.failureReason ?? 'No failure reason provided'}`
2107+
`Q Chat server failed to insert code: ${applyResult.failureReason ?? 'No failure reason provided'} `
20342108
)
20352109
}
20362110
}
@@ -2195,9 +2269,9 @@ export class AgenticChatController implements ChatHandlers {
21952269
return path.join(getUserPromptsDirectory(), relativePath)
21962270
}
21972271

2198-
this.#features.logging.error(`File not found: ${relativePath}`)
2272+
this.#features.logging.error(`File not found: ${relativePath} `)
21992273
} catch (e: any) {
2200-
this.#features.logging.error(`Error resolving absolute path: ${e.message}`)
2274+
this.#features.logging.error(`Error resolving absolute path: ${e.message} `)
22012275
}
22022276

22032277
return undefined

0 commit comments

Comments
 (0)