Skip to content
Closed
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
83 changes: 30 additions & 53 deletions src/core/Cline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { ExitCodeDetails } from "../integrations/terminal/TerminalProcess"
import { Terminal } from "../integrations/terminal/Terminal"
import { TerminalRegistry } from "../integrations/terminal/TerminalRegistry"
import { UrlContentFetcher } from "../services/browser/UrlContentFetcher"
import { listFiles } from "../services/glob/list-files"
import { listFiles, ListFilesToolHandler } from "../services/glob/list-files"
import { regexSearchFiles } from "../services/ripgrep"
import { parseSourceCodeDefinitionsForFile, parseSourceCodeForDefinitionsTopLevel } from "../services/tree-sitter"
import { CheckpointStorage } from "../shared/checkpoints"
Expand Down Expand Up @@ -414,9 +414,29 @@ export class Cline extends EventEmitter<ClineEvents> {
}
}

// Communicate with webview

// partial has three valid states true (partial message), false (completion of partial message), undefined (individual complete message)
/**
* Sends a message to the webview and waits for a response.
*
* The webview will render the message as a prompt to the user, and
* will respond with a `ClineAskResponse` depending on the action
* taken by the user.
*
* The `partial` argument is a boolean value that indicates whether
* the message is a partial message or a completion of a partial
* message. If `partial` is `true`, the message will be rendered as
* a partial message to the user. If `partial` is `false`, the
* message will be rendered as a completion of the partial message.
* If `partial` is `undefined`, the message will be rendered as a
* complete message.
*
* The `progressStatus` argument is an object that indicates the
* progress of a long-running tool. It is optional.
*
* @param type - The type of the message to send.
* @param text - The text of the message to send.
* @param partial - The partial state of the message.
* @param progressStatus - The progress status of a long-running tool.
*/
async ask(
type: ClineAsk,
text?: string,
Expand Down Expand Up @@ -1315,7 +1335,9 @@ export class Cline extends EventEmitter<ClineEvents> {
//throw new Error("No more content blocks to stream! This shouldn't happen...") // remove and just return after testing
}

const block = cloneDeep(this.assistantMessageContent[this.currentStreamingContentIndex]) // need to create copy bc while stream is updating the array, it could be updating the reference block properties too
const block: AssistantMessageContent = cloneDeep(
this.assistantMessageContent[this.currentStreamingContentIndex],
) // need to create copy bc while stream is updating the array, it could be updating the reference block properties too

let isCheckpointPossible = false

Expand Down Expand Up @@ -2455,54 +2477,9 @@ export class Cline extends EventEmitter<ClineEvents> {
}

case "list_files": {
const relDirPath: string | undefined = block.params.path
const recursiveRaw: string | undefined = block.params.recursive
const recursive = recursiveRaw?.toLowerCase() === "true"
const sharedMessageProps: ClineSayTool = {
tool: !recursive ? "listFilesTopLevel" : "listFilesRecursive",
path: getReadablePath(this.cwd, removeClosingTag("path", relDirPath)),
}
try {
if (block.partial) {
const partialMessage = JSON.stringify({
...sharedMessageProps,
content: "",
} satisfies ClineSayTool)
await this.ask("tool", partialMessage, block.partial).catch(() => {})
break
} else {
if (!relDirPath) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("list_files", "path"))
break
}
this.consecutiveMistakeCount = 0
const absolutePath = path.resolve(this.cwd, relDirPath)
const [files, didHitLimit] = await listFiles(absolutePath, recursive, 200)
const { showRooIgnoredFiles = true } =
(await this.providerRef.deref()?.getState()) ?? {}
const result = formatResponse.formatFilesList(
absolutePath,
files,
didHitLimit,
this.rooIgnoreController,
showRooIgnoredFiles,
)
const completeMessage = JSON.stringify({
...sharedMessageProps,
content: result,
} satisfies ClineSayTool)
const didApprove = await askApproval("tool", completeMessage)
if (!didApprove) {
break
}
pushToolResult(result)
break
}
} catch (error) {
await handleError("listing files", error)
break
}
const handler = new ListFilesToolHandler(this, block, pushToolResult)
await handler.handle()
break
}
case "list_code_definition_names": {
const relDirPath: string | undefined = block.params.path
Expand Down
13 changes: 13 additions & 0 deletions src/core/assistant-message/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,21 @@ export interface SearchFilesToolUse extends ToolUse {
params: Partial<Pick<Record<ToolParamName, string>, "path" | "regex" | "file_pattern">>
}

/**
* Tool use for listing files in a directory.
*
*/
export interface ListFilesToolUse extends ToolUse {
name: "list_files"
/**
* Relative path to the directory to list files from.
* Defaults to the current file's directory.
*
* @param path - Relative path to the directory to list files from.
* Defaults to the current file's directory.
* @param recursive - Whether to list files recursively in the directory.
* Defaults to false.
*/
params: Partial<Pick<Record<ToolParamName, string>, "path" | "recursive">>
}

Expand Down
5 changes: 5 additions & 0 deletions src/core/prompts/tools/list-files.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { ToolArgs } from "./types"

/**
* Get the tool description for the list_files tool
* @param args The tool arguments including cwd and toolOptions
* @returns The complete tool description including format requirements and examples
*/
export function getListFilesDescription(args: ToolArgs): string {
return `## list_files
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
Expand Down
125 changes: 124 additions & 1 deletion src/services/glob/list-files.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { globby, Options } from "globby"
import os from "os"
import * as path from "path"
import { arePathsEqual } from "../../utils/path"
import { arePathsEqual, getReadablePath } from "../../utils/path"
import { formatResponse } from "../../core/prompts/responses"
import { ClineSayTool } from "../../shared/ExtensionMessage"

export async function listFiles(dirPath: string, recursive: boolean, limit: number): Promise<[string[], boolean]> {
const absolutePath = path.resolve(dirPath)
Expand Down Expand Up @@ -95,3 +97,124 @@
return Array.from(results)
}
}

export class ListFilesTool {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

to remove this class as its redundant

private readonly cwd: string
private readonly relDirPath: string
private readonly recursive: boolean
private readonly providerRef: WeakRef<any>
private readonly rooIgnoreController: any

constructor(
cwd: string,
relDirPath: string,
recursive: boolean,
providerRef: WeakRef<any>,
rooIgnoreController: any,
) {
this.cwd = cwd
this.relDirPath = relDirPath
this.recursive = recursive
this.providerRef = providerRef
this.rooIgnoreController = rooIgnoreController
}

public getSharedMessageProps(): ClineSayTool {
return {
tool: !this.recursive ? "listFilesTopLevel" : "listFilesRecursive",
path: getReadablePath(this.cwd, removeClosingTag("path", this.relDirPath)),
}
}

public async getPartialMessage(): Promise<string> {
return JSON.stringify({
...this.getSharedMessageProps(),
content: "",
} satisfies ClineSayTool)
}

public async execute(): Promise<string> {
if (!this.relDirPath) {
throw new Error("Missing required parameter: path")
}

const absolutePath = path.resolve(this.cwd, this.relDirPath)
const [files, didHitLimit] = await listFiles(absolutePath, this.recursive, 200)
const { showRooIgnoredFiles = true } = (await this.providerRef.deref()?.getState()) ?? {}

return formatResponse.formatFilesList(
absolutePath,
files,
didHitLimit,
this.rooIgnoreController,
showRooIgnoredFiles,
)
}
}
function removeClosingTag(arg0: string, relDirPath: string): string | undefined {
throw new Error("Function not implemented.")
}

export class ListFilesToolHandler {
constructor(
private readonly cline: any, // Type this properly based on your Cline class
private readonly block: {
params: { path?: string; recursive?: string }
partial?: boolean
},
private readonly pushToolResult: (result: string) => void,
) {}

async handle(): Promise<void> {
const relDirPath = this.block.params.path
const recursiveRaw = this.block.params.recursive
const recursive = recursiveRaw?.toLowerCase() === "true"

const listFilesTool = new ListFilesTool(
this.cline.cwd,
relDirPath,

Check failure on line 175 in src/services/glob/list-files.ts

View workflow job for this annotation

GitHub Actions / compile

Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
recursive,
this.cline.providerRef,
this.cline.rooIgnoreController,
)

try {
if (this.block.partial) {
await this.handlePartial(listFilesTool)
return
}

if (!relDirPath) {
this.cline.consecutiveMistakeCount++
this.pushToolResult(await this.cline.sayAndCreateMissingParamError("list_files", "path"))
return
}

await this.handleComplete(listFilesTool)
} catch (error) {
await this.cline.handleError("listing files", error)
}
}

private async handlePartial(listFilesTool: ListFilesTool): Promise<void> {
const partialMessage = await listFilesTool.getPartialMessage()
await this.cline.ask("tool", partialMessage, this.block.partial).catch(() => {})
}

private async handleComplete(listFilesTool: ListFilesTool): Promise<void> {
this.cline.consecutiveMistakeCount = 0
const result = await listFilesTool.execute()

const completeMessage = JSON.stringify({
...listFilesTool.getSharedMessageProps(),
content: result,
} satisfies ClineSayTool)

const didApprove = await this.cline.askApproval("tool", completeMessage)
if (!didApprove) {
return
}

this.pushToolResult(result)
}
}
Loading