@autobe/agent
Version:
AI backend server code generator
217 lines (205 loc) • 7.52 kB
text/typescript
import { IAgenticaController } from "@agentica/core";
import {
AutoBeAnalyze,
AutoBeAnalyzeScenarioEvent,
AutoBeAnalyzeWriteModuleEvent,
AutoBeAnalyzeWriteSectionEvent,
AutoBeAnalyzeWriteUnitEvent,
AutoBeEventSource,
AutoBeProgressEventBase,
} from "@autobe/interface";
import { IPointer, Singleton } from "tstl";
import typia, { ILlmApplication, IValidation } from "typia";
import { v7 } from "uuid";
import { AutoBeContext } from "../../context/AutoBeContext";
import { validateSectionSectionContent } from "../../utils/validateEnglishOnly";
import { AutoBePreliminaryController } from "../common/AutoBePreliminaryController";
import { transformAnalyzeWriteSectionPatchHistory } from "./histories/transformAnalyzeWriteSectionPatchHistory";
import { IAutoBeAnalyzeWriteSectionApplication } from "./structures/IAutoBeAnalyzeWriteSectionApplication";
import { detectTechLockin } from "./utils/buildHardValidators";
import { detectInventedEntities } from "./utils/detectInventedEntities";
export const orchestrateAnalyzeWriteSectionPatch = async (
ctx: AutoBeContext,
props: {
scenario: AutoBeAnalyzeScenarioEvent;
file: AutoBeAnalyze.IFileScenario;
moduleEvent: AutoBeAnalyzeWriteModuleEvent;
unitEvent: AutoBeAnalyzeWriteUnitEvent;
moduleIndex: number;
unitIndex: number;
previousSectionEvent: AutoBeAnalyzeWriteSectionEvent;
feedback: string;
progress: AutoBeProgressEventBase;
promptCacheKey: string;
retry: number;
scenarioEntityNames?: string[];
sectionIndices?: number[] | null;
},
): Promise<AutoBeAnalyzeWriteSectionEvent> => {
const preliminary: AutoBePreliminaryController<
"previousAnalysisSections" | "complete"
> = new AutoBePreliminaryController({
application:
typia.json.application<IAutoBeAnalyzeWriteSectionApplication>(),
source: SOURCE,
kinds: ["previousAnalysisSections", "complete"],
state: ctx.state(),
dispatch: (e) => ctx.dispatch(e),
});
const counter = new Singleton(() => ++props.progress.completed);
const event: AutoBeAnalyzeWriteSectionEvent = await preliminary.orchestrate(
ctx,
async (out) => {
const pointer: IPointer<IAutoBeAnalyzeWriteSectionApplication.IWrite | null> =
{
value: null,
};
const result: AutoBeContext.IResult = await ctx.conversate({
source: SOURCE,
controller: createController({
pointer,
preliminary,
scenarioEntityNames: props.scenarioEntityNames,
}),
enforceFunctionCall: true,
promptCacheKey: props.promptCacheKey,
...transformAnalyzeWriteSectionPatchHistory(ctx, {
scenario: props.scenario,
file: props.file,
moduleEvent: props.moduleEvent,
unitEvent: props.unitEvent,
moduleIndex: props.moduleIndex,
unitIndex: props.unitIndex,
previousSectionEvent: props.previousSectionEvent,
feedback: props.feedback,
preliminary,
sectionIndices: props.sectionIndices,
}),
});
if (pointer.value === null) return out(result)(null);
// Section-level merge: preserve originals for non-targeted sections
let finalSectionSections = pointer.value.sectionSections;
if (
props.sectionIndices != null &&
props.sectionIndices.length > 0 &&
props.previousSectionEvent.sectionSections.length ===
pointer.value.sectionSections.length
) {
const targetSet = new Set(props.sectionIndices);
finalSectionSections = pointer.value.sectionSections.map(
(section, idx) =>
targetSet.has(idx)
? section
: props.previousSectionEvent.sectionSections[idx]!,
);
}
const event: AutoBeAnalyzeWriteSectionEvent = {
type: SOURCE,
id: v7(),
moduleIndex: pointer.value.moduleIndex,
unitIndex: pointer.value.unitIndex,
sectionSections: finalSectionSections,
acquisition: preliminary.getAcquisition(),
tokenUsage: result.tokenUsage,
metric: result.metric,
step: (ctx.state().analyze?.step ?? -1) + 1,
total: props.progress.total,
completed: counter.get(),
retry: props.retry,
created_at: new Date().toISOString(),
};
return out(result)(event);
},
);
ctx.dispatch(event);
return event;
};
function createController(props: {
pointer: IPointer<IAutoBeAnalyzeWriteSectionApplication.IWrite | null>;
preliminary: AutoBePreliminaryController<
"previousAnalysisSections" | "complete"
>;
scenarioEntityNames?: string[];
}): IAgenticaController.IClass {
const validate = (
input: unknown,
): IValidation<IAutoBeAnalyzeWriteSectionApplication.IProps> => {
const result: IValidation<IAutoBeAnalyzeWriteSectionApplication.IProps> =
typia.validate<IAutoBeAnalyzeWriteSectionApplication.IProps>(input);
if (result.success === false) return result;
// Validate English-only content for complete requests
if (result.data.request.type === "write") {
const englishValidation = validateSectionSectionContent(
result.data.request.sectionSections,
);
if (!englishValidation.valid) {
return {
success: false,
errors: englishValidation.errors.map((error) => ({
path: "$input.request.sectionSections",
expected: "English-only content (no Chinese, Korean, Japanese)",
value: error,
})),
data: result.data,
};
}
// Validate no technology lock-in
const techViolations = detectTechLockin(
result.data.request.sectionSections,
);
if (techViolations.length > 0) {
return {
success: false,
errors: techViolations.map((error) => ({
path: "$input.request.sectionSections",
expected:
"Technology-neutral content (no specific DB/framework/infrastructure names)",
value: error,
})),
data: result.data,
};
}
// Validate no invented entities (P0-B)
if (props.scenarioEntityNames && props.scenarioEntityNames.length > 0) {
const inventionViolations = detectInventedEntities(
result.data.request.sectionSections,
props.scenarioEntityNames,
);
if (inventionViolations.length > 0) {
return {
success: false,
errors: inventionViolations.map((error) => ({
path: "$input.request.sectionSections",
expected: `Only entities from scenario catalog: ${props.scenarioEntityNames!.join(", ")}`,
value: error,
})),
data: result.data,
};
}
}
return result;
}
return props.preliminary.validate({
thinking: result.data.thinking ?? "",
request: result.data.request,
});
};
const application: ILlmApplication = props.preliminary.fixApplication(
typia.llm.application<IAutoBeAnalyzeWriteSectionApplication>({
validate: {
process: validate,
},
}),
);
return {
protocol: "class",
name: SOURCE,
application,
execute: {
process: (input) => {
if (input.request.type === "write") props.pointer.value = input.request;
},
} satisfies IAutoBeAnalyzeWriteSectionApplication,
};
}
const SOURCE = "analyzeWriteSection" satisfies AutoBeEventSource;