@autobe/agent
Version:
AI backend server code generator
275 lines (262 loc) • 8.73 kB
text/typescript
import { IAgenticaController } from "@agentica/core";
import {
AutoBeOpenApi,
AutoBeProgressEventBase,
AutoBeTestAuthorizeFunction,
AutoBeTestWriteEvent,
} from "@autobe/interface";
import {
AutoBeFunctionCallingMetricFactory,
AutoBeOpenApiTypeChecker,
} from "@autobe/utils";
import { IPointer, Singleton } from "tstl";
import typia, { ILlmApplication, IValidation } from "typia";
import { v7 } from "uuid";
import { AutoBeContext } from "../../context/AutoBeContext";
import { AutoBeTokenUsageComponent } from "../../context/AutoBeTokenUsageComponent";
import { executeCachedBatch } from "../../utils/executeCachedBatch";
import { forceRetry } from "../../utils/forceRetry";
import { validateEmptyCode } from "../../utils/validateEmptyCode";
import { getTestArtifacts } from "./compile/getTestArtifacts";
import { transformTestAuthorizeWriteHistory } from "./histories/transformTestAuthorizeWriteHistory";
import { AutoBeTestAuthorizeProgrammer } from "./programmers/AutoBeTestAuthorizeProgrammer";
import { IAutoBeTestArtifacts } from "./structures/IAutoBeTestArtifacts";
import { IAutoBeTestAuthorizationWriteApplication } from "./structures/IAutoBeTestAuthorizationWriteApplication";
import { IAutoBeTestAuthorizeProcedure } from "./structures/IAutoBeTestAuthorizeWriteResult";
/**
* Test Authorization Write Orchestrator
*
* Creates authorization utility functions for test scenarios using LLM to
* generate proper authentication handling code.
*/
export const orchestrateTestAuthorizeWrite = async (
ctx: AutoBeContext,
props: {
instruction: string;
progress: AutoBeProgressEventBase;
document: AutoBeOpenApi.IDocument;
},
): Promise<IAutoBeTestAuthorizeProcedure[]> => {
const authOperations: AutoBeOpenApi.IOperation[] =
props.document.operations.filter(
(op) =>
op.authorizationActor !== null &&
op.authorizationType !== null &&
op.parameters.length === 0 &&
op.requestBody !== null &&
op.responseBody !== null &&
AutoBeOpenApiTypeChecker.isObject(
props.document.components.schemas[op.requestBody.typeName] ?? {},
),
);
return await executeCachedBatch(
ctx,
authOperations.map((operation) => async (promptCacheKey) => {
const artifacts: IAutoBeTestArtifacts = await getTestArtifacts(ctx, {
endpoint: {
method: operation.method,
path: operation.path,
},
});
const counter = new Singleton(() => ++props.progress.completed);
let event: AutoBeTestWriteEvent<AutoBeTestAuthorizeFunction>;
try {
event =
operation.authorizationType === "join"
? await forceRetry(() =>
process(ctx, {
operation,
artifacts,
progress: props.progress,
counter,
promptCacheKey,
}),
)
: await write(ctx, {
document: props.document,
progress: props.progress,
counter,
artifacts,
operation,
});
} catch (error) {
counter.get();
throw error;
}
ctx.dispatch(event);
return {
type: "authorize",
artifacts,
function: event.function,
operation,
};
}),
);
};
async function write(
ctx: AutoBeContext,
props: {
document: AutoBeOpenApi.IDocument;
operation: AutoBeOpenApi.IOperation;
artifacts: IAutoBeTestArtifacts;
progress: AutoBeProgressEventBase;
counter: Singleton<number>;
},
): Promise<AutoBeTestWriteEvent<AutoBeTestAuthorizeFunction>> {
const schema: AutoBeOpenApi.IJsonSchema | undefined =
props.document.components.schemas[
props.operation.requestBody?.typeName ?? ""
];
if (
schema === undefined ||
AutoBeOpenApiTypeChecker.isObject(schema) === false
)
throw new Error("Authorization operation needs object request body.");
else if (props.operation.authorizationActor === null)
throw new Error("Operation is not an authorization operation.");
const functionName: string = AutoBeTestAuthorizeProgrammer.getFunctionName(
props.operation,
);
const content: string = AutoBeTestAuthorizeProgrammer.writeTemplate({
operation: props.operation,
schema,
});
const authorizationFunction: AutoBeTestAuthorizeFunction = {
type: "authorize",
endpoint: {
method: props.operation.method,
path: props.operation.path,
},
actor: props.operation.authorizationActor,
authType: props.operation.authorizationType!,
location: `test/authorize/${functionName}.ts`,
name: functionName,
content: await AutoBeTestAuthorizeProgrammer.replaceImportStatements({
compiler: await ctx.compiler(),
artifacts: props.artifacts,
content,
}),
};
return {
type: "testWrite",
id: v7(),
created_at: new Date().toISOString(),
function: authorizationFunction,
metric: AutoBeFunctionCallingMetricFactory.create(),
tokenUsage: new AutoBeTokenUsageComponent(),
completed: props.counter.get(),
total: props.progress.total,
step: ctx.state().interface?.step ?? 0,
} satisfies AutoBeTestWriteEvent<AutoBeTestAuthorizeFunction>;
}
async function process(
ctx: AutoBeContext,
props: {
operation: AutoBeOpenApi.IOperation;
artifacts: IAutoBeTestArtifacts;
progress: AutoBeProgressEventBase;
counter: Singleton<number>;
promptCacheKey: string;
},
): Promise<AutoBeTestWriteEvent<AutoBeTestAuthorizeFunction>> {
const pointer: IPointer<IAutoBeTestAuthorizationWriteApplication.IProps | null> =
{
value: null,
};
const { metric, tokenUsage } = await ctx.conversate({
source: "testWrite",
controller: createController({
operation: props.operation,
build: (next) => {
pointer.value = next;
},
}),
enforceFunctionCall: true,
promptCacheKey: props.promptCacheKey,
...(await transformTestAuthorizeWriteHistory(ctx, {
operation: props.operation,
artifacts: props.artifacts,
})),
});
if (pointer.value === null) {
props.counter.get();
throw new Error("Failed to create authorization function.");
}
// Create the authorize function
const functionName: string = AutoBeTestAuthorizeProgrammer.getFunctionName(
props.operation,
);
const authorizationFunction: AutoBeTestAuthorizeFunction = {
type: "authorize",
endpoint: {
method: props.operation.method,
path: props.operation.path,
},
actor: pointer.value.actor,
authType: props.operation.authorizationType!,
location: `test/authorize/${functionName}.ts`,
name: functionName,
content: await AutoBeTestAuthorizeProgrammer.replaceImportStatements({
compiler: await ctx.compiler(),
artifacts: props.artifacts,
content: pointer.value.revise.final ?? pointer.value.draft,
}),
};
return {
type: "testWrite",
id: v7(),
created_at: new Date().toISOString(),
function: authorizationFunction,
metric,
tokenUsage,
completed: props.counter.get(),
total: props.progress.total,
step: ctx.state().interface?.step ?? 0,
} satisfies AutoBeTestWriteEvent<AutoBeTestAuthorizeFunction>;
}
function createController(props: {
operation: AutoBeOpenApi.IOperation;
build: (next: IAutoBeTestAuthorizationWriteApplication.IProps) => void;
}): IAgenticaController.IClass {
const validate: Validator = (input) => {
const result: IValidation<IAutoBeTestAuthorizationWriteApplication.IProps> =
typia.validate<IAutoBeTestAuthorizationWriteApplication.IProps>(input);
if (result.success === false) return result;
const functionName: string = AutoBeTestAuthorizeProgrammer.getFunctionName(
props.operation,
);
const errors: IValidation.IError[] = validateEmptyCode({
name: functionName,
draft: result.data.draft,
revise: result.data.revise,
path: "$input",
asynchronous: true,
});
return errors.length
? {
success: false,
errors,
data: result.data,
}
: result;
};
const application: ILlmApplication =
typia.llm.application<IAutoBeTestAuthorizationWriteApplication>({
validate: {
write: validate,
},
});
return {
protocol: "class",
name: "TestAuthorizationWrite",
application,
execute: {
write: (next) => {
props.build(next);
},
} satisfies IAutoBeTestAuthorizationWriteApplication,
};
}
type Validator = (
input: unknown,
) => IValidation<IAutoBeTestAuthorizationWriteApplication.IProps>;