Turn benchmarks into a monorepo

This commit is contained in:
cte 2025-03-19 19:05:03 -07:00
parent 70a5d2b48c
commit 2f6f016f3b
72 changed files with 7634 additions and 2583 deletions

View file

@ -1,2 +0,0 @@
OPENROUTER_API_KEY=sk-or-v1-...
POSTHOG_API_KEY=phc_...

38
benchmark/.gitignore vendored Normal file
View file

@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# Dependencies
node_modules
.pnp
.pnp.js
# Local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Testing
coverage
# Turbo
.turbo
# Vercel
.vercel
# Build Outputs
.next/
out/
build
dist
# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Misc
.DS_Store
*.pem

2
benchmark/.npmrc Normal file
View file

@ -0,0 +1,2 @@
public-hoist-pattern[]=*libsql*
public-hoist-pattern[]=*libsql*

View file

@ -1,5 +1,43 @@
# Benchmark Harness
Install nvm:
```sh
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash
# Reload shell.
nvm install
```
Install pnpm:
```sh
npm install --global corepack@latest
corepack enable pnpm
corepack use pnpm@latest-10
```
Install dependencies:
```sh
pnpm install
```
Configure database:
```sh
cp packages/server/.env.sample packages/server/.env
# Update BENCHMARKS_DB_PATH as needed in `packages/server/.env`.
pnpm --filter @benchmark/server db:migrate
```
Run the benchmark server:
```sh
pnpm dev
```
################################################################################
Configure ENV vars (OpenRouter, PostHog, etc):
```sh

File diff suppressed because it is too large Load diff

View file

@ -1,30 +1,22 @@
{
"name": "benchmark",
"version": "0.1.0",
"private": true,
"main": "out/run.js",
"packageManager": "pnpm@10.6.5",
"scripts": {
"build": "npm run compile && cd .. && npm run compile && npm run build:webview",
"lint": "eslint src --ext ts",
"check-types": "tsc --noEmit",
"compile": "rm -rf out && tsc -p tsconfig.json",
"cli": "npm run compile && npx dotenvx run -f .env.local -- tsx src/cli.ts",
"clean": "rimraf out",
"clean:exercises": "cd exercises && git checkout -f && git clean -fd",
"docker:build": "docker build -f Dockerfile -t roo-code-benchmark ..",
"docker:run": "touch /tmp/benchmarks.db && docker run -d -it -p 3000:3000 -v /tmp/benchmarks.db:/tmp/benchmarks.db roo-code-benchmark",
"docker:start": "npm run docker:build && npm run docker:run",
"docker:shell": "docker exec -it $(docker ps --filter \"ancestor=roo-code-benchmark\" -q) /bin/bash",
"docker:cli": "docker exec -it -w /home/vscode/repo/benchmark $(docker ps --filter \"ancestor=roo-code-benchmark\" -q) xvfb-run npm run cli --",
"docker:stop": "docker stop $(docker ps --filter \"ancestor=roo-code-benchmark\" -q)",
"docker:rm": "docker rm $(docker ps -a --filter \"ancestor=roo-code-benchmark\" -q)",
"docker:clean": "npm run docker:stop && npm run docker:rm"
"lint": "turbo lint",
"check-types": "turbo check-types",
"format": "turbo format",
"dev": "pnpm --filter @benchmark/server dev"
},
"devDependencies": {
"@vscode/test-electron": "^2.4.0",
"gluegun": "^5.1.2",
"@dotenvx/dotenvx": "^1.39.0",
"@eslint/js": "^9.22.0",
"eslint": "^9.22.0",
"globals": "^16.0.0",
"prettier": "^3.5.3",
"rimraf": "^6.0.1",
"tsx": "^4.19.3",
"typescript": "^5.4.5",
"yargs": "^17.7.2"
"turbo": "^2.4.4",
"typescript": "^5",
"typescript-eslint": "^8.26.0"
}
}

View file

@ -0,0 +1,4 @@
import { config } from "@benchmark/eslint-config/base"
/** @type {import("eslint").Linter.Config} */
export default [...config]

View file

@ -0,0 +1,20 @@
{
"name": "@benchmark/cli",
"version": "0.1.0",
"private": true,
"packageManager": "pnpm@10.6.5+sha512.cdf928fca20832cd59ec53826492b7dc25dc524d4370b6b4adbf65803d32efaa6c1c88147c0ae4e8d579a6c9eec715757b50d4fa35eea179d868eada4ed043af",
"scripts": {
"lint": "eslint src --ext ts --max-warnings=0",
"check-types": "tsc --noEmit",
"format": "prettier --write .",
"build": "pnpm --filter @benchmark/client build",
"dev": "dotenvx run -f ../../.env -- tsx src/index.ts"
},
"dependencies": {
"@vscode/test-electron": "^2.4.0",
"gluegun": "^5.1.2"
},
"devDependencies": {
"@benchmark/eslint-config": "workspace:^"
}
}

View file

@ -8,7 +8,7 @@ import { runTests } from "@vscode/test-electron"
// <...>/Roo-Code/benchmark/src
const extensionDevelopmentPath = path.resolve(__dirname, "../../")
const extensionTestsPath = path.resolve(__dirname, "../out/runExercise")
const extensionTestsPath = path.resolve(__dirname, "../out/run.js")
const promptsPath = path.resolve(__dirname, "../prompts")
const exercisesPath = path.resolve(__dirname, "../../../exercises")
const languages = ["cpp", "go", "java", "javascript", "python", "rust"]
@ -125,7 +125,7 @@ async function createRun({ model }: { model: string }): Promise<{ id: number; mo
async function main() {
const cli = build()
.brand("benchmark-runner")
.brand("benchmark-cli")
.src(__dirname)
.help()
.version()

View file

@ -0,0 +1,22 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
"composite": false,
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"inlineSources": false,
"isolatedModules": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"preserveWatchOutput": true,
"skipLibCheck": true,
"strict": true,
"module": "ESNext",
"moduleResolution": "Node",
"target": "es2020"
},
"exclude": ["node_modules"]
}

View file

@ -0,0 +1,4 @@
import { config } from "@benchmark/eslint-config/base"
/** @type {import("eslint").Linter.Config} */
export default [...config]

View file

@ -0,0 +1,18 @@
{
"name": "@benchmark/client",
"version": "0.1.0",
"private": true,
"packageManager": "pnpm@10.6.5",
"scripts": {
"lint": "eslint src --ext ts --max-warnings=0",
"check-types": "tsc --noEmit",
"format": "prettier --write .",
"compile": "pnpm clean && tsc",
"build": "pnpm compile && cd ../../.. && npm run compile && npm run build:webview",
"clean": "rimraf out"
},
"devDependencies": {
"@benchmark/eslint-config": "workspace:^",
"@types/vscode": "^1.98.0"
}
}

View file

@ -0,0 +1,260 @@
import { EventEmitter } from "events"
export interface TokenUsage {
totalTokensIn: number
totalTokensOut: number
totalCacheWrites?: number
totalCacheReads?: number
totalCost: number
contextTokens: number
}
export interface RooCodeEvents {
message: [{ taskId: string; action: "created" | "updated"; message: ClineMessage }]
taskStarted: [taskId: string]
taskPaused: [taskId: string]
taskUnpaused: [taskId: string]
taskAskResponded: [taskId: string]
taskAborted: [taskId: string]
taskSpawned: [taskId: string, childTaskId: string]
taskCompleted: [taskId: string, usage: TokenUsage]
taskTokenUsageUpdated: [taskId: string, usage: TokenUsage]
}
export interface RooCodeAPI extends EventEmitter<RooCodeEvents> {
/**
* Starts a new task with an optional initial message and images.
* @param task Optional initial task message.
* @param images Optional array of image data URIs (e.g., "data:image/webp;base64,...").
* @returns The ID of the new task.
*/
startNewTask(task?: string, images?: string[]): Promise<string>
/**
* Returns the current task stack.
* @returns An array of task IDs.
*/
getCurrentTaskStack(): string[]
/**
* Clears the current task.
*/
clearCurrentTask(lastMessage?: string): Promise<void>
/**
* Cancels the current task.
*/
cancelCurrentTask(): Promise<void>
/**
* Sends a message to the current task.
* @param message Optional message to send.
* @param images Optional array of image data URIs (e.g., "data:image/webp;base64,...").
*/
sendMessage(message?: string, images?: string[]): Promise<void>
/**
* Simulates pressing the primary button in the chat interface.
*/
pressPrimaryButton(): Promise<void>
/**
* Simulates pressing the secondary button in the chat interface.
*/
pressSecondaryButton(): Promise<void>
/**
* Sets the configuration for the current task.
* @param values An object containing key-value pairs to set.
*/
setConfiguration(values: Partial<ConfigurationValues>): Promise<void>
/**
* Returns true if the API is ready to use.
*/
isReady(): boolean
/**
* Returns the messages for a given task.
* @param taskId The ID of the task.
* @returns An array of ClineMessage objects.
*/
getMessages(taskId: string): ClineMessage[]
/**
* Returns the token usage for a given task.
* @param taskId The ID of the task.
* @returns A TokenUsage object.
*/
getTokenUsage(taskId: string): TokenUsage
/**
* Logs a message to the output channel.
* @param message The message to log.
*/
log(message: string): void
}
export type ClineAsk =
| "followup"
| "command"
| "command_output"
| "completion_result"
| "tool"
| "api_req_failed"
| "resume_task"
| "resume_completed_task"
| "mistake_limit_reached"
| "browser_action_launch"
| "use_mcp_server"
| "finishTask"
export type ClineSay =
| "task"
| "error"
| "api_req_started"
| "api_req_finished"
| "api_req_retried"
| "api_req_retry_delayed"
| "api_req_deleted"
| "text"
| "reasoning"
| "completion_result"
| "user_feedback"
| "user_feedback_diff"
| "command_output"
| "tool"
| "shell_integration_warning"
| "browser_action"
| "browser_action_result"
| "command"
| "mcp_server_request_started"
| "mcp_server_response"
| "new_task_started"
| "new_task"
| "checkpoint_saved"
| "rooignore_error"
export interface ClineMessage {
ts: number
type: "ask" | "say"
ask?: ClineAsk
say?: ClineSay
text?: string
images?: string[]
partial?: boolean
reasoning?: string
conversationHistoryIndex?: number
checkpoint?: Record<string, unknown>
progressStatus?: ToolProgressStatus
}
export type SecretKey =
| "apiKey"
| "glamaApiKey"
| "openRouterApiKey"
| "awsAccessKey"
| "awsSecretKey"
| "awsSessionToken"
| "openAiApiKey"
| "geminiApiKey"
| "openAiNativeApiKey"
| "deepSeekApiKey"
| "mistralApiKey"
| "unboundApiKey"
| "requestyApiKey"
export type GlobalStateKey =
| "apiProvider"
| "apiModelId"
| "glamaModelId"
| "glamaModelInfo"
| "awsRegion"
| "awsUseCrossRegionInference"
| "awsProfile"
| "awsUseProfile"
| "awsCustomArn"
| "vertexKeyFile"
| "vertexJsonCredentials"
| "vertexProjectId"
| "vertexRegion"
| "lastShownAnnouncementId"
| "customInstructions"
| "alwaysAllowReadOnly"
| "alwaysAllowWrite"
| "alwaysAllowExecute"
| "alwaysAllowBrowser"
| "alwaysAllowMcp"
| "alwaysAllowModeSwitch"
| "alwaysAllowSubtasks"
| "taskHistory"
| "openAiBaseUrl"
| "openAiModelId"
| "openAiCustomModelInfo"
| "openAiUseAzure"
| "ollamaModelId"
| "ollamaBaseUrl"
| "lmStudioModelId"
| "lmStudioBaseUrl"
| "anthropicBaseUrl"
| "modelMaxThinkingTokens"
| "azureApiVersion"
| "openAiStreamingEnabled"
| "openRouterModelId"
| "openRouterModelInfo"
| "openRouterBaseUrl"
| "openRouterSpecificProvider"
| "openRouterUseMiddleOutTransform"
| "googleGeminiBaseUrl"
| "allowedCommands"
| "ttsEnabled"
| "ttsSpeed"
| "soundEnabled"
| "soundVolume"
| "diffEnabled"
| "enableCheckpoints"
| "checkpointStorage"
| "browserViewportSize"
| "screenshotQuality"
| "remoteBrowserHost"
| "fuzzyMatchThreshold"
| "writeDelayMs"
| "terminalOutputLineLimit"
| "terminalShellIntegrationTimeout"
| "mcpEnabled"
| "enableMcpServerCreation"
| "alwaysApproveResubmit"
| "requestDelaySeconds"
| "rateLimitSeconds"
| "currentApiConfigName"
| "listApiConfigMeta"
| "vsCodeLmModelSelector"
| "mode"
| "modeApiConfigs"
| "customModePrompts"
| "customSupportPrompts"
| "enhancementApiConfigId"
| "experiments" // Map of experiment IDs to their enabled state
| "autoApprovalEnabled"
| "enableCustomModeCreation" // Enable the ability for Roo to create custom modes
| "customModes" // Array of custom modes
| "unboundModelId"
| "requestyModelId"
| "requestyModelInfo"
| "unboundModelInfo"
| "modelTemperature"
| "modelMaxTokens"
| "mistralCodestralUrl"
| "maxOpenTabsContext"
| "maxWorkspaceFiles"
| "browserToolEnabled"
| "lmStudioSpeculativeDecodingEnabled"
| "lmStudioDraftModelId"
| "telemetrySetting"
| "showRooIgnoredFiles"
| "remoteBrowserEnabled"
| "language"
export type ConfigurationKey = GlobalStateKey | SecretKey
export type ConfigurationValues = Record<ConfigurationKey, unknown>

View file

@ -3,7 +3,7 @@ import * as path from "path"
import * as vscode from "vscode"
import { RooCodeAPI, TokenUsage } from "../../src/exports/roo-code"
import { RooCodeAPI, TokenUsage } from "./roo-code"
import { waitUntilReady, waitUntilCompleted, sleep } from "./utils"
@ -83,7 +83,8 @@ export async function run() {
try {
usage = await waitUntilCompleted({ api, taskId, timeout: 5 * 60 * 1_000 }) // 5m
} catch (e) {
} catch (e: unknown) {
console.error(e)
usage = api.getTokenUsage(taskId)
}

View file

@ -1,6 +1,6 @@
import * as vscode from "vscode"
import { RooCodeAPI, TokenUsage } from "../../src/exports/roo-code"
import { RooCodeAPI, TokenUsage } from "./roo-code"
type WaitForOptions = {
timeout?: number

View file

@ -11,6 +11,5 @@
"useUnknownInCatchVariables": false,
"outDir": "out"
},
"include": ["src", "../src/exports/roo-code.d.ts"],
"exclude": ["**/node_modules/**", "out"]
"include": ["src", "../../../../src/exports/roo-code.d.ts"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from "drizzle-kit"
export default defineConfig({
out: "./drizzle",
schema: "./src/schema.ts",
dialect: "sqlite",
dbCredentials: {
url: process.env.BENCHMARKS_DB_PATH!,
},
})

View file

@ -0,0 +1,4 @@
import { config } from "@benchmark/eslint-config/base"
/** @type {import("eslint").Linter.Config} */
export default [...config]

View file

@ -0,0 +1,26 @@
{
"name": "@benchmark/db",
"version": "0.1.0",
"private": true,
"packageManager": "pnpm@10.6.5",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"lint": "eslint src --ext ts --max-warnings=0",
"check-types": "tsc --noEmit",
"format": "prettier --write .",
"db:migrate": "dotenvx run -f ../../.env -- drizzle-kit push"
},
"dependencies": {
"@libsql/client": "^0.14.0",
"drizzle-orm": "^0.40.0",
"drizzle-zod": "^0.7.0",
"libsql": "^0.5.1",
"zod": "^3.24.2"
},
"devDependencies": {
"@benchmark/eslint-config": "workspace:^",
"drizzle-kit": "^0.30.5"
}
}

View file

@ -0,0 +1,5 @@
import { drizzle } from "drizzle-orm/libsql"
export * from "./schema"
export const db = drizzle({ connection: { url: process.env.BENCHMARKS_DB_PATH! } })

View file

@ -0,0 +1,7 @@
export { db } from "./db"
export { type Language, languages } from "./schema"
export { type Run, insertRunSchema, runs } from "./schema"
export { type Task, insertTaskSchema, tasks } from "./schema"
export { getRuns } from "./queries"

View file

@ -0,0 +1,21 @@
import { desc, eq, sql } from "drizzle-orm"
import { db, runs, tasks } from "./db"
export const getRuns = () =>
db
.select({
id: runs.id,
model: runs.model,
description: runs.description,
createdAt: runs.createdAt,
passed: sql<number>`sum(${tasks.passed})`,
failed: sql<number>`sum(${tasks.passed} = 0)`,
total: sql<number>`count(${tasks.id})`,
rate: sql<number>`sum(${tasks.passed}) * 1.0 / count(${tasks.id})`,
cost: sql<number>`sum(${tasks.cost})`,
duration: sql<number>`sum(${tasks.duration})`,
})
.from(runs)
.leftJoin(tasks, eq(runs.id, tasks.runId))
.orderBy(desc(runs.id))

View file

@ -0,0 +1,60 @@
import { sqliteTable, text, real, integer } from "drizzle-orm/sqlite-core"
import * as t from "drizzle-orm/sqlite-core"
import { createInsertSchema } from "drizzle-zod"
/**
* languages
*/
export const languages = ["cpp", "go", "java", "javascript", "python", "rust"] as const
export type Language = (typeof languages)[number]
/**
* runs
*/
export const runs = sqliteTable("runs", {
id: integer({ mode: "number" }).primaryKey({ autoIncrement: true }),
model: text().notNull(),
description: text(),
createdAt: integer({ mode: "timestamp" }).notNull(),
})
export type Run = typeof runs.$inferSelect
export const insertRunSchema = createInsertSchema(runs).omit({
id: true,
createdAt: true,
})
/**
* tasks
*/
export const tasks = sqliteTable(
"tasks",
{
id: integer({ mode: "number" }).primaryKey({ autoIncrement: true }),
runId: integer({ mode: "number" }).notNull(),
language: text({ enum: languages }).notNull(),
exercise: text().notNull(),
tokensIn: integer({ mode: "number" }).notNull(),
tokensOut: integer({ mode: "number" }).notNull(),
tokensContext: integer({ mode: "number" }).notNull(),
cacheWrites: integer({ mode: "number" }).notNull(),
cacheReads: integer({ mode: "number" }).notNull(),
cost: real().notNull(),
duration: integer({ mode: "number" }).notNull(),
passed: integer({ mode: "boolean" }).notNull(),
createdAt: integer({ mode: "timestamp" }).notNull(),
},
(table) => [t.uniqueIndex("language_exercise_idx").on(table.runId, table.language, table.exercise)],
)
export type Task = typeof tasks.$inferSelect
export const insertTaskSchema = createInsertSchema(tasks).omit({
id: true,
createdAt: true,
})

View file

@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
"composite": false,
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"inlineSources": false,
"isolatedModules": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"preserveWatchOutput": true,
"skipLibCheck": true,
"strict": true,
"module": "ESNext",
"moduleResolution": "Node",
"target": "es2020",
"types": ["node"]
},
"exclude": ["node_modules"]
}

View file

@ -79,7 +79,7 @@ RUN /home/vscode/.local/bin/uv sync
# Build web-ui
WORKDIR /home/vscode/exercises/web-ui
RUN echo "DB_FILE_NAME=file:/tmp/benchmarks.db" > .env
RUN echo "BENCHMARKS_DB_PATH=file:/tmp/benchmarks.db" > .env
RUN pnpm install
RUN npx drizzle-kit push

View file

@ -0,0 +1,17 @@
{
"name": "@benchmark/docker",
"version": "0.1.0",
"private": true,
"scripts": {
"docker:build": "docker build -f Dockerfile -t roo-code-benchmark ..",
"docker:run": "touch /tmp/benchmarks.db && docker run -d -it -p 3000:3000 -v /tmp/benchmarks.db:/tmp/benchmarks.db roo-code-benchmark",
"docker:start": "npm run docker:build && npm run docker:run",
"docker:shell": "docker exec -it $(docker ps --filter \"ancestor=roo-code-benchmark\" -q) /bin/bash",
"docker:cli": "docker exec -it -w /home/vscode/repo/benchmark $(docker ps --filter \"ancestor=roo-code-benchmark\" -q) xvfb-run npm run cli --",
"docker:stop": "docker stop $(docker ps --filter \"ancestor=roo-code-benchmark\" -q)",
"docker:rm": "docker rm $(docker ps -a --filter \"ancestor=roo-code-benchmark\" -q)",
"docker:clean": "npm run docker:stop && npm run docker:rm"
},
"devDependencies": {},
"dependencies": {}
}

View file

@ -0,0 +1,32 @@
import js from "@eslint/js"
import eslintConfigPrettier from "eslint-config-prettier"
import turboPlugin from "eslint-plugin-turbo"
import tseslint from "typescript-eslint"
import onlyWarn from "eslint-plugin-only-warn"
/**
* A shared ESLint configuration for the repository.
*
* @type {import("eslint").Linter.Config[]}
* */
export const config = [
js.configs.recommended,
eslintConfigPrettier,
...tseslint.configs.recommended,
{
plugins: {
turbo: turboPlugin,
},
rules: {
"turbo/no-undeclared-env-vars": "warn",
},
},
{
plugins: {
onlyWarn,
},
},
{
ignores: ["dist/**"],
},
]

View file

@ -0,0 +1,49 @@
import js from "@eslint/js"
import eslintConfigPrettier from "eslint-config-prettier"
import tseslint from "typescript-eslint"
import pluginReactHooks from "eslint-plugin-react-hooks"
import pluginReact from "eslint-plugin-react"
import globals from "globals"
import pluginNext from "@next/eslint-plugin-next"
import { config as baseConfig } from "./base.js"
/**
* A custom ESLint configuration for libraries that use Next.js.
*
* @type {import("eslint").Linter.Config[]}
* */
export const nextJsConfig = [
...baseConfig,
js.configs.recommended,
eslintConfigPrettier,
...tseslint.configs.recommended,
{
...pluginReact.configs.flat.recommended,
languageOptions: {
...pluginReact.configs.flat.recommended.languageOptions,
globals: {
...globals.serviceworker,
},
},
},
{
plugins: {
"@next/next": pluginNext,
},
rules: {
...pluginNext.configs.recommended.rules,
...pluginNext.configs["core-web-vitals"].rules,
},
},
{
plugins: {
"react-hooks": pluginReactHooks,
},
settings: { react: { version: "detect" } },
rules: {
...pluginReactHooks.configs.recommended.rules,
// React scope no longer necessary with new JSX transform.
"react/react-in-jsx-scope": "off",
},
},
]

View file

@ -0,0 +1,24 @@
{
"name": "@benchmark/eslint-config",
"version": "0.0.0",
"type": "module",
"private": true,
"packageManager": "pnpm@10.6.5",
"exports": {
"./base": "./base.js",
"./next-js": "./next.js"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
"@next/eslint-plugin-next": "^15.2.1",
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-only-warn": "^1.1.0",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-turbo": "^2.4.4",
"globals": "^16.0.0",
"typescript": "^5",
"typescript-eslint": "^8.26.0"
}
}

View file

@ -0,0 +1,4 @@
import { config } from "@benchmark/eslint-config/base"
/** @type {import("eslint").Linter.Config} */
export default [...config]

View file

@ -0,0 +1,21 @@
{
"name": "@benchmark/ipc",
"version": "0.1.0",
"private": true,
"packageManager": "pnpm@10.6.5",
"scripts": {
"lint": "eslint src --ext ts --max-warnings=0",
"check-types": "tsc --noEmit",
"format": "prettier --write .",
"test:server": "tsx scripts/server.ts",
"test:client": "tsx scripts/client.ts"
},
"dependencies": {
"node-ipc": "^12.0.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@benchmark/eslint-config": "workspace:^",
"@types/node-ipc": "^9.2.3"
}
}

View file

@ -0,0 +1,34 @@
import { IpcClient } from "../src/ipcClient"
async function main(socketPath: string) {
try {
const startTime = Date.now()
const client = new IpcClient(socketPath)
client.connect()
while (!client.isConnected) {
if (Date.now() - startTime > 5000) {
throw new Error("Failed to connect to server.")
}
await new Promise((resolve) => setTimeout(resolve, 1000))
}
while (client.isConnected) {
client.ping()
await new Promise((resolve) => setTimeout(resolve, 5000))
}
process.exit(0)
} catch (e) {
console.error(e)
process.exit(1)
}
}
if (!process.argv[2]) {
console.error("Usage: npx tsx scripts/client.ts <socketPath>")
process.exit(1)
}
main(process.argv[2])

View file

@ -0,0 +1,19 @@
import { IpcServer } from "../src/ipcServer"
async function main() {
try {
const server = new IpcServer()
server.listen()
while (server.isListening) {
await new Promise((resolve) => setTimeout(resolve, 1000))
}
process.exit(0)
} catch (e) {
console.error(e)
process.exit(1)
}
}
main()

View file

@ -0,0 +1,88 @@
import ipc from "node-ipc"
import { ClientMessage, ClientMessageType, ServerMessageType, serverMessageSchema } from "./schemas"
export class IpcClient {
private readonly _socketPath: string
private _isConnected = false
private _clientId?: string
constructor(socketPath: string) {
this._socketPath = socketPath
}
connect() {
ipc.config.silent = true
ipc.connectTo("benchmarkServer", this.socketPath, () => {
ipc.of.benchmarkServer.on("connect", (args) => this.onConnect(args))
ipc.of.benchmarkServer.on("message", (data) => this.onMessage(data))
ipc.of.benchmarkServer.on("disconnect", (args) => this.onDisconnect(args))
})
}
private onConnect(args: unknown) {
console.log("[client#onConnect]", args)
this._isConnected = true
}
private onMessage(data: unknown) {
if (typeof data !== "object") {
console.log("[client#onMessage] invalid data", data)
return
}
const result = serverMessageSchema.safeParse(data)
if (!result.success) {
console.log("[client#onMessage] invalid payload", result.error)
return
}
const payload = result.data
switch (payload.type) {
case ServerMessageType.Hello:
console.log(`[client#Hello] ${payload.data.clientId}`)
this._clientId = payload.data.clientId
break
case ServerMessageType.Pong:
console.log(`[client#Pong]`)
break
}
}
private onDisconnect(args: unknown) {
console.log("[client#onDisconnect]", args)
this._isConnected = false
}
public sendMessage(message: ClientMessage) {
ipc.of.benchmarkServer.emit("message", message)
}
public ping() {
if (!this.isReady) {
return false
}
this.sendMessage({ type: ClientMessageType.Ping, data: { clientId: this._clientId! } })
return true
}
public get socketPath() {
return this._socketPath
}
public get clientId() {
return this._clientId
}
public get isConnected() {
return this._isConnected
}
public get isReady() {
return this._isConnected && this._clientId !== undefined
}
}

