andy-stack_vaultkeeper-ai/Types/ExecutionPlan.ts
Andrew Beal e22cd8698a refactor: implement planning mode with user questions and cross-provider function call improvements
- Add planning mode toggle and AskUserQuestion function for interactive planning
- Fix Gemini cross-provider function call detection using toolId presence
- Update OpenAI naming service to handle new response format
- Improve system prompts by removing complexity gate, streamlining to action-first principle
- Add InputDisplay component and question mode to ChatInput
- Refactor execution plan error messages to show all incomplete steps
- Update tests to reflect cross-provider function call format changes
2026-01-04 18:52:44 +00:00

121 lines
No EOL
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 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 })
}
};
}
private incompleteSteps(): number[] {
const incompleteSteps = [];
for (const step of this.executionSteps) {
if (step.status !== ExecutionStatus.Completed) {
incompleteSteps.push(step.step);
}
}
return incompleteSteps;
}
}