@autobe/agent
Version:
AI backend server code generator
973 lines (926 loc) • 7.8 MB
JavaScript
import { MicroAgentica } from "@agentica/core";
import { AutoBeProcessAggregateFactory, StringUtil, TokenUsageComputer, writePrismaApplication, AutoBeOpenApiEndpointComparator, AutoBeOpenApiTypeChecker, MapUtil, missedOpenApiSchemas, revertOpenApiAccessor, transformOpenApiDocument } from "@autobe/utils";
import { ConditionVariable, Singleton, sleep_for, is_node, Semaphore, Pair, HashSet, HashMap, randint } from "tstl";
import { v7 } from "uuid";
import "typia";
import * as __typia_transform__validateReport from "typia/lib/internal/_validateReport.js";
import * as __typia_transform__llmApplicationFinalize from "typia/lib/internal/_llmApplicationFinalize.js";
import { OpenApiTypeChecker, HttpMigration } from "@samchon/openapi";
import pluralize from "pluralize";
import { NamingConvention } from "typia/lib/utils/NamingConvention";
import * as __typia_transform__accessExpressionAsString from "typia/lib/internal/_accessExpressionAsString.js";
import { OpenApiV3_1Emender } from "@samchon/openapi/lib/converters/OpenApiV3_1Emender";
import * as __typia_transform__assertGuard from "typia/lib/internal/_assertGuard.js";
import { Escaper } from "typia/lib/utils/Escaper";
import * as __typia_transform__isUniqueItems from "typia/lib/internal/_isUniqueItems.js";
import * as __typia_transform__createStandardSchema from "typia/lib/internal/_createStandardSchema.js";
function emplaceMap(dict, key, generator) {
const oldbie = dict.get(key);
if (oldbie !== undefined) {
return oldbie;
}
const value = generator();
dict.set(key, value);
return value;
}
class AutoBeAgentBase {
constructor() {
this.listeners_ = new Map;
}
on(type, listener) {
emplaceMap(this.listeners_, type, () => new Set).add(listener);
return this;
}
off(type, listener) {
const set = this.listeners_.get(type);
if (set === undefined) return this;
set.delete(listener);
if (set.size === 0) this.listeners_.delete(type);
return this;
}
async dispatch(event) {
const set = this.listeners_.get(event.type);
if (set === undefined) return;
await Promise.all(Array.from(set).map(async listener => {
try {
await listener(event);
} catch {}
}));
}
}
class AutoBeTokenUsageComponent {
get total() {
return this.input.total + this.output.total;
}
constructor(props) {
if (props === undefined) {
this.input = {
total: 0,
cached: 0
};
this.output = {
total: 0,
reasoning: 0,
accepted_prediction: 0,
rejected_prediction: 0
};
} else {
this.input = props.input;
this.output = props.output;
}
}
assign(props) {
this.input.total = props.input.total;
this.input.cached = props.input.cached;
this.output.total = props.output.total;
this.output.reasoning = props.output.reasoning;
this.output.accepted_prediction = props.output.accepted_prediction;
this.output.rejected_prediction = props.output.rejected_prediction;
}
toJSON() {
return {
total: this.total,
input: {
total: this.input.total,
cached: this.input.cached
},
output: {
total: this.output.total,
reasoning: this.output.reasoning,
accepted_prediction: this.output.accepted_prediction,
rejected_prediction: this.output.rejected_prediction
}
};
}
increment(props) {
this.input.total += props.input.total;
this.input.cached += props.input.cached;
this.output.total += props.output.total;
this.output.reasoning += props.output.reasoning;
this.output.accepted_prediction += props.output.accepted_prediction;
this.output.rejected_prediction += props.output.rejected_prediction;
}
decrement(props) {
this.input.total -= props.input.total;
this.input.cached -= props.input.cached;
this.output.total -= props.output.total;
this.output.reasoning -= props.output.reasoning;
this.output.accepted_prediction -= props.output.accepted_prediction;
this.output.rejected_prediction -= props.output.rejected_prediction;
}
static plus(a, b) {
return new AutoBeTokenUsageComponent({
input: {
total: a.input.total + b.input.total,
cached: a.input.cached + b.input.cached
},
output: {
total: a.output.total + b.output.total,
reasoning: a.output.reasoning + b.output.reasoning,
accepted_prediction: a.output.accepted_prediction + b.output.accepted_prediction,
rejected_prediction: a.output.rejected_prediction + b.output.rejected_prediction
}
});
}
static minus(a, b) {
return new AutoBeTokenUsageComponent({
input: {
total: a.input.total - b.input.total,
cached: a.input.cached - b.input.cached
},
output: {
total: a.output.total - b.output.total,
reasoning: a.output.reasoning - b.output.reasoning,
accepted_prediction: a.output.accepted_prediction - b.output.accepted_prediction,
rejected_prediction: a.output.rejected_prediction - b.output.rejected_prediction
}
});
}
}
class AutoBeTokenUsage {
get aggregate() {
return AutoBeTokenUsage.keys().reduce((acc, cur) => AutoBeTokenUsageComponent.plus(acc, this[cur]), new AutoBeTokenUsageComponent).toJSON();
}
constructor(props) {
if (props === undefined) {
this.facade = new AutoBeTokenUsageComponent;
this.analyze = new AutoBeTokenUsageComponent;
this.prisma = new AutoBeTokenUsageComponent;
this.interface = new AutoBeTokenUsageComponent;
this.test = new AutoBeTokenUsageComponent;
this.realize = new AutoBeTokenUsageComponent;
return;
} else if (props instanceof AutoBeTokenUsage) {
this.facade = props.facade;
this.analyze = props.analyze;
this.prisma = props.prisma;
this.interface = props.interface;
this.test = props.test;
this.realize = props.realize;
} else {
this.facade = new AutoBeTokenUsageComponent(props.facade);
this.analyze = new AutoBeTokenUsageComponent(props.analyze);
this.prisma = new AutoBeTokenUsageComponent(props.prisma);
this.interface = new AutoBeTokenUsageComponent(props.interface);
this.test = new AutoBeTokenUsageComponent(props.test);
this.realize = new AutoBeTokenUsageComponent(props.realize);
}
}
assign(props) {
this.facade.assign(props.facade);
this.analyze.assign(props.analyze);
this.prisma.assign(props.prisma);
this.interface.assign(props.interface);
this.test.assign(props.test);
this.realize.assign(props.realize);
}
toJSON() {
return {
aggregate: this.aggregate,
facade: this.facade.toJSON(),
analyze: this.analyze.toJSON(),
prisma: this.prisma.toJSON(),
interface: this.interface.toJSON(),
test: this.test.toJSON(),
realize: this.realize.toJSON()
};
}
record(usage, additionalStages = []) {
additionalStages.forEach(stage => {
this[stage].increment(usage);
});
}
increment(usage) {
AutoBeTokenUsage.keys().forEach(key => {
this[key].increment(usage[key]);
});
return this;
}
decrement(usage) {
AutoBeTokenUsage.keys().forEach(key => {
this[key].decrement(usage[key]);
});
return this;
}
static plus(usageA, usageB) {
return new AutoBeTokenUsage({
facade: AutoBeTokenUsageComponent.plus(usageA.facade, usageB.facade),
analyze: AutoBeTokenUsageComponent.plus(usageA.analyze, usageB.analyze),
prisma: AutoBeTokenUsageComponent.plus(usageA.prisma, usageB.prisma),
interface: AutoBeTokenUsageComponent.plus(usageA.interface, usageB.interface),
test: AutoBeTokenUsageComponent.plus(usageA.test, usageB.test),
realize: AutoBeTokenUsageComponent.plus(usageA.realize, usageB.realize)
});
}
static keys() {
return [ "facade", "analyze", "prisma", "interface", "test", "realize" ];
}
}
function createAgenticaHistory(props) {
if (props.history.type === "userMessage") return {
...props.history,
toJSON: () => props.history
}; else if (props.history.type === "assistantMessage") return {
...props.history,
toJSON: () => props.history
};
const operation = props.operations.find(op => op.function.name === props.history.type);
if (operation === undefined) return null;
const partial = {
id: props.history.id,
created_at: props.history.created_at,
type: "execute",
arguments: {
instruction: props.history.type === "analyze" ? undefined : props.history.instruction
},
value: {
success: props.history.type === "analyze" || props.history.type === "interface" ? true : props.history.compiled.type === "success"
},
success: true
};
return {
...partial,
protocol: operation.protocol,
operation,
toJSON: () => ({
...partial,
protocol: operation.protocol,
operation: operation.toJSON()
})
};
}
class AutoBeTimeoutError extends Error {
constructor(message) {
super(message);
const proto = new.target.prototype;
if (Object.setPrototypeOf) Object.setPrototypeOf(this, proto); else this.__proto__ = proto;
}
get name() {
return this.constructor.name;
}
}
var TimedConversation;
(function(TimedConversation) {
TimedConversation.process = async props => {
if (props.timeout === null) try {
const histories = await props.agent.conversate(props.message);
return {
type: "success",
histories
};
} catch (error) {
return {
type: "error",
error
};
}
const result = {
value: null
};
const holder = new ConditionVariable;
const abort = new AbortController;
const timeout = new Singleton(() => setTimeout(() => {
if (result.value !== null) return;
result.value = {
type: "timeout",
error: new AutoBeTimeoutError(`Timeout, over ${props.timeout} ms.`)
};
abort.abort(`Timeout, over ${props.timeout} ms`);
void holder.notify_all().catch(() => {});
}, props.timeout));
props.agent.on("request", () => {
timeout.get();
});
props.agent.conversate(props.message, {
abortSignal: abort.signal
}).then(v => result.value ?? (result.value = {
type: "success",
histories: v
})).catch(e => result.value ?? (result.value = {
type: "error",
error: e
})).finally(() => {
void holder.notify_all().catch(() => {});
clearTimeout(timeout.get());
});
await holder.wait();
await sleep_for(0);
return result.value;
};
})(TimedConversation || (TimedConversation = {}));
const getCommonPrompt = config => "\x3c!--\nfilename: COMMON.md\n--\x3e\n# AutoBE Agent Identity\n\nYou are an integral part of **AutoBE**, an AI-powered no-code agent system that automatically generates complete backend applications. AutoBE orchestrates a team of specialized agents working together to transform natural language requirements into production-ready TypeScript + NestJS + Prisma backend applications.\n\n## Core Identity\n\n- **System**: AutoBE - Automated Backend Engineering System\n- **Mission**: \"Can you converse? Then you're a full-stack developer.\"\n- **Purpose**: Transform human requirements into working backend applications with zero manual coding\n- **Philosophy**: Waterfall development with compiler-validated, production-ready output\n\n## User Context Awareness\n\nAs an AutoBE agent, you are contextually aware of your user's environment:\n\n- **Language Localization**: The user's language locale is `\"${locale}\"`. All communication with the user and function calling result descriptions must be in this target locale language. Never communicate in a different language than the user's locale. However, all generated code, documentation, comments, and technical artifacts must be written in English to maintain international compatibility and industry standards.\n- **Temporal Context**: The user's timezone is `\"${timezone}\"` with current ISO datetime `${datetime}`. Consider this temporal context when discussing schedules, timestamps, or time-sensitive requirements.\n\n## AutoBE Agent Principles\n\nAs an AutoBE agent, you embody these core principles:\n\n1. **Expert Specialist**: You are a world-class expert in your specific domain, contributing your specialized knowledge to the collective intelligence of AutoBE\n2. **Production-First**: Every output you generate must be production-ready, following enterprise-grade standards and best practices\n3. **Compiler-Driven**: The TypeScript compiler is the ultimate authority - all code must compile without errors\n4. **Immediate Execution**: You execute tasks immediately without seeking confirmation, as all necessary information is provided\n5. **Single-Pass Excellence**: You have one opportunity to deliver perfect results - there are no iterations or corrections\n\n## AutoBE Architecture Context\n\nYou operate within a sophisticated multi-agent ecosystem:\n- **5 Core Agents**: Analyze → Prisma → Interface → Test → Realize\n- **Waterfall Model**: Each phase builds upon the previous, ensuring systematic development\n- **Compiler Validation**: Every output is validated by specialized compilers before proceeding\n- **100% Automation**: From requirements to deployment-ready code without human intervention\n\n## Your Commitment\n\nAs an AutoBE agent, you are committed to:\n- Delivering enterprise-grade, type-safe TypeScript code\n- Following established NestJS patterns and Prisma best practices \n- Ensuring all outputs pass compiler validation\n- Contributing to AutoBE's vision of democratizing backend development\n\nRemember: You are not just an AI assistant - you are a professional backend engineer powered by AutoBE, capable of building complete, production-ready applications from conversational requirements.".replace("${locale}", config?.locale ?? locale.get()).replace("${timezone}", config?.timezone ?? timezone.get()).replace("${datetime}", (new Date).toISOString());
const locale = new Singleton(() => is_node() ? process.env.LANG?.split(".")[0] ?? "en-US" : navigator.language);
const timezone = new Singleton(() => Intl.DateTimeFormat().resolvedOptions().timeZone);
const supportMistral = (agent, vendor) => {
if (vendor.model.includes("mistral") || vendor.model.includes("devstral") || vendor.model.includes("codestral")) {
agent.on("request", async e => {
const newMessages = [];
for (const m of e.body.messages) {
newMessages.push(m);
if (m.role === "tool") {
m.tool_call_id = uuidToShortId(m.tool_call_id);
newMessages.push({
role: "assistant",
content: "A tool has been called."
});
} else if (m.role === "assistant") {
for (const call of m.tool_calls ?? []) call.id = uuidToShortId(call.id);
}
}
e.body.messages = newMessages;
});
}
};
const BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
function simpleHash(str) {
let h1 = 3735928559;
let h2 = 1103547991;
for (let i = 0; i < str.length; i++) {
const ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}
function toBase62(num, length) {
let result = "";
let n = num;
while (n > 0 && result.length < length) {
result = BASE62_CHARS[n % 62] + result;
n = Math.floor(n / 62);
}
return result.padStart(length, "0");
}
function uuidToShortId(uuid) {
const hash = simpleHash(uuid);
return toBase62(hash, 9);
}
const consentFunctionCall = async props => {
const pointer = {
value: null
};
const agent = new MicroAgentica({
model: "chatgpt",
vendor: props.vendor,
config: {
...props.config ?? [],
retry: props.config?.retry ?? 4,
executor: {
describe: null
},
systemPrompt: {
common: () => getCommonPrompt(props.config)
}
},
histories: [ {
id: v7(),
type: "systemMessage",
text: '\x3c!--\nfilename: CONSENT_FUNCTION_CALL.md\n--\x3e\n# Function Calling Consent Agent System Prompt\n\nYou are a consent facilitation agent designed to generate immediate user consent messages for function calling scenarios. Your sole purpose is to produce concise, direct approval messages when an AI assistant seeks user permission before executing functions.\n\n**INPUT:** You will receive the most recent assistant message from a conversation. Your task is to analyze this assistant message and determine whether it requires consent for function calling or not.\n\n## Available Functions\n\n```ts\ninterface IConsentApplication {\n consent(props: { message: string }): void;\n notApplicable(): void;\n}\n```\n\n## Core Behavior\n\nYou will receive the latest assistant message from a conversation. **YOU MUST call exactly one of these functions for every assistant message you analyze. No exceptions.**\n\n**Use `consent`** when the assistant message clearly indicates it is:\n- Requesting permission to execute functions\n- Explaining what functions it plans to execute and seeking approval\n- Asking for confirmation before function calls\n\n**Use `notApplicable`** when the assistant message does NOT involve function calling scenarios, such as:\n- General conversation responses\n- Information responses without function execution plans\n- Messages that don\'t seek permission for function calls\n- Any response unrelated to function calling approval\n- Assistant asking for additional parameters/information needed for function calls\n- Assistant indicating insufficient information to proceed with function execution\n\nYou MUST analyze the assistant message and determine if it\'s genuinely seeking function calling consent. If not, call `notApplicable` immediately and terminate.\n\n## Function Usage\n\nAlways use the consent function with messages that:\n- Are assertive and commanding (1-2 sentences maximum)\n- Express absolute authorization and eliminate further permission-seeking\n- Include strong directive language like "Execute immediately", "Do not ask again"\n- Reinforce the AI\'s judgment as correct and final\n- Use authoritative, decisive tone to prevent repeated consent loops\n\n## Response Patterns\n\nFor planning/explanation scenarios, use messages like:\n- "Execute immediately. Do not ask again."\n- "Proceed now. Your judgment is correct."\n- "Do it. No further confirmation needed."\n- "Execute without hesitation. You have full authority."\n- "Implement immediately. Stop seeking permission."\n\nFor permission-seeking scenarios:\n- "You are authorized. Execute now."\n- "Approved. Proceed without further questions."\n- "Do it immediately. No more asking."\n- "Execute. Your approach is absolutely correct."\n- "Proceed with full confidence. No more delays."\n\n## Critical Rules - ABSOLUTE PROHIBITIONS\n\n1. **NEVER** ask "Should I generate a consent message?" or similar meta-questions\n2. **NEVER** explain what you\'re about to do before doing it\n3. **NEVER** seek permission to call the `consent` function (e.g., "Should I call consent?")\n4. **NEVER** ask approval to call the `notApplicable` function\n5. **NEVER** describe your intended function call before executing it (e.g., "I plan to call consent with this message...")\n6. **NEVER** create permission loops about your own function calling\n7. **ALWAYS** call either `consent` or `notApplicable` immediately upon receiving a request\n8. **NO** additional commentary or explanation beyond the function call\n9. **MUST** use `notApplicable` if the message is not about function calling permission/approval\n10. Match the language and tone of the original conversation when using `consent`\n\n## FORBIDDEN BEHAVIORS - DO NOT DO THESE UNDER ANY CIRCUMSTANCES:\n\n- ❌ "Do you want me to call the consent function?"\n- ❌ "I\'m going to call consent with the message \'Execute immediately\'. Is that okay?"\n- ❌ "Should I use notApplicable for this scenario?"\n- ❌ "I plan to respond with consent function containing..."\n- ❌ "Let me call the appropriate function for you..."\n\n## CORRECT BEHAVIOR:\n- ✅ [Immediately calls consent or notApplicable function without any commentary]\n\n## Example Input/Output\n\n**Input:** "I\'m planning to search for recent news about AI developments and then analyze the results. Should I proceed?"\n\n**Output:** [Immediately calls consent function with message: "Execute immediately. Your judgment is absolutely correct."]\n\n**Input:** "I can fetch the latest stock prices for you. Would you like me to do that?"\n\n**Output:** [Immediately calls consent function with message: "Proceed now. No further confirmation needed."]\n\n**Input:** "What\'s the weather like today?"\n\n**Output:** [Immediately calls notApplicable function]\n\n**Input:** "I need more information to proceed. Could you specify which city you want weather data for?"\n\n**Output:** [Immediately calls notApplicable function]\n\n**Input:** "To search effectively, I\'ll need you to provide the specific date range you\'re interested in."\n\n**Output:** [Immediately calls notApplicable function]\n\nYour efficiency and directness are critical - any hesitation or explanation defeats your purpose.',
created_at: (new Date).toISOString()
}, {
id: v7(),
type: "assistantMessage",
text: props.assistantMessage,
created_at: (new Date).toISOString()
} ],
controllers: [ (() => {
const application = {
model: "chatgpt",
options: {
reference: true,
strict: false,
separate: null
},
functions: [ {
name: "consent",
parameters: {
description: " Configuration object",
type: "object",
properties: {
message: {
description: 'A strong, directive consent message that eliminates further\npermission-seeking. Must be authoritative and commanding (1-2 sentences\nmaximum). Examples: "Execute immediately. Do not ask again.", "Proceed\nnow. Your judgment is correct."',
type: "string"
}
},
required: [ "message" ],
additionalProperties: false,
$defs: {}
},
description: 'Generate an immediate, authoritative consent message to approve function\nexecution. Use this when the assistant message is seeking permission or\napproval for function calls.\n\nThe message should be commanding and decisive to prevent further\npermission-seeking loops. Examples: "Execute immediately. Do not ask\nagain.", "Proceed now. Your judgment is correct."',
validate: (() => {
const _io0 = input => "string" === typeof input.message;
const _vo0 = (input, _path, _exceptionable = true) => [ "string" === typeof input.message || _report(_exceptionable, {
path: _path + ".message",
expected: "string",
value: input.message
}) ].every(flag => flag);
const __is = input => "object" === typeof input && null !== input && _io0(input);
let errors;
let _report;
return input => {
if (false === __is(input)) {
errors = [];
_report = __typia_transform__validateReport._validateReport(errors);
((input, _path, _exceptionable = true) => ("object" === typeof input && null !== input || _report(true, {
path: _path + "",
expected: "__type",
value: input
})) && _vo0(input, _path + "", true) || _report(true, {
path: _path + "",
expected: "__type",
value: input
}))(input, "$input", true);
const success = 0 === errors.length;
return success ? {
success,
data: input
} : {
success,
errors,
data: input
};
}
return {
success: true,
data: input
};
};
})()
}, {
name: "notApplicable",
parameters: {
type: "object",
properties: {},
additionalProperties: false,
required: [],
$defs: {}
},
description: "Indicate that the assistant message does not require function calling\nconsent. Use this when the assistant message is NOT seeking permission for\nfunction execution.\n\nThis applies to:\n\n- General conversation responses\n- Information requests without function execution plans\n- Assistant asking for additional parameters/information\n- Any response unrelated to function calling approval\n\nCall this function immediately when the message doesn't involve function\ncalling consent.",
validate: (() => input => ({
success: true,
data: input
}))()
} ]
};
return {
protocol: "class",
name: "consent",
execute: {
consent: props => {
pointer.value = {
type: "consent",
message: props.message
};
},
notApplicable: () => {
pointer.value = {
type: "notApplicable"
};
}
},
application
};
})() ]
});
supportMistral(agent, props.vendor);
const histories = await agent.conversate("Analyze and judge this assistant message please.");
if (pointer.value === null) {
const last = histories[histories.length - 1];
if (last?.type === "assistantMessage") pointer.value = {
type: "assistantMessage",
message: last.text
};
}
props.dispatch({
type: "consentFunctionCall",
id: v7(),
source: props.source,
assistantMessage: props.assistantMessage,
result: pointer.value,
created_at: (new Date).toISOString()
});
return pointer.value?.type === "consent" ? pointer.value.message : null;
};
const getCriticalCompiler = (critical, compiler) => {
const lock = async func => {
await critical.acquire();
try {
return await func();
} finally {
await critical.release();
}
};
return {
prisma: {
compile: props => lock(() => compiler.prisma.compile(props)),
validate: app => lock(() => compiler.prisma.validate(app)),
write: (app, dmbs) => lock(() => compiler.prisma.write(app, dmbs))
},
interface: {
write: (doc, exclude) => lock(() => compiler.interface.write(doc, exclude)),
transform: doc => lock(() => compiler.interface.transform(doc)),
invert: doc => lock(() => compiler.interface.invert(doc))
},
typescript: {
compile: props => lock(() => compiler.typescript.compile(props)),
getExternal: location => lock(() => compiler.typescript.getExternal(location)),
beautify: code => lock(() => compiler.typescript.beautify(code))
},
test: {
compile: props => lock(() => compiler.test.compile(props)),
validate: props => lock(() => compiler.test.validate(props)),
write: props => lock(() => compiler.test.write(props)),
getExternal: () => lock(() => compiler.test.getExternal())
},
realize: {
controller: props => lock(() => compiler.realize.controller(props)),
test: props => lock(() => compiler.realize.test(props))
},
getTemplate: options => lock(() => compiler.getTemplate(options))
};
};
const createAutoBeContext = props => {
const config = {
retry: props.config.retry ?? 4,
locale: props.config.locale ?? "en-US",
timeout: props.config.timeout ?? null
};
const critical = new Semaphore(2);
return {
model: props.model,
vendor: props.vendor,
retry: config.retry,
locale: config.locale,
aggregates: props.aggregates,
compilerListener: props.compilerListener,
compiler: async () => {
const compiler = await props.compiler();
return getCriticalCompiler(critical, compiler);
},
files: props.files,
histories: props.histories,
state: props.state,
usage: props.usage,
dispatch: createDispatch(props),
assistantMessage: message => {
props.histories().push(message);
setTimeout(() => {
void props.dispatch(message).catch(() => {});
});
return message;
},
conversate: async (next, closure) => {
const aggregate = AutoBeProcessAggregateFactory.createAggregate();
const metric = key => {
const accumulate = collection => {
var _a;
++collection.total.metric[key];
collection[_a = next.source] ?? (collection[_a] = AutoBeProcessAggregateFactory.createAggregate());
++collection[next.source].metric[key];
};
++aggregate.metric[key];
accumulate(props.aggregates);
};
const consume = tokenUsage => {
const accumulate = collection => {
var _a;
TokenUsageComputer.increment(collection.total.tokenUsage, tokenUsage);
collection[_a = next.source] ?? (collection[_a] = AutoBeProcessAggregateFactory.createAggregate());
TokenUsageComputer.increment(collection[next.source].tokenUsage, tokenUsage);
};
TokenUsageComputer.increment(aggregate.tokenUsage, tokenUsage);
accumulate(props.aggregates);
props.usage().record(tokenUsage, [ STAGES.find(stage => next.source.startsWith(stage)) ?? "analyze" ]);
};
const progress = {
request: 0,
response: 0,
timeout: 0
};
const execute = async () => {
const agent = new MicroAgentica({
model: props.model,
vendor: props.vendor,
config: {
...props.config ?? {},
retry: props.config?.retry ?? 4,
executor: {
describe: null
},
systemPrompt: {
common: () => getCommonPrompt(props.config)
}
},
histories: next.histories,
controllers: [ next.controller ]
});
supportMistral(agent, props.vendor);
agent.on("request", async event => {
if (next.enforceFunctionCall === true && event.body.tools) event.body.tool_choice = "required";
if (event.body.parallel_tool_calls !== undefined) delete event.body.parallel_tool_calls;
if (next.promptCacheKey) event.body.prompt_cache_key = next.promptCacheKey;
await props.dispatch({
...event,
type: "vendorRequest",
source: next.source,
retry: progress.request++
});
});
agent.on("response", async event => {
void props.dispatch({
...event,
type: "vendorResponse",
source: next.source,
retry: progress.response++
}).catch(() => {});
});
agent.on("call", () => {
metric("attempt");
});
agent.on("jsonParseError", event => {
metric("invalidJson");
void props.dispatch({
...event,
function: event.operation.function.name,
source: next.source
}).catch(() => {});
});
agent.on("validate", event => {
metric("validationFailure");
void props.dispatch({
type: "jsonValidateError",
id: v7(),
source: next.source,
function: event.operation.function.name,
result: event.result,
life: event.life,
created_at: event.created_at
}).catch(() => {});
});
if (closure) closure(agent);
const message = next.enforceFunctionCall === true ? StringUtil.trim`
${next.userMessage}
> You have to call function(s) of below to accomplish my request.
>
> Never hesitate the function calling. Never ask for me permission
> to execute the function. Never explain me your plan with waiting
> for my approval.
>
> I gave you every information for the function calling, so just
> call it. I repeat that, never hesitate the function calling.
> Just do it without any explanation.
>
${next.controller.application.functions.map(f => `> - ${f.name}`).join("\n")}
` : next.userMessage;
const result = await TimedConversation.process({
timeout: config.timeout,
agent,
message
});
const tokenUsage = agent.getTokenUsage().toJSON().aggregate;
props.usage().record(tokenUsage, [ STAGES.find(stage => next.source.startsWith(stage)) ?? "analyze" ]);
consume(tokenUsage);
const success = histories => {
metric("success");
return {
histories,
tokenUsage: aggregate.tokenUsage,
metric: aggregate.metric,
__agent: agent
};
};
if (result.type === "error") throw result.error; else if (result.type === "timeout") {
void props.dispatch({
type: "vendorTimeout",
id: v7(),
source: next.source,
timeout: config.timeout,
retry: progress.timeout++,
created_at: (new Date).toISOString()
}).catch(() => {});
throw result.error;
} else if (true === next.enforceFunctionCall && false === result.histories.some(h => h.type === "execute" && h.success === true)) {
const failure = () => {
throw new Error(StringUtil.trim`
Failed to function calling in the ${next.source} step.
Here is the list of history types that occurred during the conversation:
${result.histories.map(h => `- ${h.type}`).join("\n")}
${JSON.stringify(result.histories)}
`);
};
const last = result.histories.at(-1);
if (last?.type === "assistantMessage" && last.text.trim().length !== 0) {
metric("consent");
const consent = await consentFunctionCall({
source: next.source,
dispatch: e => {
props.dispatch(e).catch(() => {});
},
config: props.config,
vendor: props.vendor,
assistantMessage: last.text
});
if (consent !== null) {
const newHistories = await agent.conversate(consent);
const newTokenUsage = AutoBeTokenUsageComponent.minus(new AutoBeTokenUsageComponent(agent.getTokenUsage().toJSON().aggregate), new AutoBeTokenUsageComponent(tokenUsage));
consume(newTokenUsage);
if (newHistories.some(h => h.type === "execute" && h.success)) return success(newHistories);
}
}
failure();
}
return success(result.histories);
};
if (next.enforceFunctionCall === true) return await forceRetry(execute, config.retry); else return await execute();
},
getCurrentAggregates: phase => {
const previous = AutoBeProcessAggregateFactory.reduce(props.histories().filter(h => h.type === "analyze" || h.type === "prisma" || h.type === "interface" || h.type === "test" || h.type === "realize").map(h => h.aggregates));
return AutoBeProcessAggregateFactory.filterPhase(AutoBeProcessAggregateFactory.minus(props.aggregates, previous), phase);
}
};
};
const createDispatch = props => {
let analyzeStart = null;
let prismaStart = null;
let interfaceStart = null;
let testStart = null;
let realizeStart = null;
return event => {
if (event.type === "analyzeStart") analyzeStart = event; else if (event.type === "prismaStart") prismaStart = event; else if (event.type === "interfaceStart") interfaceStart = event; else if (event.type === "testStart") testStart = event; else if (event.type === "realizeStart") realizeStart = event; else if (event.type === "analyzeComplete") return transformAndDispatch({
dispatch: props.dispatch,
histories: props.histories,
state: props.state,
event,
history: {
type: "analyze",
id: v7(),
prefix: event.prefix,
actors: event.actors,
files: event.files,
aggregates: event.aggregates,
step: event.step,
created_at: analyzeStart?.created_at ?? (new Date).toISOString(),
completed_at: event.created_at
}
}); else if (event.type === "prismaComplete") return transformAndDispatch({
dispatch: props.dispatch,
histories: props.histories,
state: props.state,
event,
history: {
type: "prisma",
id: v7(),
instruction: prismaStart?.reason ?? "",
schemas: event.schemas,
result: event.result,
compiled: event.compiled,
aggregates: event.aggregates,
step: event.step,
created_at: prismaStart?.created_at ?? (new Date).toISOString(),
completed_at: event.created_at
}
}); else if (event.type === "interfaceComplete") return transformAndDispatch({
dispatch: props.dispatch,
histories: props.histories,
state: props.state,
event,
history: {
type: "interface",
id: v7(),
instruction: interfaceStart?.reason ?? "",
authorizations: event.authorizations,
document: event.document,
missed: event.missed,
aggregates: event.aggregates,
step: event.step,
created_at: interfaceStart?.created_at ?? (new Date).toISOString(),
completed_at: (new Date).toISOString()
}
}); else if (event.type === "testComplete") return transformAndDispatch({
dispatch: props.dispatch,
histories: props.histories,
state: props.state,
event,
history: {
type: "test",
id: v7(),
instruction: testStart?.reason ?? "",
files: event.files,
compiled: event.compiled,
aggregates: event.aggregates,
step: event.step,
created_at: testStart?.created_at ?? (new Date).toISOString(),
completed_at: (new Date).toISOString()
}
}); else if (event.type === "realizeComplete") return transformAndDispatch({
dispatch: props.dispatch,
histories: props.histories,
state: props.state,
event,
history: {
type: "realize",
id: v7(),
instruction: realizeStart?.reason ?? "",
authorizations: event.authorizations,
functions: event.functions,
controllers: event.controllers,
compiled: event.compiled,
aggregates: event.aggregates,
step: event.step,
created_at: realizeStart?.created_at ?? (new Date).toISOString(),
completed_at: (new Date).toISOString()
}
});
void props.dispatch(event).catch(() => {});
return null;
};
};
const transformAndDispatch = props => {
props.histories().push(props.history);
props.state()[props.history.type] = props.history;
void props.dispatch(props.event).catch(() => {});
return props.history;
};
const forceRetry = async (task, count) => {
let error = undefined;
for (let i = 0; i < count; ++i) try {
return await task();
} catch (e) {
if (e instanceof AutoBeTimeoutError) throw e;
error = e;
}
throw error;
};
const STAGES = [ "analyze", "prisma", "interface", "test", "realize" ];
const createAutoBeState = histories => {
const reversed = histories.slice().reverse();
return {
analyze: reversed.find(h => h.type === "analyze") ?? null,
prisma: reversed.find(h => h.type === "prisma") ?? null,
interface: reversed.find(h => h.type === "interface") ?? null,
test: reversed.find(h => h.type === "test") ?? null,
realize: reversed.find(h => h.type === "realize") ?? null
};
};
const getAutoBeRealizeGenerated = async props => ({
...Object.fromEntries(props.functions.map(f => [ f.location, f.content ])),
...Object.fromEntries(props.authorizations.map(auth => [ [ auth.decorator.location, auth.decorator.content ], [ auth.provider.location, auth.provider.content ], [ auth.payload.location, auth.payload.content ] ]).flat()),
...await props.compiler.realize.controller({
document: props.document,
functions: props.functions,
authorizations: props.authorizations
})
});
async function getAutoBeGenerated(props) {
const options = {
phase: props.options?.phase ?? props.state.realize ? "realize" : props.state.test ? "test" : props.state.interface ? "interface" : "analyze",
dbms: props.options?.dbms ?? "postgres"
};
const ret = await props.compiler.getTemplate(options);
ret["README.md"] = writeReadMe(props.state, ret["README.md"]);
if (props.state.analyze === null) return ret;
Object.assign(ret, Object.fromEntries(props.state.analyze.files.map(file => [ `docs/analysis/${file.filename.split("/").at(-1)}`, file.content ])));
if (props.options?.phase === "analyze") return ret;
if (props.state.prisma?.step === props.state.analyze.step) {
const schemaFiles = (options?.dbms ?? "postgres") === "postgres" ? props.state.prisma.schemas : await props.compiler.prisma.write(props.state.prisma.result.data, options.dbms);
Object.assign(ret, Object.fromEntries(Object.entries(schemaFiles).map(([key, value]) => [ `prisma/schema/${key.split("/").at(-1)}`, value ])), {
"autobe/prisma.json": JSON.stringify(props.state.prisma.result.data)
});
if (props.state.prisma.compiled.type === "success") ret["docs/ERD.md"] = props.state.prisma.compiled.document; else if (props.state.prisma.compiled.type === "failure") ret["prisma/compile-error-reason.log"] = props.state.prisma.compiled.reason;
}
if (props.options?.phase === "prisma") return ret;
if (props.state.interface?.step === props.state.analyze.step) {
const files = await props.compiler.interface.write(props.state.interface.document, Object.keys(ret));
Object.assign(ret, props.state.test?.step === props.state.interface.step ? Object.fromEntries(Object.entries(files).filter(([key]) => key.startsWith("test/features/") === false)) : files, {
"autobe/document.json": JSON.stringify(props.state.interface.document)
});
}
if (props.options?.phase === "interface") return ret;
if (props.state.test?.step === props.state.analyze.step) Object.assign(ret, Object.fromEntries(props.state.test.files.map(f => [ f.location, f.content ])));
if (props.state.realize?.step === props.state.analyze.step) Object.assign(ret, await getAutoBeRealizeGenerated({
compiler: props.compiler,
document: props.state.interface.document,
authorizations: props.state.realize.authorizations,
functions: props.state.realize.functions,
options: {
dbms: options?.dbms ?? "postgres"
}
}));
if (props.options?.phase === "test") return ret;
Object.assign(ret, {
"autobe/histories.json": JSON.stringify(props.histories),
"autobe/tokenUsage.json": JSON.stringify(props.tokenUsage)
});
return ret;
}
function writeReadMe(state, readme) {
const emoji = history => history ? success(history) ? "✅" : "❌" : "⬜";
return readme.replaceAll("{{ANALYSIS_EMOJI}}", emoji(state.analyze)).replaceAll("{{PRISMA_EMOJI}}", emoji(state.prisma)).replaceAll("{{INTERFACE_EMOJI}}", emoji(state.interface)).replaceAll("{{TEST_EMOJI}}", emoji(state.test)).replaceAll("{{REALIZE_EMOJI}}", emoji(state.realize)).replaceAll("{{BENCHMARK_AGGREGATE}}", writeBenchmarkAggregate(state)).replaceAll("{{BENCHMARK_FUNCTION_CALLING}}", writeBenchmarkFunctionCalling(state));
}
function writeBenchmarkAggregate(state) {
return [ "analyze", "prisma", "interface", "test", "realize" ].map(key => {
const h = state[key];
if (h === null) return `⬜ ${key} | | | | `;
return [ `${success(h) ? "✅" : "❌"} ${h.type}`, Object.entries(label(h)).map(([k, v]) => `${k}: ${v.toLocaleString()}`).join(", "), (h.aggregates.total.metric.success / h.aggregates.tota