View file

@ -0,0 +1,82 @@
import { Socket } from "node:net"
import ipc from "node-ipc"
import * as os from "node:os"
import * as path from "node:path"
import * as crypto from "node:crypto"
import { ClientMessageType, ServerMessage, ServerMessageType, clientMessageSchema } from "./schemas"
export class IpcServer {
private _isListening = false
private _socketId: string
private _clients: Map<string, Socket>
constructor() {
this._socketId = "benchmark"
this._clients = new Map()
}
public listen() {
this._isListening = true
ipc.config.id = this._socketId
ipc.config.silent = true
ipc.serve(this.socketPath, () => {
ipc.server.on("connect", (socket) => this.onConnect(socket))
ipc.server.on("message", (data, socket) => this.onMessage(data, socket))
ipc.server.on("socket.disconnected", (socket, id) => this.onDisconnect(socket, id))
})
ipc.server.start()
}
private onConnect(socket: Socket) {
const clientId = crypto.randomBytes(6).toString("hex")
console.log(`[server#onConnect]`, clientId)
this._clients.set(clientId, socket)
this.sendMessage(socket, { type: ServerMessageType.Hello, data: { clientId } })
}
private onMessage(data: unknown, socket: Socket) {
if (typeof data !== "object") {
console.log("[server#onMessage] invalid data", data)
return
}
const result = clientMessageSchema.safeParse(data)
if (!result.success) {
console.log("[server#onMessage] invalid payload", result.error)
return
}
const payload = result.data
switch (payload.type) {
case ClientMessageType.Message:
console.log(`[server#Message] ${payload.data.message}`)
break
case ClientMessageType.Ping:
console.log(`[server#Ping]`)
this.sendMessage(socket, { type: ServerMessageType.Pong })
break
}
}
private onDisconnect(socket: Socket, destroyedSocketID: string) {
console.log(`[server#socket.disconnected] ${destroyedSocketID}`)
}
public sendMessage(socket: Socket, message: ServerMessage) {
ipc.server.emit(socket, "message", message)
}
public get socketPath() {
return path.join(os.tmpdir(), `${this._socketId}.sock`)
}
public get isListening() {
return this._isListening
}
}

