andy-stack_vaultkeeper-ai/Types/ExecutionPlan.ts
Andrew Beal 0fb17e7b3a feat: add planning model selection and rate limit countdown UI
Introduce separate planning model setting to allow using different models for planning vs execution. Add visual countdown display when rate limits are hit, with improved retry delay parsing across providers (Claude, OpenAI, Gemini). Refactor settings tab into Views directory and enhance mobile layout for input controls.
2026-01-05 21:49:51 +00:00

132 lines
No EOL
4.4 KiB
TypeScript

import { ExecutionStatus } from "Enums/ExecutionStatus";
import { ExecutionStep } from "./ExecutionStep";
import type { SubmitPlanArgs } from "AIClasses/Schemas/AIFunctionSchemas";
import { Copy, replaceCopy } from "Enums/Copy";
export class ExecutionPlan {
public readonly executionSteps: ExecutionStep[] = [];
public constructor(plan: SubmitPlanArgs) {
for (const [index, step] of plan.steps.entries()) {
this.executionSteps.push(new ExecutionStep(
index + 1,
step.description,
step.instruction,
step.context
));
}
if (this.executionSteps[0]) { // Mark first step as active
this.executionSteps[0].status = ExecutionStatus.Active;
}
}
public completed(): boolean {
return this.executionSteps.every(step => step.status === ExecutionStatus.Completed);
}
public getStatusSummary(): { completed: string[], remaining: string[] } {
const completed: string[] = [];
const remaining: string[] = [];
for (const step of this.executionSteps) {
const stepDescription = `${step.step + 1}. ${step.description}`;
if (step.status === ExecutionStatus.Completed) {
completed.push(stepDescription);
} else {
remaining.push(stepDescription);
}
}
return { completed, remaining };
}
public completeExecutionStep(stepNumber: number): object {
const stepIndex = stepNumber - 1;
const currentStep = this.executionSteps[stepIndex];
if (!currentStep) {
return {
error: replaceCopy(Copy.StepDoesNotExistError, [
stepNumber.toString(),
this.executionSteps.length.toString()
])
};
}
for (let i = 0; i < stepIndex; i++) { // Ensure all previous steps are completed
if (this.executionSteps[i].status !== ExecutionStatus.Completed) {
return {
error: replaceCopy(Copy.StepMustBeCompletedInOrderError, [
stepNumber.toString(),
this.incompleteSteps().join(",")
])
};
}
}
currentStep.status = ExecutionStatus.Completed;
const nextStep = this.executionSteps[stepIndex + 1];
if (nextStep) {
nextStep.status = ExecutionStatus.Active;
return {
message: replaceCopy(Copy.StepCompletedWithNextStep, [
stepNumber.toString(),
(stepNumber + 1).toString()
]),
step: nextStep.step,
description: nextStep.description,
instruction: nextStep.instruction,
...(nextStep.context && { context: nextStep.context })
};
} else {
return {
message: replaceCopy(Copy.AllStepsCompleted, [
stepNumber.toString()
])
};
}
}
public completeExecutionPlan(confirmation: boolean): object {
if (!confirmation) {
return { error: Copy.ConfirmationFalse };
}
if (!this.completed()) {
return { error: replaceCopy(Copy.IncompleteExecutionAttempt,
[this.incompleteSteps().join(", ")]) };
}
return { message: Copy.PlanExecutionComplete }
}
public toFunctionResponse(): object {
if (this.executionSteps.length === 0) {
return {
error: Copy.PlanningFailedError
};
}
const firstStep = this.executionSteps[0];
return {
plan: this.executionSteps.map((step, index) => `${index + 1}. ${step.description}`),
firstStep: {
step: firstStep.step,
description: firstStep.description,
instruction: firstStep.instruction,
...(firstStep.context && { context: firstStep.context })
}
};
}
public incompleteSteps(): number[] {
const incompleteSteps = [];
for (const step of this.executionSteps) {
if (step.status !== ExecutionStatus.Completed) {
incompleteSteps.push(step.step);
}
}
return incompleteSteps;
}
}