mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 16:02:19 +00:00
4296a6ae1f
* docs: add global AI chat context-optimization plan for raw apps Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(ai-evals): add global raw-app debugging cases on a large fixture Adds a ~20-file analytics_dashboard raw-app fixture (incl. a 5k-line data module and a planted wrong-totals bug), two global cases (read-heavy debug + small-edit baseline), app-seed support in the mock backend, directory-fixture loading, and a decorateHelpers seam so read-dedupe is measurable. Records tokenUsage for before/ after comparison of the read-tool optimization. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ai-chat): cap and dedupe read_app_file to bound context in large apps read_app_file now defaults to a head slice (1500 lines / 50k chars) with offset/ limit to page further, and skips resending a file whose earlier read is still in context (per-conversation ledger keyed off the originating tool-call id, so it self-heals after compaction). Bounds the file-content portion of global-chat context when working in large raw apps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(ai-evals): add read-heavy raw-app debug case (large data module) global-test31 induces the model to inspect the 5k-line seedData module, exercising the read_app_file cap/offset path. Baseline ~262k tokens vs ~200k with the cap+dedupe change (-24%). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: record A+B benchmark results and fixed-overhead finding Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ai-chat): clearer read_app_file past-EOF message + unit tests for cap/dedupe Addresses local-review nits: out-of-range offset now reports 'offset N is past the end of the file' instead of a backwards 'lines 11-10' label; adds unit coverage for the slicing (line cap, offset/limit window, char budget, past-EOF) and re-read dedupe (hit + miss-when-not-retained). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ai-chat): char-level paging + per-range dedupe for read_app_file Adds char_offset/char_limit so minified/long-line files can be paged within a line window, keys the re-read ledger by range (so reading different ranges no longer collides), and dedupes on the full-file hash (a cached range stub is invalidated when any byte of the file changes, not just the returned range). Tests updated for the char-slice behavior plus single-line capping, char paging, and out-of-window change detection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(ai-chat): add read_app_file context micro-benchmark + re-read eval case Adds a deterministic micro-benchmark (no LLM) that drives read_app_file through a realistic big-project read pattern (large file, re-read, minified bundle, paging) and asserts the cap+dedupe cut returned context >50% vs the old whole-file behavior — isolating the feature's effect from model nondeterminism and guarding against silent weakening. Adds global-test32, a cross-file consistency investigation that revisits overlapping files so re-read dedupe is exercised in a real run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(ai-evals): clarify test32 measures the read cap, not dedupe Verified: sonnet and haiku both read each file once per conversation and retain it, so test32 never triggers read_app_file re-read dedupe. Dedupe is measured deterministically by the micro-benchmark instead. Comment corrected to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(ai-chat): drop read_app_file re-read dedupe, ship the cap only Benchmarking showed the per-conversation re-read dedupe never fires in practice: across sonnet/opus/gpt-5.5/haiku, every model reads each file once per conversation and keeps it in context (0 within-conversation re-reads). It was a correct but unused guard, so this removes the ledger, full-file hash, retention predicate, the AIChatManager wiring, and the eval decorateHelpers seam — keeping the read cap + offset/limit/char paging (A), which is the lever that actually bounds context. The micro-benchmark is now cap-only; test32 is kept as a multi-file read-load case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ai-chat): add search_app grep tool for global raw-app chat (experimental) Client-side grep over a raw app's frontend files and inline runnables (literal, case-insensitive, optional file_glob/context_lines/max_matches, head-capped). Completes the list -> search -> ranged-read triad. Includes the eval A/B gate (WMILL_AI_EVAL_DISABLE_SEARCH_APP), unit tests + micro-benchmark, and a find-all-usages eval case (global-test33). Experimental: A/B benchmarking shows it is not an unconditional win — it helps on find-all-usages but adds agentic iterations on navigable apps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(ai-evals): accept search_app as a valid file-inspection tool in raw-app cases Add requiredToolsAnyOf alternatives-group to ToolValidationSpec and switch global-test29..32 to it so a model that locates files via search_app instead of read_app_file no longer false-fails the tool assertion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: remove stale ai-chat context-optimization planning doc Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(ai-chat): drop read_app_file char paging for a hard char cap The char_offset/char_limit params guarded minified files (a single line over the char budget) but were effectively unused in benchmarks. Remove them and the in-window char paging; keep the hard 50k-char budget and, when a read hits it, tell the model to narrow the line limit (or treat the file as unreadable if a single line exceeds the budget). Proper long-line handling is left as a TODO. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(ai-chat): bake search_app context to 1 line, clarify query is literal Drop the context_lines param (models varied it to little effect) for a fixed SEARCH_APP_CONTEXT_LINES=1, and cap on matching lines instead of pushed rows so max_matches stays accurate with context always on. Sharpen the query description to state it is a literal (non-regex) substring and to suggest the call form (e.g. formatCurrency() to hit call sites and skip formatCurrencyPrecise. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(ai-chat): widen baked search_app context to 2 lines Models that set the old context_lines param leaned to 2; match the lean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ai-chat): count every file with a match in search_app header Move fileHadMatch ahead of the render cap so files whose matches fall past max_matches are still counted (with a regression test). Also swap the raw NUL globstar sentinel for a printable escape (the NUL bytes made core.ts read as binary to grep) and reword two comments to describe current constraints instead of drafting history. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ai-chat): drop redundant input echoes from app tool results read_app_file and search_app no longer prefix results with the tool name or echo back the caller's own inputs (file path, query, file_glob) — the model already has them from the call args, and the unbounded query echo could push the search result past its output budget. Keeps the useful signals (line range, match/file counts, truncation) and the actionable advice. Also reword max_matches to 'matching lines' since it caps lines (each expands to context rows). Unit tests updated to the new format. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
472 lines
19 KiB
TypeScript
472 lines
19 KiB
TypeScript
import { expect, it, vi } from 'vitest'
|
|
// @ts-ignore - Node.js fs/promises
|
|
import { mkdir, writeFile } from 'fs/promises'
|
|
// @ts-ignore - Node.js path
|
|
import { dirname, resolve } from 'path'
|
|
|
|
vi.mock('monaco-editor', () => ({
|
|
editor: {},
|
|
languages: {},
|
|
KeyCode: {},
|
|
Uri: {
|
|
parse: (value: string) => ({ toString: () => value })
|
|
},
|
|
MarkerSeverity: {
|
|
Error: 8,
|
|
Warning: 4,
|
|
Info: 2,
|
|
Hint: 1
|
|
}
|
|
}))
|
|
|
|
vi.mock('@codingame/monaco-vscode-standalone-typescript-language-features', () => ({
|
|
getTypeScriptWorker: async () => async () => ({}),
|
|
typescriptVersion: 'test'
|
|
}))
|
|
|
|
vi.mock('@codingame/monaco-vscode-languages-service-override', () => ({
|
|
default: () => ({})
|
|
}))
|
|
|
|
vi.mock('$lib/components/vscode', () => ({}))
|
|
|
|
vi.mock('$lib/gen', async () => {
|
|
const actual = await vi.importActual<any>('$lib/gen')
|
|
const {
|
|
getBenchmarkAppByPath,
|
|
getBenchmarkCompletedJob,
|
|
getBenchmarkCompletedJobResultMaybe,
|
|
getBenchmarkDatatableSchema,
|
|
getBenchmarkDraftForUser,
|
|
getBenchmarkFlowByPath,
|
|
getBenchmarkJobLogs,
|
|
getBenchmarkScriptByHash,
|
|
getBenchmarkScriptByPath,
|
|
hasBenchmarkWorkspace,
|
|
listBenchmarkApps,
|
|
listBenchmarkDatatables,
|
|
listBenchmarkDrafts,
|
|
listBenchmarkFlows,
|
|
listBenchmarkJobs,
|
|
listBenchmarkScripts,
|
|
createBenchmarkHttpTrigger,
|
|
createBenchmarkSchedule,
|
|
previewBenchmarkSchedule,
|
|
runBenchmarkDatatableSql,
|
|
runBenchmarkFlowByPath,
|
|
runBenchmarkScriptPreview,
|
|
updateBenchmarkDraft
|
|
} = await import('./mockBackend')
|
|
|
|
function wrapService<T extends object>(target: T, overrides: Record<string, unknown>): T {
|
|
return new Proxy(target, {
|
|
get(source, property, receiver) {
|
|
if (typeof property === 'string' && property in overrides) {
|
|
return overrides[property]
|
|
}
|
|
return Reflect.get(source, property, receiver)
|
|
}
|
|
})
|
|
}
|
|
|
|
return {
|
|
...actual,
|
|
DraftService: wrapService(actual.DraftService, {
|
|
updateDraft: async (data: {
|
|
workspace: string
|
|
kind: any
|
|
path: string
|
|
requestBody?: { value?: unknown }
|
|
}) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? updateBenchmarkDraft(data)
|
|
: actual.DraftService.updateDraft(data),
|
|
getDraftForUser: async (data: { workspace: string; kind: any; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? getBenchmarkDraftForUser(data)
|
|
: actual.DraftService.getDraftForUser(data),
|
|
listDrafts: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? listBenchmarkDrafts(data.workspace)
|
|
: actual.DraftService.listDrafts(data)
|
|
}),
|
|
ScriptService: wrapService(actual.ScriptService, {
|
|
listScripts: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? (listBenchmarkScripts(data.workspace) ?? [])
|
|
: actual.ScriptService.listScripts(data),
|
|
existsScriptByPath: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? Boolean(getBenchmarkScriptByPath(data.workspace, data.path))
|
|
: actual.ScriptService.existsScriptByPath(data),
|
|
getScriptByPath: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
const script = getBenchmarkScriptByPath(data.workspace, data.path)
|
|
if (!script) {
|
|
throw new Error(`Script "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return script
|
|
}
|
|
return actual.ScriptService.getScriptByPath(data)
|
|
},
|
|
getScriptByPathWithDraft: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
const script = getBenchmarkScriptByPath(data.workspace, data.path)
|
|
if (!script) {
|
|
throw new Error(`Script "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return script
|
|
}
|
|
return actual.ScriptService.getScriptByPathWithDraft(data)
|
|
},
|
|
getScriptByHash: async (data: { workspace: string; hash: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
const script = getBenchmarkScriptByHash(data.workspace, data.hash)
|
|
if (!script) {
|
|
throw new Error(`Script hash "${data.hash}" not found in benchmark workspace`)
|
|
}
|
|
return script
|
|
}
|
|
return actual.ScriptService.getScriptByHash(data)
|
|
}
|
|
}),
|
|
FlowService: wrapService(actual.FlowService, {
|
|
listFlows: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? (listBenchmarkFlows(data.workspace) ?? [])
|
|
: actual.FlowService.listFlows(data),
|
|
existsFlowByPath: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? Boolean(getBenchmarkFlowByPath(data.workspace, data.path))
|
|
: actual.FlowService.existsFlowByPath(data),
|
|
getFlowByPath: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
const flow = getBenchmarkFlowByPath(data.workspace, data.path)
|
|
if (!flow) {
|
|
throw new Error(`Flow "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return flow
|
|
}
|
|
return actual.FlowService.getFlowByPath(data)
|
|
},
|
|
getFlowByPathWithDraft: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
const flow = getBenchmarkFlowByPath(data.workspace, data.path)
|
|
if (!flow) {
|
|
throw new Error(`Flow "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return flow
|
|
}
|
|
return actual.FlowService.getFlowByPathWithDraft(data)
|
|
},
|
|
getFlowLatestVersion: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
const flow = getBenchmarkFlowByPath(data.workspace, data.path)
|
|
if (!flow) {
|
|
throw new Error(`Flow "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return { id: 1 }
|
|
}
|
|
return actual.FlowService.getFlowLatestVersion(data)
|
|
}
|
|
}),
|
|
JobService: wrapService(actual.JobService, {
|
|
runScriptPreview: async (data: {
|
|
workspace: string
|
|
requestBody?: {
|
|
content?: string
|
|
language?: string
|
|
args?: Record<string, unknown>
|
|
path?: string
|
|
}
|
|
}) => {
|
|
if (!hasBenchmarkWorkspace(data.workspace)) {
|
|
return actual.JobService.runScriptPreview(data)
|
|
}
|
|
const requestBody = data.requestBody ?? {}
|
|
const database = requestBody.args?.database
|
|
// Datatable SQL runs as a `postgresql` preview against `datatable://<name>`.
|
|
// Execute it through the canned-SQL mock instead of linting it as a script.
|
|
if (
|
|
requestBody.language === 'postgresql' &&
|
|
typeof database === 'string' &&
|
|
database.startsWith('datatable://')
|
|
) {
|
|
return runBenchmarkDatatableSql({
|
|
workspace: data.workspace,
|
|
datatableName: database.slice('datatable://'.length),
|
|
sql: requestBody.content ?? ''
|
|
})
|
|
}
|
|
return runBenchmarkScriptPreview({ workspace: data.workspace, requestBody })
|
|
},
|
|
runFlowByPath: async (data: {
|
|
workspace: string
|
|
path: string
|
|
requestBody?: Record<string, unknown>
|
|
}) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? runBenchmarkFlowByPath({
|
|
workspace: data.workspace,
|
|
path: data.path,
|
|
args: data.requestBody
|
|
})
|
|
: actual.JobService.runFlowByPath(data),
|
|
getJob: async (data: { workspace: string; id: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
const job = getBenchmarkCompletedJob(data.workspace, data.id)
|
|
if (!job) {
|
|
throw new Error(`Job "${data.id}" not found in benchmark workspace`)
|
|
}
|
|
return job
|
|
}
|
|
return actual.JobService.getJob(data)
|
|
},
|
|
getCompletedJobResultMaybe: async (data: { workspace: string; id: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? getBenchmarkCompletedJobResultMaybe({ workspace: data.workspace, id: data.id })
|
|
: actual.JobService.getCompletedJobResultMaybe(data),
|
|
listJobs: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? (listBenchmarkJobs(data.workspace) ?? [])
|
|
: actual.JobService.listJobs(data),
|
|
getJobLogs: async (data: { workspace: string; id: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? getBenchmarkJobLogs(data.workspace, data.id)
|
|
: actual.JobService.getJobLogs(data)
|
|
}),
|
|
WorkspaceService: wrapService(actual.WorkspaceService, {
|
|
listDataTableTables: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? (listBenchmarkDatatables(data.workspace) ?? [])
|
|
: actual.WorkspaceService.listDataTableTables(data),
|
|
getDataTableTableSchema: async (data: {
|
|
workspace: string
|
|
datatableName: string
|
|
schemaName: string
|
|
tableName: string
|
|
}) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? getBenchmarkDatatableSchema({
|
|
workspace: data.workspace,
|
|
datatableName: data.datatableName,
|
|
schemaName: data.schemaName,
|
|
tableName: data.tableName
|
|
})
|
|
: actual.WorkspaceService.getDataTableTableSchema(data)
|
|
}),
|
|
ScheduleService: wrapService(actual.ScheduleService, {
|
|
existsSchedule: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? false : actual.ScheduleService.existsSchedule(data),
|
|
listSchedules: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? [] : actual.ScheduleService.listSchedules(data),
|
|
getSchedule: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
throw new Error(`Schedule "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return actual.ScheduleService.getSchedule(data)
|
|
},
|
|
previewSchedule: async (data: { requestBody?: Record<string, unknown> }) =>
|
|
previewBenchmarkSchedule(data),
|
|
createSchedule: async (data: { workspace: string; requestBody: Record<string, unknown> }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? createBenchmarkSchedule(data)
|
|
: actual.ScheduleService.createSchedule(data)
|
|
}),
|
|
ResourceService: wrapService(actual.ResourceService, {
|
|
existsResource: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? false : actual.ResourceService.existsResource(data),
|
|
listResource: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? [] : actual.ResourceService.listResource(data),
|
|
getResource: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
throw new Error(`Resource "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return actual.ResourceService.getResource(data)
|
|
},
|
|
queryResourceTypes: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? [] : actual.ResourceService.queryResourceTypes(data)
|
|
}),
|
|
VariableService: wrapService(actual.VariableService, {
|
|
existsVariable: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? false : actual.VariableService.existsVariable(data),
|
|
listVariable: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? [] : actual.VariableService.listVariable(data),
|
|
getVariable: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
throw new Error(`Variable "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return actual.VariableService.getVariable(data)
|
|
}
|
|
}),
|
|
AppService: wrapService(actual.AppService, {
|
|
existsApp: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? Boolean(getBenchmarkAppByPath(data.workspace, data.path))
|
|
: actual.AppService.existsApp(data),
|
|
listApps: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? (listBenchmarkApps(data.workspace) ?? [])
|
|
: actual.AppService.listApps(data),
|
|
getAppByPath: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
const app = getBenchmarkAppByPath(data.workspace, data.path)
|
|
if (!app) {
|
|
throw new Error(`App "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return app
|
|
}
|
|
return actual.AppService.getAppByPath(data)
|
|
}
|
|
}),
|
|
HttpTriggerService: wrapService(actual.HttpTriggerService, {
|
|
existsHttpTrigger: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? false : actual.HttpTriggerService.existsHttpTrigger(data),
|
|
listHttpTriggers: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? [] : actual.HttpTriggerService.listHttpTriggers(data),
|
|
getHttpTrigger: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
throw new Error(`HTTP trigger "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return actual.HttpTriggerService.getHttpTrigger(data)
|
|
},
|
|
createHttpTrigger: async (data: { workspace: string; requestBody: Record<string, unknown> }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? createBenchmarkHttpTrigger(data)
|
|
: actual.HttpTriggerService.createHttpTrigger(data)
|
|
}),
|
|
WebsocketTriggerService: wrapService(actual.WebsocketTriggerService, {
|
|
existsWebsocketTrigger: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? false
|
|
: actual.WebsocketTriggerService.existsWebsocketTrigger(data),
|
|
listWebsocketTriggers: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? []
|
|
: actual.WebsocketTriggerService.listWebsocketTriggers(data),
|
|
getWebsocketTrigger: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
throw new Error(`Websocket trigger "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return actual.WebsocketTriggerService.getWebsocketTrigger(data)
|
|
}
|
|
}),
|
|
KafkaTriggerService: wrapService(actual.KafkaTriggerService, {
|
|
existsKafkaTrigger: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? false
|
|
: actual.KafkaTriggerService.existsKafkaTrigger(data),
|
|
listKafkaTriggers: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? [] : actual.KafkaTriggerService.listKafkaTriggers(data),
|
|
getKafkaTrigger: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
throw new Error(`Kafka trigger "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return actual.KafkaTriggerService.getKafkaTrigger(data)
|
|
}
|
|
}),
|
|
NatsTriggerService: wrapService(actual.NatsTriggerService, {
|
|
existsNatsTrigger: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? false : actual.NatsTriggerService.existsNatsTrigger(data),
|
|
listNatsTriggers: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? [] : actual.NatsTriggerService.listNatsTriggers(data),
|
|
getNatsTrigger: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
throw new Error(`NATS trigger "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return actual.NatsTriggerService.getNatsTrigger(data)
|
|
}
|
|
}),
|
|
PostgresTriggerService: wrapService(actual.PostgresTriggerService, {
|
|
existsPostgresTrigger: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? false
|
|
: actual.PostgresTriggerService.existsPostgresTrigger(data),
|
|
listPostgresTriggers: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? []
|
|
: actual.PostgresTriggerService.listPostgresTriggers(data),
|
|
getPostgresTrigger: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
throw new Error(`Postgres trigger "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return actual.PostgresTriggerService.getPostgresTrigger(data)
|
|
}
|
|
}),
|
|
MqttTriggerService: wrapService(actual.MqttTriggerService, {
|
|
existsMqttTrigger: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? false : actual.MqttTriggerService.existsMqttTrigger(data),
|
|
listMqttTriggers: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? [] : actual.MqttTriggerService.listMqttTriggers(data),
|
|
getMqttTrigger: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
throw new Error(`MQTT trigger "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return actual.MqttTriggerService.getMqttTrigger(data)
|
|
}
|
|
}),
|
|
SqsTriggerService: wrapService(actual.SqsTriggerService, {
|
|
existsSqsTrigger: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? false : actual.SqsTriggerService.existsSqsTrigger(data),
|
|
listSqsTriggers: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? [] : actual.SqsTriggerService.listSqsTriggers(data),
|
|
getSqsTrigger: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
throw new Error(`SQS trigger "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return actual.SqsTriggerService.getSqsTrigger(data)
|
|
}
|
|
}),
|
|
GcpTriggerService: wrapService(actual.GcpTriggerService, {
|
|
existsGcpTrigger: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? false : actual.GcpTriggerService.existsGcpTrigger(data),
|
|
listGcpTriggers: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? [] : actual.GcpTriggerService.listGcpTriggers(data),
|
|
getGcpTrigger: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
throw new Error(`GCP trigger "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return actual.GcpTriggerService.getGcpTrigger(data)
|
|
}
|
|
}),
|
|
AzureTriggerService: wrapService(actual.AzureTriggerService, {
|
|
existsAzureTrigger: async (data: { workspace: string; path: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace)
|
|
? false
|
|
: actual.AzureTriggerService.existsAzureTrigger(data),
|
|
listAzureTriggers: async (data: { workspace: string }) =>
|
|
hasBenchmarkWorkspace(data.workspace) ? [] : actual.AzureTriggerService.listAzureTriggers(data),
|
|
getAzureTrigger: async (data: { workspace: string; path: string }) => {
|
|
if (hasBenchmarkWorkspace(data.workspace)) {
|
|
throw new Error(`Azure trigger "${data.path}" not found in benchmark workspace`)
|
|
}
|
|
return actual.AzureTriggerService.getAzureTrigger(data)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
const benchmarkOutputPath = process.env.WMILL_FRONTEND_AI_EVAL_OUTPUT_PATH
|
|
const benchmarkIt = benchmarkOutputPath ? it : it.skip
|
|
|
|
benchmarkIt(
|
|
'runs the frontend benchmark adapter from environment input',
|
|
async () => {
|
|
const { resetBenchmarkMockBackend } = await import('./mockBackend')
|
|
resetBenchmarkMockBackend()
|
|
const { runFrontendBenchmarkFromEnv } = await import('./benchmarkRunner')
|
|
try {
|
|
const payload = await runFrontendBenchmarkFromEnv()
|
|
const absoluteOutputPath = resolve(benchmarkOutputPath!)
|
|
await mkdir(dirname(absoluteOutputPath), { recursive: true })
|
|
await writeFile(absoluteOutputPath, JSON.stringify(payload, null, 2) + '\n', 'utf8')
|
|
|
|
expect(payload.cases.length).toBeGreaterThan(0)
|
|
} finally {
|
|
resetBenchmarkMockBackend()
|
|
}
|
|
},
|
|
// Full-suite runs (30+ cases at concurrency 2-3) routinely exceed 10 minutes.
|
|
7_200_000
|
|
)
|