View file

@ -0,0 +1,51 @@
import { z } from "zod"
/**
* Client
*/
export enum ClientMessageType {
Message = "message",
Ping = "ping",
}
export const clientMessageSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal(ClientMessageType.Message),
data: z.object({
clientId: z.string(),
message: z.string(),
}),
}),
z.object({
type: z.literal(ClientMessageType.Ping),
data: z.object({
clientId: z.string(),
}),
}),
])
export type ClientMessage = z.infer<typeof clientMessageSchema>
/**
* Server
*/
export enum ServerMessageType {
Hello = "hello",
Pong = "pong",
}
export const serverMessageSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal(ServerMessageType.Hello),
data: z.object({
clientId: z.string(),
}),
}),
z.object({
type: z.literal(ServerMessageType.Pong),
}),
])
export type ServerMessage = z.infer<typeof serverMessageSchema>

View file

@ -0,0 +1,22 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
"composite": false,
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"inlineSources": false,
"isolatedModules": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"preserveWatchOutput": true,
"skipLibCheck": true,
"strict": true,
"module": "ESNext",
"moduleResolution": "Node",
"target": "es2020"
},
"exclude": ["node_modules"]
}

45
benchmark/packages/server/.gitignore vendored Normal file
View file

@ -0,0 +1,45 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
!.env.sample
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# sqlite
local.db

