* Will look through the ANUSent history and determine when the most recent * edit to a target file occurred. If no edit happened, it will return -1 * @param filePath the path to the file * @param client the anusClient, so that we can get the history * @returns a DateTime (as a number) of when th
( filePath: string, client: AnusClient, )
| 79 | * @returns a DateTime (as a number) of when the last edit occurred, or -1 if no edit was found. |
| 80 | */ |
| 81 | async function findLastEditTimestamp( |
| 82 | filePath: string, |
| 83 | client: AnusClient, |
| 84 | ): Promise<number> { |
| 85 | const history = (await client.getHistory()) ?? []; |
| 86 | |
| 87 | // Tools that may reference the file path in their FunctionResponse `output`. |
| 88 | const toolsInResp = new Set([ |
| 89 | WriteFileTool.Name, |
| 90 | EditTool.Name, |
| 91 | ReadManyFilesTool.Name, |
| 92 | GrepTool.Name, |
| 93 | ]); |
| 94 | // Tools that may reference the file path in their FunctionCall `args`. |
| 95 | const toolsInCall = new Set([...toolsInResp, ReadFileTool.Name]); |
| 96 | |
| 97 | // Iterate backwards to find the most recent relevant action. |
| 98 | for (const entry of history.slice().reverse()) { |
| 99 | if (!entry.parts) continue; |
| 100 | |
| 101 | for (const part of entry.parts) { |
| 102 | let id: string | undefined; |
| 103 | let content: unknown; |
| 104 | |
| 105 | // Check for a relevant FunctionCall with the file path in its arguments. |
| 106 | if ( |
| 107 | isFunctionCall(entry) && |
| 108 | part.functionCall?.name && |
| 109 | toolsInCall.has(part.functionCall.name) |
| 110 | ) { |
| 111 | id = part.functionCall.id; |
| 112 | content = part.functionCall.args; |
| 113 | } |
| 114 | // Check for a relevant FunctionResponse with the file path in its output. |
| 115 | else if ( |
| 116 | isFunctionResponse(entry) && |
| 117 | part.functionResponse?.name && |
| 118 | toolsInResp.has(part.functionResponse.name) |
| 119 | ) { |
| 120 | const { response } = part.functionResponse; |
| 121 | if (response && !('error' in response) && 'output' in response) { |
| 122 | id = part.functionResponse.id; |
| 123 | content = response['output']; |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | if (!id || content === undefined) continue; |
| 128 | |
| 129 | // Use the "blunt hammer" approach to find the file path in the content. |
| 130 | // Note that the tool response data is inconsistent in their formatting |
| 131 | // with successes and errors - so, we just check for the existence |
| 132 | // as the best guess to if error/failed occurred with the response. |
| 133 | const stringified = JSON.stringify(content); |
| 134 | if ( |
| 135 | !stringified.includes('Error') && // only applicable for functionResponse |
| 136 | !stringified.includes('Failed') && // only applicable for functionResponse |
| 137 | stringified.includes(filePath) |
| 138 | ) { |
no test coverage detected