Roo-Code/packages/evals/src/cli/redis.ts
roomote[bot] c96b399dd7
feat: add configurable timeout for evals (5-10 min) (#5865)
* feat: add configurable timeout for evals (5-10 min)

- Add timeout field to CreateRun schema with min 5, max 10, default 5
- Add timeout slider UI component to /runs/new page
- Update database schema to include timeout column in runs table
- Create migration to add timeout column with default value of 5
- Update runTask.ts to use configurable timeout from run settings
- Pass timeout parameter through the createRun action

* fix: remove unused EVALS_TIMEOUT import

* fix: add timeout field to createRun calls in copyRun test

- Added timeout: 5 to both createRun calls in copyRun.spec.ts
- This fixes the test failure caused by the new required timeout field in the runs schema
- The timeout field was added in the configurable timeout feature but the test was not updated

* fix: use configurable timeout for Redis key expiration in registerRunner

- Updated registerRunner function to accept timeoutSeconds parameter
- Modified call in runTask.ts to pass configurable timeout instead of hardcoded EVALS_TIMEOUT
- Removed unused EVALS_TIMEOUT import from redis.ts
- Ensures Redis keys remain valid for the entire duration of task execution (up to 10 minutes)

---------

Co-authored-by: Roo Code <roomote@roocode.com>
Co-authored-by: hannesrudolph <hrudolph@gmail.com>
2025-07-19 22:20:23 -04:00

63 lines
1.8 KiB
TypeScript

import { createClient, type RedisClientType } from "redis"
let redis: RedisClientType | undefined
export const redisClient = async () => {
if (!redis) {
redis = createClient({ url: process.env.REDIS_URL || "redis://localhost:6379" })
redis.on("error", (error) => console.error("redis error:", error))
await redis.connect()
}
return redis
}
export const getPubSubKey = (runId: number) => `evals:${runId}`
export const getRunnersKey = (runId: number) => `runners:${runId}`
export const getHeartbeatKey = (runId: number) => `heartbeat:${runId}`
export const registerRunner = async ({
runId,
taskId,
timeoutSeconds,
}: {
runId: number
taskId: number
timeoutSeconds: number
}) => {
const redis = await redisClient()
const runnersKey = getRunnersKey(runId)
await redis.sAdd(runnersKey, `task-${taskId}:${process.env.HOSTNAME ?? process.pid}`)
await redis.expire(runnersKey, timeoutSeconds)
}
export const deregisterRunner = async ({ runId, taskId }: { runId: number; taskId: number }) => {
const redis = await redisClient()
await redis.sRem(getRunnersKey(runId), `task-${taskId}:${process.env.HOSTNAME ?? process.pid}`)
}
export const startHeartbeat = async (runId: number, seconds: number = 10) => {
const pid = process.pid.toString()
const redis = await redisClient()
const heartbeatKey = getHeartbeatKey(runId)
await redis.setEx(heartbeatKey, seconds, pid)
return setInterval(
() =>
redis.expire(heartbeatKey, seconds).catch((error) => {
console.error("heartbeat error:", error)
}),
(seconds * 1_000) / 2,
)
}
export const stopHeartbeat = async (runId: number, heartbeat: NodeJS.Timeout) => {
clearInterval(heartbeat)
try {
const redis = await redisClient()
await redis.del(getHeartbeatKey(runId))
} catch (error) {
console.error("redis.del failed:", error)
}
}