View file

@ -0,0 +1,56 @@
# Roo Code Benchmark Web UI
## Getting Started
Install dependencies:
```sh
pnpm install
```
Create your SQLite database:
```sh
cp .env.sample .env
# Update path to SQLite database as needed in `.env`.
npx drizzle-kit push
```
Start the app:
```sh
pnpm dev
```
## API
```sh
curl -f -X POST http://localhost:3000/api/runs \
-H "Content-Type: application/json" \
-d '{"model": "Claude 3.7 Sonnet"}'
curl -f -X POST http://localhost:3000/api/tasks \
-H "Content-Type: application/json" \
-d '{
"runId": 1,
"language": "javascript",
"exercise": "binary",
"tokensIn": 1000000,
"tokensOut": 50000,
"tokensContext": 50,
"cacheWrites": 5,
"cacheReads": 10,
"cost": 0.543,
"duration": 150000,
"passed": true
}'
```
## Recipes
Zero-out and re-create database file:
```sh
truncate -s 0 /tmp/benchmarks.db
npx drizzle-kit push
```

View file

@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

View file

@ -0,0 +1,4 @@
import { nextJsConfig } from "@benchmark/eslint-config/next-js"
/** @type {import("eslint").Linter.Config} */
export default [...nextJsConfig]

View file

