perf: optimize environment details by omitting identical records

Added prevEnvDetails property to Task class to store previous environment state.
Modified getEnvironmentDetails to recursively compare current and previous
environment details, returning only the differences.

This optimization reduces the size of environment details in responses by
omitting any records that are identical to the previous state.

Fixes: #5844
Signed-off-by: Eric Wheeler <roo-code@z.ewheeler.org>
This commit is contained in:
Eric Wheeler 2025-07-17 14:58:09 -07:00
parent 426614047d
commit 8734faff54
3 changed files with 57 additions and 3 deletions

View file

@ -30,7 +30,7 @@ export async function getMetadataContext(cline: Task) {
.padStart(2, "0")}:${offsetMinutes.toString().padStart(2, "0")}`
const isoDateWithOffset = now.toISOString().replace(/Z$/, offsetString)
const time = {
"@v": isoDateWithOffset,
"@I": isoDateWithOffset,
}
const { totalCost } = getApiMetrics(cline.clineMessages)

View file

@ -19,7 +19,7 @@ export async function getEnvironmentDetails(task: Task, includeFileDetails: bool
getTodoContext(task),
])
const envDetails = {
const currentEnvDetails = {
...vscodeContext,
...terminalContext,
...fileContext,
@ -28,6 +28,10 @@ export async function getEnvironmentDetails(task: Task, includeFileDetails: bool
...todoContext,
}
const diffEnvDetails = _envDiff(currentEnvDetails, task.prevEnvDetails)
task.prevEnvDetails = currentEnvDetails
const builder = new XMLBuilder({
format: true, // Enable pretty printing
indentBy: " ", // Use two spaces for indentation
@ -38,5 +42,54 @@ export async function getEnvironmentDetails(task: Task, includeFileDetails: bool
textNodeName: "#text",
})
return builder.build({ environment_details: envDetails })
return builder.build({ environment_details: diffEnvDetails })
}
function _envDiff(current: any, previous: any): any {
if (!previous) return current
return Object.keys(current).reduce((acc, key) => {
const currentValue = current[key]
const previousValue = previous ? previous[key] : undefined
if (_objIsEqual(currentValue, previousValue)) {
return acc
}
// Check for nested objects (but not arrays)
if (
typeof currentValue === "object" &&
currentValue !== null &&
!Array.isArray(currentValue) &&
typeof previousValue === "object" &&
previousValue !== null &&
!Array.isArray(previousValue)
) {
const nestedDiff = _envDiff(currentValue, previousValue)
if (Object.keys(nestedDiff).length > 0) {
acc[key] = nestedDiff
}
} else {
// For primitives, arrays, or if previous was not an object
acc[key] = currentValue
}
return acc
}, {} as any)
}
function _objIsEqual(a: any, b: any): boolean {
if (a === b) return true
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false
const keysA = Object.keys(a)
const keysB = Object.keys(b)
if (keysA.length !== keysB.length) return false
for (const key of keysA) {
if (!keysB.includes(key) || !_objIsEqual(a[key], b[key])) return false
}
return true
}

View file

@ -180,6 +180,7 @@ export class Task extends EventEmitter<ClineEvents> {
// LLM Messages & Chat Messages
apiConversationHistory: ApiMessage[] = []
clineMessages: ClineMessage[] = []
prevEnvDetails?: Record<string, any>
// Ask
private askResponse?: ClineAskResponse