UNPKG

@autobe/agent

Version:

AI backend server code generator

747 lines (685 loc) 28.2 kB
import { AutoBeOpenApi, AutoBePreliminaryKind } from "@autobe/interface"; import { AutoBeOpenApiEndpointComparator, StringUtil } from "@autobe/utils"; import { HashSet } from "tstl"; import typia, { IValidation } from "typia"; import { AutoBeSystemPromptConstant } from "../../../constants/AutoBeSystemPromptConstant"; import { AutoBePreliminaryController } from "../AutoBePreliminaryController"; import { IAutoBePreliminaryRequest } from "../structures/AutoBePreliminaryRequest"; import { IAutoBePreliminaryComplete } from "../structures/IAutoBePreliminaryComplete"; import { IAutoBePreliminaryGetAnalysisSections } from "../structures/IAutoBePreliminaryGetAnalysisSections"; import { IAutoBePreliminaryGetDatabaseSchemas } from "../structures/IAutoBePreliminaryGetDatabaseSchemas"; import { IAutoBePreliminaryGetInterfaceOperations } from "../structures/IAutoBePreliminaryGetInterfaceOperations"; import { IAutoBePreliminaryGetInterfaceSchemas } from "../structures/IAutoBePreliminaryGetInterfaceSchemas"; import { IAutoBePreliminaryGetPreviousAnalysisSections } from "../structures/IAutoBePreliminaryGetPreviousAnalysisSections"; import { IAutoBePreliminaryGetPreviousDatabaseSchemas } from "../structures/IAutoBePreliminaryGetPreviousDatabaseSchemas"; import { IAutoBePreliminaryGetPreviousInterfaceOperations } from "../structures/IAutoBePreliminaryGetPreviousInterfaceOperations"; import { IAutoBePreliminaryGetPreviousInterfaceSchemas } from "../structures/IAutoBePreliminaryGetPreviousInterfaceSchemas"; import { IAutoBePreliminaryGetRealizeCollectors } from "../structures/IAutoBePreliminaryGetRealizeCollectors"; import { IAutoBePreliminaryGetRealizeTransformers } from "../structures/IAutoBePreliminaryGetRealizeTransformers"; export const validatePreliminary = <Kind extends AutoBePreliminaryKind>( controller: AutoBePreliminaryController<Kind>, data: IAutoBePreliminaryRequest<Kind>, ): IValidation<IAutoBePreliminaryRequest<Kind>> => { // discriminator const type: Exclude< IAutoBePreliminaryRequest<AutoBePreliminaryKind>["request"]["type"], `getPrevious${string}` > = ( data.request.type.startsWith("getPrevious") ? data.request.type.replace("getPrevious", "get") : data.request.type ) as Exclude< IAutoBePreliminaryRequest<AutoBePreliminaryKind>["request"]["type"], `getPrevious${string}` >; // --------------------------------------------------------------------------- // COMPLETE CASE // // Every IXApplication interface (e.g. IAutoBeRealizeCollectorWriteApplication, // IAutoBeInterfaceEndpointWriteApplication, IAutoBeDatabaseSchemaApplication, // etc.) exposes a single `process()` whose `request` parameter is a // discriminated union: // // request: IWrite — submit generated artifacts // | IAutoBePreliminaryGet* — incremental RAG data loading // | IAutoBePreliminaryComplete — finalize the loop // // The LLM sends `{ type: "complete" }` when it believes the cyclinic // write → validate → correct loop is finished. However, LLMs frequently // attempt to call complete() prematurely — before ever submitting a write — // especially when the context window is thin or when exhausted preliminary // types are removed from the union, leaving only `complete` as a seemingly // valid choice. // // To guard against this, we check `controller.getPreviousWrite()`: // // - Prior write EXISTS → validate the `IAutoBePreliminaryComplete` // structure via typia and allow finalization. // - NO prior write → reject with an explicit error instructing the // LLM to submit its write first before requesting completion. // // @see IAutoBePreliminaryComplete — shared completion request structure // @see AutoBePreliminaryController — orchestrate() loop that consumes // the completed flag // @see orchestratePreliminary — sets completed.value when // confirm === true // --------------------------------------------------------------------------- if (type === "complete") { const previousWrite: Record<string, unknown> | null = controller.getPreviousWrite(); if (previousWrite !== null) return typia.validate<{ thinking: string; request: IAutoBePreliminaryComplete; }>(data) as IValidation<IAutoBePreliminaryRequest<Kind>>; return { success: false, data, errors: [ { path: "$input.request", value: data.request, expected: "IWrite", description: StringUtil.trim` No write has been submitted yet. Please call \`process({ request: { type: "write", ... } })\` with your content first, then call "complete" once you are satisfied with the result. `, }, ], }; } // individual validation const func = PreliminaryApplicationValidator[type]; // biome-ignore-start lint: intended return func( controller as any, data as any, data.request.type.startsWith("getPrevious"), ) as any; // biome-ignore-end lint: intended }; namespace PreliminaryApplicationValidator { export const getAnalysisSections = ( controller: AutoBePreliminaryController< "analysisSections" | "previousAnalysisSections" >, input: IAutoBePreliminaryRequest< "analysisSections" | "previousAnalysisSections" >, previous: boolean, ): IValidation< IAutoBePreliminaryRequest<"analysisSections" | "previousAnalysisSections"> > => { const accessor: "analysisSections" | "previousAnalysisSections" = previous ? "previousAnalysisSections" : "analysisSections"; if (controller.getAll()[accessor] === undefined) return nonExisting(controller, accessor, input); const all: Set<number> = new Set( controller.getAll()[accessor].map((s) => s.id), ); const oldbie: Set<number> = new Set( controller.getLocal()[accessor].map((s) => s.id), ); const newbie: Set<number> = new Set( controller .getAll() [accessor].filter((s) => oldbie.has(s.id) === false) .map((s) => s.id), ); const errors: IValidation.IError[] = []; input.request.sectionIds.forEach((key, i) => { if (all.has(key) === false) errors.push({ path: `$input.request.sectionIds[${i}]`, value: key, expected: Array.from(newbie) .sort((a, b) => a - b) .map((x) => String(x)) .join(" | "), description: StringUtil.trim` You've requested a NON-EXISTING analysis section ID: ${key} This section ID does NOT exist in the system. This is NOT a recommendation, but an ABSOLUTE INSTRUCTION you MUST follow: ⛔ NEVER request section ID ${key} again - it does not exist! ⛔ NEVER assume or invent section IDs that are not in the catalog! ⛔ You MUST choose ONLY from the available section IDs listed in the catalog! Available analysis sections you can request: ID | File | Unit | Section ---|------|------|-------- ${controller .getAll() [accessor].filter((s) => newbie.has(s.id)) .sort((a, b) => a.id - b.id) .map( (s) => `${s.id} | ${s.filename} | ${s.unitTitle} | ${s.sectionTitle}`, ) .join("\n")} ${ newbie.size === 0 ? AutoBeSystemPromptConstant.PRELIMINARY_ANALYSIS_SECTION_EXHAUSTED.replace( "getAnalysisSections" satisfies IAutoBePreliminaryGetAnalysisSections["type"], previous ? ("getPreviousAnalysisSections" satisfies IAutoBePreliminaryGetPreviousAnalysisSections["type"]) : ("getAnalysisSections" satisfies IAutoBePreliminaryGetAnalysisSections["type"]), ) : "" } `, }); }); if (input.request.sectionIds.every((k) => oldbie.has(k))) errors.push({ path: `$input.request`, value: input.request, expected: controller .getArgumentTypeNames() .filter( (k) => k !== typia.reflect.name<IAutoBePreliminaryGetAnalysisSections>(), ) .join(" | "), description: AutoBeSystemPromptConstant.PRELIMINARY_ARGUMENT_ALL_DUPLICATED.replaceAll( "{{REQUEST_TYPE}}", typia.misc.literals< IAutoBePreliminaryGetAnalysisSections["type"] >()[0], ), }); return finalize(input, errors); }; export const getDatabaseSchemas = ( controller: AutoBePreliminaryController< "databaseSchemas" | "previousDatabaseSchemas" >, input: IAutoBePreliminaryRequest< "databaseSchemas" | "previousDatabaseSchemas" >, previous: boolean, ): IValidation< IAutoBePreliminaryRequest<"databaseSchemas" | "previousDatabaseSchemas"> > => { const accessor: "databaseSchemas" | "previousDatabaseSchemas" = previous ? "previousDatabaseSchemas" : "databaseSchemas"; if (controller.getAll()[accessor] === undefined) return nonExisting(controller, accessor, input); const all: Set<string> = new Set( controller.getAll()[accessor].map((s) => s.name), ); const oldbie: Set<string> = new Set( controller.getLocal()[accessor].map((s) => s.name), ); const newbie: Set<string> = new Set( controller .getAll() [accessor].filter((s) => oldbie.has(s.name) === false) .map((s) => s.name), ); const errors: IValidation.IError[] = []; const quoted: string[] = Array.from(newbie) .sort() .map((x) => JSON.stringify(x)); input.request.schemaNames.forEach((key, i) => { if (all.has(key) === false) errors.push({ path: `$input.request.schemaNames[${i}]`, value: key, expected: quoted.join(" | "), description: StringUtil.trim` You've referenced a NON-EXISTING database schema name: ${JSON.stringify(key)} This database schema does NOT exist in the system. This is NOT a recommendation, but an ABSOLUTE INSTRUCTION you MUST follow: ⛔ NEVER request ${JSON.stringify(key)} again - it does not exist! ⛔ NEVER assume or invent schema names that are not in the list below! ⛔ NEVER repeat the same invalid value - I repeat: ${JSON.stringify(key)} is INVALID! ⛔ You MUST choose ONLY from the existing database schemas listed below! Existing database schema names you can request: ${quoted.map((q) => `- ${q}`).join("\n")} ${ newbie.size === 0 ? AutoBeSystemPromptConstant.PRELIMINARY_DATABASE_SCHEMA_EXHAUSTED.replace( "getDatabaseSchemas" satisfies IAutoBePreliminaryGetDatabaseSchemas["type"], previous ? ("getPreviousDatabaseSchemas" satisfies IAutoBePreliminaryGetPreviousDatabaseSchemas["type"]) : ("getDatabaseSchemas" satisfies IAutoBePreliminaryGetDatabaseSchemas["type"]), ) : "" } `, }); }); if (input.request.schemaNames.every((k) => oldbie.has(k))) errors.push({ path: `$input.request`, value: input.request, expected: controller.getArgumentTypeNames().join(" | "), description: AutoBeSystemPromptConstant.PRELIMINARY_ARGUMENT_ALL_DUPLICATED.replaceAll( "{{REQUEST_TYPE}}", typia.misc.literals< IAutoBePreliminaryGetDatabaseSchemas["type"] >()[0], ) .replace( "{{OLDBIE}}", Array.from(oldbie.keys()) .map((k) => `- ${k}`) .join("\n"), ) .replace( "{{NEWBIE}}", Array.from(newbie.keys()) .map((k) => `- ${k}`) .join("\n") || "(none)", ), }); return finalize(input, errors); }; export const getInterfaceOperations = ( controller: AutoBePreliminaryController< "interfaceOperations" | "previousInterfaceOperations" >, input: IAutoBePreliminaryRequest< "interfaceOperations" | "previousInterfaceOperations" >, previous: boolean, ): IValidation< IAutoBePreliminaryRequest< "interfaceOperations" | "previousInterfaceOperations" > > => { const accessor: "interfaceOperations" | "previousInterfaceOperations" = previous ? "previousInterfaceOperations" : "interfaceOperations"; if (controller.getAll()[accessor] === undefined) return nonExisting(controller, accessor, input); const all: HashSet<AutoBeOpenApi.IEndpoint> = new HashSet( controller.getAll()[accessor].map((o) => ({ method: o.method, path: o.path, })), AutoBeOpenApiEndpointComparator.hashCode, AutoBeOpenApiEndpointComparator.equals, ); const oldbie: HashSet<AutoBeOpenApi.IEndpoint> = new HashSet( controller.getLocal()[accessor].map((o) => ({ method: o.method, path: o.path, })), AutoBeOpenApiEndpointComparator.hashCode, AutoBeOpenApiEndpointComparator.equals, ); const newbie: HashSet<AutoBeOpenApi.IEndpoint> = new HashSet( controller .getAll() [accessor].map((o) => ({ method: o.method, path: o.path, })) .filter((e) => oldbie.has(e) === false), AutoBeOpenApiEndpointComparator.hashCode, AutoBeOpenApiEndpointComparator.equals, ); const errors: IValidation.IError[] = []; input.request.endpoints.forEach((key, i) => { if (all.has(key) === false) errors.push({ path: `$input.request.endpoints[${i}]`, value: key, expected: "AutoBeOpenApi.IEndpoint", description: StringUtil.trim` You've requested a NON-EXISTING API endpoint: \`${JSON.stringify(key)}\` This endpoint does NOT exist in the system. This is NOT a recommendation, but an ABSOLUTE INSTRUCTION you MUST follow: ⛔ NEVER request \`${JSON.stringify(key)}\` again - it does not exist! ⛔ NEVER assume or invent endpoints that are not in the list below! ⛔ NEVER repeat the same invalid endpoint! ⛔ You MUST choose ONLY from the existing endpoints listed below! Existing API endpoints you can request: Method | Path ------ | ---- ${newbie .toJSON() .sort(AutoBeOpenApiEndpointComparator.compare) .map((o) => `${o.method} | ${o.path}`) .join("\n")} ${ newbie.size() === 0 ? AutoBeSystemPromptConstant.PRELIMINARY_INTERFACE_OPERATION_EXHAUSTED.replace( "getInterfaceOperations" satisfies IAutoBePreliminaryGetInterfaceOperations["type"], previous ? ("getPreviousInterfaceOperations" satisfies IAutoBePreliminaryGetPreviousInterfaceOperations["type"]) : ("getInterfaceOperations" satisfies IAutoBePreliminaryGetInterfaceOperations["type"]), ) : "" } `, }); }); if (input.request.endpoints.every((k) => oldbie.has(k))) errors.push({ path: `$input.request`, value: input.request, expected: controller.getArgumentTypeNames().join(" | "), description: AutoBeSystemPromptConstant.PRELIMINARY_ARGUMENT_ALL_DUPLICATED.replaceAll( "{{REQUEST_TYPE}}", typia.misc.literals< IAutoBePreliminaryGetInterfaceOperations["type"] >()[0], ) .replace( "{{OLDBIE}}", StringUtil.trim` Path | Method -----|------- ${Array.from(oldbie.toJSON()) .sort(AutoBeOpenApiEndpointComparator.compare) .map((o) => `${o.path} | ${o.method}`) .join("\n")} `, ) .replace( "{{NEWBIE}}", newbie .toJSON() .sort(AutoBeOpenApiEndpointComparator.compare) .map((o) => `- ${o.method} ${o.path}`) .join("\n") || "(none)", ), }); return finalize(input, errors); }; export const getInterfaceSchemas = ( controller: AutoBePreliminaryController< "interfaceSchemas" | "previousInterfaceSchemas" >, input: IAutoBePreliminaryRequest< "interfaceSchemas" | "previousInterfaceSchemas" >, previous: boolean, ): IValidation< IAutoBePreliminaryRequest<"interfaceSchemas" | "previousInterfaceSchemas"> > => { const accessor: "interfaceSchemas" | "previousInterfaceSchemas" = previous ? "previousInterfaceSchemas" : "interfaceSchemas"; if (controller.getAll()[accessor] === undefined) return nonExisting(controller, accessor, input); const all: Set<string> = new Set( Object.keys(controller.getAll()[accessor]), ); const oldbie: Set<string> = new Set( Object.keys(controller.getLocal()[accessor]), ); const newbie: Set<string> = new Set( Object.keys(controller.getAll()[accessor]).filter( (k) => oldbie.has(k) === false, ), ); const errors: IValidation.IError[] = []; const quoted: string[] = Array.from(newbie) .sort() .map((k) => JSON.stringify(k)); input.request.typeNames.forEach((key, i) => { if (all.has(key) === false) errors.push({ path: `$input.request.typeNames[${i}]`, value: key, expected: quoted.join(" | "), description: StringUtil.trim` You've referenced a NON-EXISTING interface schema name: ${JSON.stringify(key)} This interface schema does NOT exist in the system. This is NOT a recommendation, but an ABSOLUTE INSTRUCTION you MUST follow: ⛔ NEVER request ${JSON.stringify(key)} again - it does not exist! ⛔ NEVER assume or invent schema names that are not in the list below! ⛔ NEVER repeat the same invalid value - I repeat: ${JSON.stringify(key)} is INVALID! ⛔ You MUST choose ONLY from the existing interface schemas listed below! Existing interface schema names you can request: ${quoted.map((q) => `- ${q}`).join("\n")} ${ newbie.size === 0 ? AutoBeSystemPromptConstant.PRELIMINARY_INTERFACE_SCHEMA_EXHAUSTED.replace( "getInterfaceSchemas" satisfies IAutoBePreliminaryGetInterfaceSchemas["type"], previous ? ("getPreviousInterfaceSchemas" satisfies IAutoBePreliminaryGetPreviousInterfaceSchemas["type"]) : ("getInterfaceSchemas" satisfies IAutoBePreliminaryGetInterfaceSchemas["type"]), ) : "" } `, }); }); if (input.request.typeNames.every((k) => oldbie.has(k))) errors.push({ path: `$input.request`, value: input.request, expected: controller.getArgumentTypeNames().join(" | "), description: AutoBeSystemPromptConstant.PRELIMINARY_ARGUMENT_ALL_DUPLICATED.replaceAll( "{{REQUEST_TYPE}}", typia.misc.literals< IAutoBePreliminaryGetInterfaceSchemas["type"] >()[0], ) .replace( "{{OLDBIE}}", Array.from(oldbie.keys()) .map((k) => `- ${k}`) .join("\n"), ) .replace( "{{NEWBIE}}", Array.from(newbie.keys()) .map((k) => `- ${k}`) .join("\n") || "(none)", ), }); return finalize(input, errors); }; export const getRealizeCollectors = ( controller: AutoBePreliminaryController<"realizeCollectors">, input: IAutoBePreliminaryRequest<"realizeCollectors">, _previous: boolean, ): IValidation<IAutoBePreliminaryRequest<"realizeCollectors">> => { const all: Set<string> = new Set( controller.getAll().realizeCollectors.map((c) => c.plan.dtoTypeName), ); const oldbie: Set<string> = new Set( controller.getLocal().realizeCollectors.map((c) => c.plan.dtoTypeName), ); const newbie: Set<string> = new Set( controller .getAll() .realizeCollectors.filter( (c) => oldbie.has(c.plan.dtoTypeName) === false, ) .map((c) => c.plan.dtoTypeName), ); const errors: IValidation.IError[] = []; const quoted: string[] = Array.from(newbie) .sort() .map((x) => JSON.stringify(x)); input.request.dtoTypeNames.forEach((key, i) => { if (all.has(key) === false) errors.push({ path: `$input.request.dtoTypeNames[${i}]`, value: key, expected: quoted.join(" | "), description: StringUtil.trim` You've referenced a NON-EXISTING realize collector: ${JSON.stringify(key)} This collector does NOT exist in the system. This is NOT a recommendation, but an ABSOLUTE INSTRUCTION you MUST follow: ⛔ NEVER request ${JSON.stringify(key)} again - it does not exist! ⛔ NEVER assume or invent collector names that are not in the list below! ⛔ NEVER repeat the same invalid value - I repeat: ${JSON.stringify(key)} is INVALID! ⛔ You MUST choose ONLY from the existing collectors listed below! Existing realize collectors you can request: ${quoted.map((q) => `- ${q}`).join("\n")} ${ newbie.size === 0 ? AutoBeSystemPromptConstant.PRELIMINARY_REALIZE_COLLECTOR_EXHAUSTED : "" } `, }); }); if (input.request.dtoTypeNames.every((k) => oldbie.has(k))) errors.push({ path: `$input.request`, value: input.request, expected: controller.getArgumentTypeNames().join(" | "), description: AutoBeSystemPromptConstant.PRELIMINARY_ARGUMENT_ALL_DUPLICATED.replaceAll( "{{REQUEST_TYPE}}", typia.misc.literals< IAutoBePreliminaryGetRealizeCollectors["type"] >()[0], ) .replace( "{{OLDBIE}}", Array.from(oldbie.keys()) .map((k) => `- ${k}`) .join("\n"), ) .replace( "{{NEWBIE}}", Array.from(newbie.keys()) .map((k) => `- ${k}`) .join("\n") || "(none)", ), }); return finalize(input, errors); }; export const getRealizeTransformers = ( controller: AutoBePreliminaryController<"realizeTransformers">, input: IAutoBePreliminaryRequest<"realizeTransformers">, _previous: boolean, ): IValidation<IAutoBePreliminaryRequest<"realizeTransformers">> => { const all: Set<string> = new Set( controller.getAll().realizeTransformers.map((t) => t.plan.dtoTypeName), ); const oldbie: Set<string> = new Set( controller.getLocal().realizeTransformers.map((t) => t.plan.dtoTypeName), ); const newbie: Set<string> = new Set( controller .getAll() .realizeTransformers.filter( (t) => oldbie.has(t.plan.dtoTypeName) === false, ) .map((t) => t.plan.dtoTypeName), ); const errors: IValidation.IError[] = []; const quoted: string[] = Array.from(newbie) .sort() .map((x) => JSON.stringify(x)); input.request.dtoTypeNames.forEach((key, i) => { if (all.has(key) === false) errors.push({ path: `$input.request.dtoTypeNames[${i}]`, value: key, expected: quoted.join(" | "), description: StringUtil.trim` You've referenced a NON-EXISTING realize transformer: ${JSON.stringify(key)} This transformer does NOT exist in the system. This is NOT a recommendation, but an ABSOLUTE INSTRUCTION you MUST follow: ⛔ NEVER request ${JSON.stringify(key)} again - it does not exist! ⛔ NEVER assume or invent transformer names that are not in the list below! ⛔ NEVER repeat the same invalid value - I repeat: ${JSON.stringify(key)} is INVALID! ⛔ You MUST choose ONLY from the existing transformers listed below! Existing realize transformers you can request: ${quoted.map((q) => `- ${q}`).join("\n")} ${ newbie.size === 0 ? AutoBeSystemPromptConstant.PRELIMINARY_REALIZE_TRANSFORMER_EXHAUSTED : "" } `, }); }); if (input.request.dtoTypeNames.every((k) => oldbie.has(k))) errors.push({ path: `$input.request`, value: input.request, expected: controller.getArgumentTypeNames().join(" | "), description: AutoBeSystemPromptConstant.PRELIMINARY_ARGUMENT_ALL_DUPLICATED.replaceAll( "{{REQUEST_TYPE}}", typia.misc.literals< IAutoBePreliminaryGetRealizeTransformers["type"] >()[0], ) .replace( "{{OLDBIE}}", Array.from(oldbie.keys()) .map((k) => `- ${k}`) .join("\n"), ) .replace( "{{NEWBIE}}", Array.from(newbie.keys()) .map((k) => `- ${k}`) .join("\n") || "(none)", ), }); return finalize(input, errors); }; } const finalize = <T>(data: T, errors: IValidation.IError[]): IValidation<T> => errors.length === 0 ? ({ success: true, data, } satisfies IValidation.ISuccess<T>) : ({ success: false, data, errors, } satisfies IValidation.IFailure); const nonExisting = <Kind extends AutoBePreliminaryKind>( controller: AutoBePreliminaryController<Kind>, kind: Kind, data: IAutoBePreliminaryRequest<Kind>, ): IValidation.IFailure => ({ success: false, data, errors: [ { path: "$input.request.type", expected: controller .getKinds() .map((k) => JSON.stringify(k)) .join(" | "), value: kind, description: StringUtil.trim` You've requested a NON-EXISTING preliminary data type: "${kind}" This data type does NOT exist in the current context. This is NOT a recommendation, but an ABSOLUTE INSTRUCTION you MUST follow: ⛔ NEVER request "${kind}" again - it is NOT available in this context! ⛔ NEVER assume data types that are not in the list below! ⛔ NEVER repeat the same invalid request type! ${ controller.getKinds().length === 0 ? StringUtil.trim` ⛔ NO preliminary data is available at all in this context. ✅ You MUST call process({ request: { type: "write", ... } }) RIGHT NOW. ✅ Stop requesting preliminary data and submit your write immediately. ` : StringUtil.trim` ⛔ You MUST choose ONLY from the available kinds listed below! Available preliminary data kinds you can request: ${controller .getKinds() .map((k) => `- ${k}`) .join("\n")} ` } `, }, ], });