@ -0,0 +1,7 @@
import type { NextConfig } from "next"
const nextConfig: NextConfig = {
/* config options here */
}
export default nextConfig

View file

@ -0,0 +1,35 @@
{
"name": "@benchmark/server",
"version": "0.1.0",
"private": true,
"packageManager": "pnpm@10.6.5",
"scripts": {
"dev": "dotenvx run -f ../../.env -- next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint",
"check-types": "tsc -b",
"format": "prettier --write ."
},
"dependencies": {
"@benchmark/db": "workspace:^",
"@radix-ui/react-slot": "^1.1.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.479.0",
"next": "15.2.2",
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwind-merge": "^3.0.2",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2"
},
"devDependencies": {
"@benchmark/eslint-config": "workspace:^",
"@tailwindcss/postcss": "^4",
"@types/react": "^19",
"@types/react-dom": "^19",
"tailwindcss": "^4"
}
}

View file

@ -0,0 +1,5 @@
const config = {
plugins: ["@tailwindcss/postcss"],
}
export default config

View file

@ -0,0 +1,19 @@
import { NextResponse } from "next/server"
import { db, runs, insertRunSchema } from "@benchmark/db"
export async function POST(request: Request) {
try {
const run = await db
.insert(runs)
.values({
...insertRunSchema.parse(await request.json()),
createdAt: new Date(),
})
.returning()
return NextResponse.json({ run }, { status: 201 })
} catch (error) {
return NextResponse.json({ error: (error as Error).message }, { status: 500 })
}
}

