@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
JavaScript
;
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;