@autobe/agent
Version:
AI backend server code generator
270 lines (251 loc) • 9.54 kB
text/typescript
import { IAgenticaController } from "@agentica/core";
import {
AutoBeAnalyzeActor,
AutoBeEventSource,
AutoBeInterfaceAuthorization,
AutoBeOpenApi,
AutoBeProgressEventBase,
} from "@autobe/interface";
import { AutoBeInterfaceAuthorizationEvent } from "@autobe/interface/src/events/AutoBeInterfaceAuthorizationEvent";
import { StringUtil } from "@autobe/utils";
import { ILlmApplication, ILlmSchema, IValidation } from "@samchon/openapi";
import { IPointer } from "tstl";
import typia from "typia";
import { v7 } from "uuid";
import { AutoBeContext } from "../../context/AutoBeContext";
import { assertSchemaModel } from "../../context/assertSchemaModel";
import { executeCachedBatch } from "../../utils/executeCachedBatch";
import { AutoBePreliminaryController } from "../common/AutoBePreliminaryController";
import { transformInterfaceAuthorizationHistory } from "./histories/transformInterfaceAuthorizationHistory";
import { IAutoBeInterfaceAuthorizationsApplication } from "./structures/IAutoBeInterfaceAuthorizationsApplication";
export async function orchestrateInterfaceAuthorization<
Model extends ILlmSchema.Model,
>(
ctx: AutoBeContext<Model>,
props: {
instruction: string;
},
): Promise<AutoBeInterfaceAuthorization[]> {
const actors: AutoBeAnalyzeActor[] = ctx.state().analyze?.actors ?? [];
const progress: AutoBeProgressEventBase = {
total: actors.length,
completed: 0,
};
const authorizations: AutoBeInterfaceAuthorization[] =
await executeCachedBatch(
ctx,
actors.map((a) => async (promptCacheKey) => {
const event: AutoBeInterfaceAuthorizationEvent = await process(ctx, {
actor: a,
progress,
promptCacheKey,
instruction: props.instruction,
});
ctx.dispatch(event);
return {
name: a.name,
operations: event.operations,
};
}),
);
return authorizations;
}
async function process<Model extends ILlmSchema.Model>(
ctx: AutoBeContext<Model>,
props: {
instruction: string;
actor: AutoBeAnalyzeActor;
progress: AutoBeProgressEventBase;
promptCacheKey: string;
},
): Promise<AutoBeInterfaceAuthorizationEvent> {
const preliminary: AutoBePreliminaryController<
"analysisFiles" | "prismaSchemas"
> = new AutoBePreliminaryController({
application:
typia.json.application<IAutoBeInterfaceAuthorizationsApplication>(),
source: SOURCE,
kinds: ["analysisFiles", "prismaSchemas"],
state: ctx.state(),
});
return await preliminary.orchestrate(ctx, async (out) => {
const pointer: IPointer<IAutoBeInterfaceAuthorizationsApplication.IComplete | null> =
{
value: null,
};
const result: AutoBeContext.IResult<Model> = await ctx.conversate({
source: SOURCE,
controller: createController({
model: ctx.model,
actor: props.actor,
build: (next) => {
pointer.value = next;
},
preliminary,
}),
enforceFunctionCall: true,
promptCacheKey: props.promptCacheKey,
...transformInterfaceAuthorizationHistory({
state: ctx.state(),
instruction: props.instruction,
actor: props.actor,
preliminary,
}),
});
return out(result)(
pointer.value !== null
? ({
type: SOURCE,
id: v7(),
operations: pointer.value.operations,
completed: ++props.progress.completed,
metric: result.metric,
tokenUsage: result.tokenUsage,
created_at: new Date().toISOString(),
step: ctx.state().analyze?.step ?? 0,
total: props.progress.total,
} satisfies AutoBeInterfaceAuthorizationEvent)
: null,
);
});
}
function createController<Model extends ILlmSchema.Model>(props: {
model: Model;
actor: AutoBeAnalyzeActor;
preliminary: AutoBePreliminaryController<"analysisFiles" | "prismaSchemas">;
build: (next: IAutoBeInterfaceAuthorizationsApplication.IComplete) => void;
}): IAgenticaController.IClass<Model> {
assertSchemaModel(props.model);
const validate = (
next: unknown,
): IValidation<IAutoBeInterfaceAuthorizationsApplication.IProps> => {
const result: IValidation<IAutoBeInterfaceAuthorizationsApplication.IProps> =
typia.validate<IAutoBeInterfaceAuthorizationsApplication.IProps>(next);
if (result.success === false) return result;
else if (result.data.request.type !== "complete")
return props.preliminary.validate({
thinking: result.data.thinking,
request: result.data.request,
});
// remove login operation for guest role
else if (props.actor.kind === "guest") {
result.data.request.operations = result.data.request.operations.filter(
(op) => op.authorizationType !== "login",
);
}
const errors: IValidation.IError[] = [];
result.data.request.operations.forEach((op, i) => {
// validate authorizationActor
if (op.authorizationActor !== null) {
op.authorizationActor = props.actor.name;
}
// validate responseBody.typeName -> must be ~.IAuthorized
if (op.authorizationType === null) return;
else if (op.responseBody === null)
errors.push({
path: `$input.request.operations.${i}.responseBody`,
expected:
"Response body with I{RoleName(PascalCase)}.IAuthorized type is required",
value: op.responseBody,
description: StringUtil.trim`
Response body is required for authentication operations.
The responseBody must contain description and typeName fields.
typeName must be I{Prefix(PascalCase)}{RoleName(PascalCase)}.IAuthorized
description must be a detailed description of the response body.
`,
});
else if (!op.responseBody.typeName.endsWith(".IAuthorized"))
errors.push({
path: `$input.request.operations.${i}.responseBody.typeName`,
expected: `Type name must be I{RoleName(PascalCase)}.IAuthorized`,
value: op.responseBody?.typeName,
description: StringUtil.trim`
Wrong response body type name: ${op.responseBody?.typeName}
For authentication operations (login, join, refresh), the response body type name must follow the convention "I{RoleName}.IAuthorized".
This standardized naming convention ensures consistency across all authentication endpoints and clearly identifies authorization response types.
The actor name should be in PascalCase format (e.g., IUser.IAuthorized, IAdmin.IAuthorized, ISeller.IAuthorized).
`,
});
});
// validate authorization types' existence
type AuthorizationType = NonNullable<
AutoBeOpenApi.IOperation["authorizationType"]
>;
const authorizationTypes: Set<AuthorizationType> = new Set(
result.data.request.operations
.map((o) => o.authorizationType)
.filter((v) => v !== null),
);
for (const type of typia.misc.literals<AuthorizationType>())
if (props.actor.kind === "guest" && type === "login") continue;
else if (authorizationTypes.has(type) === false)
errors.push({
path: "$input.request.operations[].authorizationType",
expected: StringUtil.trim`{
...(AutoBeOpenApi.IOperation data),
authorizationType: "${type}"
}`,
value: `No authorizationType "${type}" found in any operation`,
description: StringUtil.trim`
There must be an operation that has defined AutoBeOpenApi.IOperation.authorizationType := "${type}"
for the "${props.actor}" role's authorization activity; "${type}".
However, none of the operations have the AutoBeOpenApi.IOperation.authorizationType := "${type}"
value, so that the "${props.actor}" cannot perform the authorization ${type} activity.
Please make that operation at the next function calling. You have to do it.
`,
});
if (errors.length !== 0) {
return {
success: false,
errors,
data: next,
};
}
return result;
};
const application: ILlmApplication<Model> = collection[
props.model === "chatgpt"
? "chatgpt"
: props.model === "gemini"
? "gemini"
: "claude"
](
validate,
) satisfies ILlmApplication<any> as unknown as ILlmApplication<Model>;
return {
protocol: "class",
name: SOURCE,
application,
execute: {
process: (next) => {
if (next.request.type === "complete") props.build(next.request);
},
} satisfies IAutoBeInterfaceAuthorizationsApplication,
};
}
const collection = {
chatgpt: (validator: Validator) =>
typia.llm.application<IAutoBeInterfaceAuthorizationsApplication, "chatgpt">(
{
validate: {
process: validator,
},
},
),
claude: (validator: Validator) =>
typia.llm.application<IAutoBeInterfaceAuthorizationsApplication, "claude">({
validate: {
process: validator,
},
}),
gemini: (validator: Validator) =>
typia.llm.application<IAutoBeInterfaceAuthorizationsApplication, "gemini">({
validate: {
process: validator,
},
}),
};
type Validator = (
input: unknown,
) => IValidation<IAutoBeInterfaceAuthorizationsApplication.IProps>;
const SOURCE = "interfaceAuthorization" satisfies AutoBeEventSource;