View file

@ -0,0 +1,19 @@
import { NextResponse } from "next/server"
import { db, tasks, insertTaskSchema } from "@benchmark/db"
export async function POST(request: Request) {
try {
const task = await db
.insert(tasks)
.values({
...insertTaskSchema.parse(await request.json()),
createdAt: new Date(),
})
.returning()
return NextResponse.json({ task }, { status: 201 })
} catch (error) {
return NextResponse.json({ error: (error as Error).message }, { status: 500 })
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,123 @@
@import "tailwindcss";
@plugin "tailwindcss-animate";
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

View file

@ -0,0 +1,37 @@
import type { Metadata } from "next"
import { Geist, Geist_Mono } from "next/font/google"
import { ThemeProvider } from "@/components/theme-provider"
import "./globals.css"
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
})
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
})
export const metadata: Metadata = {
title: "Roo Code Benchmarks",
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
{children}
</ThemeProvider>
</body>
</html>
)
}

View file

@ -0,0 +1,43 @@
import { getRuns } from "@benchmark/db"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"
import { formatCurrency, formatDuration } from "@/lib"
export const dynamic = "force-dynamic"
export default async function Home() {
const runs = await getRuns()
return (
<div className="mx-auto my-20 w-3xl">
<Table className="border">
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Model</TableHead>
<TableHead>Timestamp</TableHead>
<TableHead>Passed</TableHead>
<TableHead>Failed</TableHead>
<TableHead>% Correct</TableHead>
<TableHead>Cost</TableHead>
<TableHead>Duration</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{runs.map((run) => (
<TableRow key={run.id}>
<TableCell>{run.id}</TableCell>
<TableCell>{run.model}</TableCell>
<TableCell>{new Date(run.createdAt).toLocaleString()}</TableCell>
<TableCell>{run.passed}</TableCell>
<TableCell>{run.failed}</TableCell>
<TableCell>{(run.rate * 100).toFixed(1)}%</TableCell>
<TableCell>{formatCurrency(run.cost)}</TableCell>
<TableCell>{formatDuration(run.duration)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)
}

View file

@ -0,0 +1,13 @@
"use client"
import * as React from "react"
import { type ThemeProviderProps } from "next-themes"
import dynamic from "next/dynamic"
const NextThemesProvider = dynamic(() => import("next-themes").then((e) => e.ThemeProvider), {
ssr: false,
})
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

View file

@ -0,0 +1,50 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
)
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
return <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} />
}
export { Button, buttonVariants }

View file

@ -0,0 +1,2 @@
export * from "./button"
export * from "./table"

View file

@ -0,0 +1,75 @@
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
function Table({ className, ...props }: React.ComponentProps<"table">) {
return (
<div data-slot="table-container" className="relative w-full overflow-x-auto">
<table data-slot="table" className={cn("w-full caption-bottom text-sm", className)} {...props} />
</div>
)
}
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
return <thead data-slot="table-header" className={cn("[&_tr]:border-b", className)} {...props} />
}
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
return <tbody data-slot="table-body" className={cn("[&_tr:last-child]:border-0", className)} {...props} />
}
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
return (
<tfoot
data-slot="table-footer"
className={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)}
{...props}
/>
)
}
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
return (
<tr
data-slot="table-row"
className={cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", className)}
{...props}
/>
)
}
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
return (
<th
data-slot="table-head"
className={cn(
"text-muted-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className,
)}
{...props}
/>
)
}
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
return (
<td
data-slot="table-cell"
className={cn(
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className,
)}
{...props}
/>
)
}
function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
return (
<caption data-slot="table-caption" className={cn("text-muted-foreground mt-4 text-sm", className)} {...props} />
)
}
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }

