mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat(evals): add message log deduper utility (#10286)
This commit is contained in:
parent
44a7ba5072
commit
bd78a63844
3 changed files with 96 additions and 0 deletions
35
packages/evals/src/cli/messageLogDeduper.test.ts
Normal file
35
packages/evals/src/cli/messageLogDeduper.test.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { MessageLogDeduper } from "./messageLogDeduper.js"
|
||||
|
||||
describe("MessageLogDeduper", () => {
|
||||
it("dedupes identical messages for same action+ts", () => {
|
||||
const d = new MessageLogDeduper()
|
||||
const msg = { ts: 123, type: "say", say: "reasoning", text: "hello", partial: false }
|
||||
|
||||
expect(d.shouldLog("updated", msg)).toBe(true)
|
||||
expect(d.shouldLog("updated", msg)).toBe(false)
|
||||
})
|
||||
|
||||
it("logs again if payload changes for same action+ts", () => {
|
||||
const d = new MessageLogDeduper()
|
||||
expect(d.shouldLog("updated", { ts: 123, text: "a" })).toBe(true)
|
||||
expect(d.shouldLog("updated", { ts: 123, text: "b" })).toBe(true)
|
||||
})
|
||||
|
||||
it("does not dedupe across different actions", () => {
|
||||
const d = new MessageLogDeduper()
|
||||
const msg = { ts: 123, text: "same" }
|
||||
expect(d.shouldLog("created", msg)).toBe(true)
|
||||
expect(d.shouldLog("updated", msg)).toBe(true)
|
||||
})
|
||||
|
||||
it("evicts oldest entries", () => {
|
||||
const d = new MessageLogDeduper(2)
|
||||
|
||||
expect(d.shouldLog("updated", { ts: 1, text: "a" })).toBe(true)
|
||||
expect(d.shouldLog("updated", { ts: 2, text: "b" })).toBe(true)
|
||||
// causes eviction of ts:1
|
||||
expect(d.shouldLog("updated", { ts: 3, text: "c" })).toBe(true)
|
||||
// ts:1 was evicted so it should log again
|
||||
expect(d.shouldLog("updated", { ts: 1, text: "a" })).toBe(true)
|
||||
})
|
||||
})
|
||||
50
packages/evals/src/cli/messageLogDeduper.ts
Normal file
50
packages/evals/src/cli/messageLogDeduper.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
export class MessageLogDeduper {
|
||||
private readonly lastLoggedByKey = new Map<string, string>()
|
||||
|
||||
constructor(private readonly maxEntries = 10_000) {}
|
||||
|
||||
/**
|
||||
* Returns true if this message should be logged.
|
||||
* Dedupe key: `${action}:${message.ts}`.
|
||||
* Dedupe rule: skip if payload is identical to the last logged payload for that key.
|
||||
*/
|
||||
public shouldLog(action: string | undefined, message: unknown): boolean {
|
||||
if (!action || !message || typeof message !== "object") {
|
||||
return true
|
||||
}
|
||||
|
||||
const ts = (message as { ts?: unknown }).ts
|
||||
if (typeof ts !== "number") {
|
||||
return true
|
||||
}
|
||||
|
||||
let serialized: string
|
||||
try {
|
||||
serialized = JSON.stringify(message)
|
||||
} catch {
|
||||
// If serialization fails, prefer logging.
|
||||
return true
|
||||
}
|
||||
|
||||
const key = `${action}:${ts}`
|
||||
const prev = this.lastLoggedByKey.get(key)
|
||||
if (prev === serialized) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Refresh insertion order so eviction removes true oldest.
|
||||
if (this.lastLoggedByKey.has(key)) {
|
||||
this.lastLoggedByKey.delete(key)
|
||||
}
|
||||
this.lastLoggedByKey.set(key, serialized)
|
||||
|
||||
if (this.lastLoggedByKey.size > this.maxEntries) {
|
||||
const oldestKey = this.lastLoggedByKey.keys().next().value as string | undefined
|
||||
if (oldestKey) {
|
||||
this.lastLoggedByKey.delete(oldestKey)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ import { EVALS_REPO_PATH } from "../exercises/index.js"
|
|||
import { Logger, getTag, isDockerContainer } from "./utils.js"
|
||||
import { redisClient, getPubSubKey, registerRunner, deregisterRunner } from "./redis.js"
|
||||
import { runUnitTest } from "./runUnitTest.js"
|
||||
import { MessageLogDeduper } from "./messageLogDeduper.js"
|
||||
|
||||
class SubprocessTimeoutError extends Error {
|
||||
constructor(timeout: number) {
|
||||
|
|
@ -305,6 +306,7 @@ export const runTask = async ({ run, task, publish, logger, jobToken }: RunTaskO
|
|||
]
|
||||
|
||||
let isApiUnstable = false
|
||||
const messageLogDeduper = new MessageLogDeduper()
|
||||
|
||||
client.on(IpcMessageType.TaskEvent, async (taskEvent) => {
|
||||
const { eventName, payload } = taskEvent
|
||||
|
|
@ -330,6 +332,15 @@ export const runTask = async ({ run, task, publish, logger, jobToken }: RunTaskO
|
|||
(payload[0].message.say && loggableSays.includes(payload[0].message.say)) ||
|
||||
payload[0].message.partial !== true)
|
||||
) {
|
||||
// Dedupe identical repeated message events (same message.ts + same payload)
|
||||
if (eventName === RooCodeEventName.Message) {
|
||||
const action = payload[0]?.action as string | undefined
|
||||
const message = payload[0]?.message
|
||||
if (!messageLogDeduper.shouldLog(action, message)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Extract tool name for tool-related messages for clearer logging
|
||||
let logEventName: string = eventName
|
||||
if (eventName === RooCodeEventName.Message && payload[0]?.message?.ask === "tool") {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue