UNPKG

@wix-pilot/core

Version:

A flexible plugin that drives your tests with human-written commands, enhanced by the power of large language models (LLMs)

140 lines (139 loc) 6.15 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.StepPerformer = void 0; const extractTaggedOutputs_1 = require("../../common/extract/extractTaggedOutputs"); const logger_1 = __importDefault(require("../../common/logger")); class StepPerformer { context; promptCreator; codeEvaluator; promptHandler; cacheHandler; snapshotComparator; screenCapturer; sharedContext = {}; constructor(context, promptCreator, codeEvaluator, promptHandler, cacheHandler, snapshotComparator, screenCapturer) { this.context = context; this.promptCreator = promptCreator; this.codeEvaluator = codeEvaluator; this.promptHandler = promptHandler; this.cacheHandler = cacheHandler; this.snapshotComparator = snapshotComparator; this.screenCapturer = screenCapturer; } extendJSContext(newContext) { for (const key in newContext) { if (key in this.context) { logger_1.default .labeled("WARNING") .warn(`Pilot's variable from context \`${key}\` is overridden by a new value from \`extendJSContext\``); break; } } this.context = { ...this.context, ...newContext }; } async generateCode(currentStep, previousSteps, screenshotHandler) { const cacheKey = this.cacheHandler.generateCacheKey({ currentStep, previousSteps, }); if (this.cacheHandler.isCacheInUse() && cacheKey) { const cachedValues = this.cacheHandler.getFromPersistentCache(cacheKey); if (cachedValues) { const matchingEntry = await this.cacheHandler.findMatchingCacheEntryValidationMatcherBased(cachedValues, this.context, this.sharedContext); if (matchingEntry) { logger_1.default.labeled("CACHE").warn(`Using cached value`); return matchingEntry.value.code; } } } const screenCapture = await screenshotHandler(); // No cache match found, generate new code const prompt = this.promptCreator.createPrompt(currentStep, screenCapture.viewHierarchy ?? "Unknown view hierarchy", !!screenCapture.snapshot, previousSteps); const promptResult = await this.promptHandler.runPrompt(prompt, screenCapture.snapshot); const extractedCodeBlock = (0, extractTaggedOutputs_1.extractPilotOutputs)(promptResult); if (!extractedCodeBlock.code) { logger_1.default.error("No code found"); } const code = extractedCodeBlock.code; const cacheValue = { code }; if (this.cacheHandler.isCacheInUse() && cacheKey && extractedCodeBlock.cacheValidationMatcher) { this.cacheHandler.addToTemporaryCacheValidationMatcherBased(cacheKey, cacheValue, extractedCodeBlock.cacheValidationMatcher); } else if (this.cacheHandler.isCacheInUse() && cacheKey) { this.cacheHandler.addToTemporaryCacheValidationMatcherBased(cacheKey, cacheValue); } return code; } async perform(step, previous = [], screenCapture, maxAttempts = 2) { const progress = logger_1.default.startProgress({ actionLabel: "STEP", successLabel: "DONE", failureLabel: "FAIL", }, { message: step, isBold: true, color: "whiteBright", }); let lastError = null; let lastCode; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { const screenshotHandler = async () => { return attempt == 1 ? screenCapture : await this.screenCapturer.capture(true); }; const generatedCode = await this.generateCode(step, previous, screenshotHandler); lastCode = generatedCode; if (!generatedCode) { progress.updateLabel("RETRY", { message: step, isBold: true, color: "yellow", }); throw new Error("Failed to generate code from intent, please retry generating the code or provide a code that throws a descriptive error."); } const result = await this.codeEvaluator.evaluate(generatedCode, this.context, this.sharedContext); this.sharedContext = result.sharedContext || this.sharedContext; progress.stop("success", { message: "Step completed successfully", isBold: true, color: "green", }); if (attempt > 1) { logger_1.default .labeled("SUCCESS") .info(`Attempt ${attempt}/${maxAttempts} succeeded for step "${step}"`); } return result; } catch (error) { lastError = error; const errorDetails = error instanceof Error ? error.message : error; logger_1.default .labeled("ERROR") .error(`Attempt ${attempt}/${maxAttempts} failed for step: ${step}, with error: ${errorDetails}`); if (attempt < maxAttempts) { progress.updateLabel("RETRY", "Trying again"); previous = [ ...previous, { step, code: lastCode ?? "undefined", error: errorDetails, }, ]; } } } progress.stop("failure", "Step failed after multiple attempts"); throw lastError; } } exports.StepPerformer = StepPerformer;