View file

@ -0,0 +1,6 @@
const formatter = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
})
export const formatCurrency = (amount: number) => formatter.format(amount)

View file

@ -0,0 +1,22 @@
export const formatDuration = (durationMs: number) => {
const seconds = Math.floor(durationMs / 1000)
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const remainingSeconds = seconds % 60
const parts = []
if (hours > 0) {
parts.push(`${hours}h`)
}
if (minutes > 0) {
parts.push(`${minutes}m`)
}
if (remainingSeconds > 0 || parts.length === 0) {
parts.push(`${remainingSeconds}s`)
}
return parts.join(" ")
}

View file

@ -0,0 +1,2 @@
export { formatCurrency } from "./formatCurrency"
export { formatDuration } from "./formatDuration"

View file

@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View file

@ -0,0 +1,35 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"../db/src/index.ts",
"../db/src/queries.ts",
"../db/src/schema.ts"
],
"exclude": ["node_modules"]
}

5952
benchmark/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,2 @@
packages:
- 'packages/*'

View file

@ -1,17 +0,0 @@
Your job is to complete a coding exercise described by `.docs/instructions.md`.
A file with the implementation stubbed out has been created for you, along with a test file.
To successfully complete the exercise, you must pass all the tests in the test file.
To confirm that your solution is correct, you can compile your code and run the tests with:
```
mkdir -p build && cd build
cmake -G "Unix Makefiles" -DEXERCISM_RUN_ALL_TESTS=1 ..
make
```
Note that running `make` will compile the tests and generate compile time errors. Once the errors are fixed, running `make` will build and run the tests.
Do not alter the test file; it should be run as-is.

