Skip to content

feat: initial fsWrite chat message #1026

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

Merged
merged 1 commit into from
Apr 21, 2025
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
19 changes: 18 additions & 1 deletion chat-client/src/client/mynahUi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,24 @@ export const createMynahUi = (

if (isPartialResult) {
// type for MynahUI differs from ChatResult types so we ignore it
mynahUi.updateLastChatAnswer(tabId, { ...chatResultWithoutType, header: header, buttons: buttons })
mynahUi.updateLastChatAnswer(tabId, {
...chatResultWithoutType,
header: header,
buttons: buttons,
// TODO: this won't be needed once we have multiple message support
...(type === 'tool'
? {
header: {
...header,
// TODO: this is specific to fsWrite tool, modify for other tools as needed
fileList: { ...header?.fileList, fileTreeTitle: '', hideFileCount: true },
Copy link
Contributor

Choose a reason for hiding this comment

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

is only this line going to be modified for other tools or all of it? If so, just wondering if it makes sense to pull this out to a different file so that its easy to understand which tools have which interface

Copy link
Contributor

@Hweinstock Hweinstock Apr 21, 2025

Choose a reason for hiding this comment

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

+1 to separating this logic out.

buttons: header?.buttons?.map(button => ({ ...button, status: 'clear' })),
},
fullWidth: true,
padding: false,
}
: {}),
})
return
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Will be deleted or merged.
*/

import * as path from 'path'

Check warning on line 6 in server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts

View workflow job for this annotation

GitHub Actions / Test (Windows)

Do not import Node.js builtin module "path"

Check warning on line 6 in server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts

View workflow job for this annotation

GitHub Actions / Test

Do not import Node.js builtin module "path"
import {
ChatTriggerType,
GenerateAssistantResponseCommandInput,
Expand Down Expand Up @@ -94,6 +94,7 @@
import { workspaceUtils } from '@aws/lsp-core'
import { FsReadParams } from './tools/fsRead'
import { ListDirectoryParams } from './tools/listDirectory'
import { FsWrite, FsWriteParams } from './tools/fsWrite'

type ChatHandlers = Omit<
LspHandlers<Chat>,
Expand Down Expand Up @@ -425,9 +426,18 @@
content: [toolResultContent],
})

// no need to write tool result for listDir and fsRead into chat stream
if (toolUse.name !== 'fsRead' && toolUse.name !== 'listDirectory') {
await chatResultStream.writeResultBlock({ body: toolResultMessage(toolUse, result) })
switch (toolUse.name) {
Copy link
Contributor

@Hweinstock Hweinstock Apr 21, 2025

Choose a reason for hiding this comment

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

For the sake of future MCP support, I think this tool specific branching should be isolated from the core logic. This also won't scale well as we add more tools.

case 'fsRead':
case 'listDirectory':
// no need to write tool result for listDir and fsRead into chat stream
Copy link
Contributor

@laileni-aws laileni-aws Apr 21, 2025

Choose a reason for hiding this comment

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

nit: Can I know the reason, Why ? May be I am missing some context here!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we are sending it before running the tool above, but actually it might make more sense to move it here

Copy link
Contributor

Choose a reason for hiding this comment

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

We should send the tool use with the list of paths before we run it, so we tell the user what we're doing before we're actually doing it.

I can see us update a spinner to a checkmark here upon completion.

But we don't have to repeat the tool output (the list of files, the status of the edit) unless there is an error.

break
case 'fsWrite':
const chatResult = await this.#getFsWriteChatResult(toolUse)
await chatResultStream.writeResultBlock(chatResult)
break
default:
await chatResultStream.writeResultBlock({ body: toolResultMessage(toolUse, result) })
break
}
} catch (err) {
const errMsg = err instanceof Error ? err.message : 'unknown error'
Expand All @@ -446,6 +456,36 @@
return results
}

async #getFsWriteChatResult(toolUse: ToolUse): Promise<ChatResult> {
const input = toolUse.input as unknown as FsWriteParams
const fileName = path.basename(input.path)
// TODO: right now diff changes is coupled with fsWrite class, we should move it to shared utils
const fsWrite = new FsWrite(this.#features)
const diffChanges = await fsWrite.getDiffChanges(input)
Copy link
Contributor

@kmile kmile Apr 21, 2025

Choose a reason for hiding this comment

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

This could be a static, but even better would be in a shared util like you mentioned so the representation is not coupled with the tool itself.

const changes = diffChanges.reduce(
(acc, { count = 0, added, removed }) => {
if (added) {
acc.added += count
} else if (removed) {
acc.deleted += count
}
return acc
},
{ added: 0, deleted: 0 }
)
return {
type: 'tool',
messageId: toolUse.toolUseId,
header: {
fileList: {
filePaths: [fileName],
details: { [fileName]: { changes } },
},
buttons: [{ id: 'undo-changes', text: 'Undo', icon: 'undo' }],
},
}
}

#processReadOrList(toolUse: ToolUse, chatResultStream: AgenticChatResultStream): ChatResult | undefined {
// return initial message about fsRead or listDir
const toolUseId = toolUse.toolUseId!
Expand Down
Loading