View file

@ -1,7 +0,0 @@
Your job is to complete a coding exercise described by `.docs/instructions.md`.
A file with the implementation stubbed out has been created for you, along with a test file.
To successfully complete the exercise, you must pass all the tests in the test file.
To confirm that your solution is correct, run the tests with `go test`. Do not alter the test file; it should be run as-is.

View file

@ -1,7 +0,0 @@
Your job is to complete a coding exercise described by `.docs/instructions.md`.
A file with the implementation stubbed out has been created for you, along with a test file.
To successfully complete the exercise, you must pass all the tests in the test file.
To confirm that your solution is correct, run the tests with `./gradlew test`. Do not alter the test file; it should be run as-is.

View file

@ -1,9 +0,0 @@
Your job is to complete a coding exercise described by `.docs/instructions.md`.
A file with the implementation stubbed out has been created for you, along with a test file.
To successfully complete the exercise, you must pass all the tests in the test file.
To confirm that your solution is correct, run the tests with `pnpm test`. Do not alter the test file; it should be run as-is.
Before running the tests make sure your environment is set up by running `pnpm install` to install the dependencies.

View file

@ -1,7 +0,0 @@
Your job is to complete a coding exercise described by `.docs/instructions.md`.
A file with the implementation stubbed out has been created for you, along with a test file.
To successfully complete the exercise, you must pass all the tests in the test file.
To confirm that your solution is correct, run the tests with `uv run python3 -m pytest -o markers=task [name]_test.py`. Do not alter the test file; it should be run as-is.

View file

@ -1,7 +0,0 @@
Your job is to complete a coding exercise described by `.docs/instructions.md`.
A file with the implementation stubbed out has been created for you, along with a test file.
To successfully complete the exercise, you must pass all the tests in the test file.
To confirm that your solution is correct, run the tests with `cargo test`. Do not alter the test file; it should be run as-is.

28
benchmark/turbo.json Normal file
View file

@ -0,0 +1,28 @@
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"globalEnv": [
"NODE_ENV",
"NEXT_RUNTIME",
"RUN_ID",
"OPENROUTER_API_KEY",
"OPENROUTER_MODEL_ID",
"PROMPT_PATH",
"WORKSPACE_PATH",
"BENCHMARKS_DB_PATH"
],
"tasks": {
"format": {},
"lint": {},
"check-types": {},
"test": {},
"dev": {
"cache": false,
"persistent": true
},
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
}
}
}

View file

@ -284,17 +284,14 @@
"install-extension": "npm install",
"install-webview": "cd webview-ui && npm install",
"install-e2e": "cd e2e && npm install",
"install-benchmark": "cd benchmark && npm install",
"lint": "npm-run-all -p lint:*",
"lint:extension": "eslint src --ext ts",
"lint:webview": "cd webview-ui && npm run lint",
"lint:e2e": "cd e2e && npm run lint",
"lint:benchmark": "cd benchmark && npm run lint",
"check-types": "npm-run-all -p check-types:*",
"check-types:extension": "tsc --noEmit",
"check-types:webview": "cd webview-ui && npm run check-types",
"check-types:e2e": "cd e2e && npm run check-types",
"check-types:benchmark": "cd benchmark && npm run check-types",
"package": "npm-run-all -p build:webview build:esbuild check-types lint",
"pretest": "npm run compile",
"dev": "cd webview-ui && npm run dev",
@ -317,7 +314,6 @@
"clean:extension": "rimraf bin dist out",
"clean:webview": "cd webview-ui && npm run clean",
"clean:e2e": "cd e2e && npm run clean",
"clean:benchmark": "cd benchmark && npm run clean",
"update-contributors": "node scripts/update-contributors.js"
},
"dependencies": {