@microfox/ai-code
Version:
A TypeScript SDK for Ai Code.
478 lines (461 loc) • 16.9 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
FilePlanSchema: () => FilePlanSchema,
formatDirectoryStructure: () => formatDirectoryStructure,
generateCodeFile: () => generateCodeFile,
generateCodeV0: () => generateCodeV0,
generateCodeV2: () => generateCodeV2,
generateProject: () => generateProject,
scanDirectoryStructure: () => scanDirectoryStructure
});
module.exports = __toCommonJS(index_exports);
// src/schemas/index.ts
var import_zod = require("zod");
var FilePlanSchema = import_zod.z.object({
fileName: import_zod.z.string().describe("The name of the file without the extension."),
fileExtension: import_zod.z.string().describe("The file extension (e.g., 'ts', 'tsx', 'js')."),
path: import_zod.z.string().describe("The relative path where the file should be created (e.g., 'src/components')."),
type: import_zod.z.enum(["typescript", "javascript", "json", "markdown", "yaml", "text", "other"]).describe("The type of file."),
codeBrief: import_zod.z.string().describe("A brief, one-sentence description of what this file contains."),
dependencies: import_zod.z.array(import_zod.z.string()).optional().describe("A list of other full file names that this file depends on.")
});
// src/utils/scanDirectoryStructure.ts
var fs = __toESM(require("fs"));
var path = __toESM(require("path"));
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", ".next", ".git", "coverage"]);
var IGNORED_EXTENSIONS = /* @__PURE__ */ new Set([".log", ".lock", ".svg", ".png", ".jpg", ".jpeg", ".gif", ".env"]);
function formatDirectoryStructure(node, depth = 0) {
let output = " ".repeat(depth) + `- ${node.name} (${node.type})
`;
if (node.children) {
for (const child of node.children) {
output += formatDirectoryStructure(child, depth + 1);
}
}
return output;
}
function scan(dirPath, baseDir) {
const name = path.basename(dirPath);
if (IGNORED_DIRS.has(name)) {
return null;
}
try {
const stats = fs.statSync(dirPath);
const relativePath = path.relative(baseDir, dirPath).replace(/\\/g, "/");
if (stats.isDirectory()) {
const children = fs.readdirSync(dirPath).map((child) => {
const childPath = path.join(dirPath, child);
return scan(childPath, baseDir);
}).filter((child) => child !== null);
if (children.length === 0 && dirPath !== baseDir) {
}
return {
path: relativePath,
type: "directory",
name,
children
};
} else if (stats.isFile()) {
const ext = path.extname(name);
if (IGNORED_EXTENSIONS.has(ext)) {
return null;
}
return {
path: relativePath,
type: "file",
name
};
}
} catch (error) {
if (error.code === "ENOENT" || error.code === "EACCES" || error.code === "EPERM") {
return null;
}
throw error;
}
return null;
}
function scanDirectoryStructure(dirPath) {
const baseDir = dirPath;
return scan(dirPath, baseDir);
}
// src/generateCodeFile.ts
var import_ai = require("ai");
async function generateCodeFile(options) {
const {
model,
systemPrompt,
userPrompt,
...args
} = options;
const finalSystemPrompt = `${systemPrompt}
CRITICAL: You MUST respond with ONLY the raw code content for the file. Do not include any markdown, explanations, or any text other than the code itself.`;
const { text } = await (0, import_ai.generateText)({
model,
system: finalSystemPrompt,
prompt: userPrompt,
...args
});
const cleanedText = text.replace(/^```[a-zA-Z]*\n/, "").replace(/\n```$/, "");
return cleanedText;
}
// src/generateCode.v0.ts
var import_ai2 = require("ai");
var import_zod2 = require("zod");
var path2 = __toESM(require("path"));
var import_google = require("@ai-sdk/google");
async function generateCodeV0(options) {
const {
model,
submodel,
systemPrompt,
subsystemPrompt,
userPrompt,
subuserPrompt,
dir,
verbose = false,
paramsSchema,
preparePrompt,
prepareSystemPrompt,
onFileSubmit
} = options;
const log = (message, data) => verbose && console.log(`[generateCodeV0] ${message}`, data || "");
log("Phase 1: Planning...");
const planningModel = submodel || model;
const dirStructure = dir ? scanDirectoryStructure(dir) : void 0;
const planningSystemPrompt = subsystemPrompt || `You are a master software architect. Your job is to analyze a user's request and create a detailed plan for a single file. You must also extract any parameters from the request.`;
const planningUserPrompt = subuserPrompt || `<user_instruction>
${userPrompt}
</user_instruction>
${dirStructure ? `<directory_structure>
${formatDirectoryStructure(dirStructure)}
</directory_structure>` : ""}
Create a file plan and extract parameters.`;
const PlanWithParamsSchema = paramsSchema ? import_zod2.z.object({
plan: FilePlanSchema,
params: paramsSchema
}) : import_zod2.z.object({
plan: FilePlanSchema
});
const { object } = await (0, import_ai2.generateObject)({
model: (0, import_google.google)("gemini-2.0-flash-001"),
system: planningSystemPrompt,
prompt: planningUserPrompt,
schema: PlanWithParamsSchema
});
const { plan, params } = object;
log("Planning successful.", { plan });
log("Phase 2: Generating File...");
const fullFileName = `${plan.fileName}.${plan.fileExtension}`;
const generationSystemPrompt = params ? prepareSystemPrompt ? await prepareSystemPrompt({
filePlan: plan,
planParams: params,
systemPrompt,
userPrompt,
dirStructure: dirStructure || void 0
}) : systemPrompt : systemPrompt;
const defaultUserPrompt = `Generate the code for the file "${fullFileName}".
**User Instructions:**
${userPrompt}
**Code Brief:**
${plan.codeBrief}
`;
const generationUserPrompt = params ? preparePrompt ? await preparePrompt({
filePlan: plan,
planParams: params,
systemPrompt,
userPrompt,
dirStructure: dirStructure || void 0
}) : defaultUserPrompt : defaultUserPrompt;
const code = await generateCodeFile({
...options,
model,
systemPrompt: generationSystemPrompt,
userPrompt: generationUserPrompt
});
const finalPath = path2.join(dir || "", plan.path, fullFileName);
await onFileSubmit(finalPath, code);
log(`File "${finalPath}" submitted successfully.`);
}
// src/generateCode.v2.ts
var import_ai3 = require("ai");
var import_zod3 = require("zod");
var path3 = __toESM(require("path"));
async function generateCodeV2(options) {
const {
model,
submodel,
systemPrompt,
subsystemPrompt,
userPrompt,
subuserPrompt,
dir,
verbose = false,
paramsSchema,
prepareSystemPrompt,
prepareChunkPrompt,
onFileSubmit,
onChunkSubmit,
maxChunks = 10,
messages,
...args
} = options;
const log = (message, data) => verbose && console.log(`[generateCodeV2] ${message}`, data || "");
log("Phase 1: Planning...");
const planningModel = submodel || model;
const dirStructure = dir ? scanDirectoryStructure(dir) : void 0;
const planningSystemPrompt = subsystemPrompt || `You are a master software architect. Your job is to analyze a user's request and create a detailed plan for a single file. You must also extract any parameters from the request.`;
const planningUserPrompt = subuserPrompt || `<user_instruction>
${userPrompt}
</user_instruction>
${dirStructure ? `<directory_structure>
${formatDirectoryStructure(dirStructure)}
</directory_structure>` : ""}
Create a plan for a single file and extract parameters. The plan should include fileName, fileExtension, path, and a detailed codeBrief. The 'path' should be relative to the root of the directory, e.g., 'utils' or '.' for the root.`;
const PlanWithParamsSchema = paramsSchema ? import_zod3.z.object({
plan: FilePlanSchema,
params: paramsSchema
}) : import_zod3.z.object({
plan: FilePlanSchema
});
const { object } = await (0, import_ai3.generateObject)({
model: planningModel,
system: planningSystemPrompt,
prompt: planningUserPrompt,
schema: PlanWithParamsSchema
});
const { plan, params } = object;
log("Planning successful.", { plan, params });
log("Phase 2: Generating File in Chunks...");
const fullFileName = `${plan.fileName}.${plan.fileExtension}`;
const generationSystemPrompt = prepareSystemPrompt ? await prepareSystemPrompt({
filePlan: plan,
planParams: params,
systemPrompt,
userPrompt,
dirStructure: dirStructure || void 0
}) : systemPrompt;
let codeSoFar = "";
let currentChunk = 1;
let isDone = false;
let stopGeneration = false;
const stop = () => {
stopGeneration = true;
};
while (currentChunk <= maxChunks && !isDone && !stopGeneration) {
log(`Generating chunk ${currentChunk} of ${maxChunks}...`);
const chunkPrompt = prepareChunkPrompt ? await prepareChunkPrompt({
filePlan: plan,
planParams: params,
systemPrompt,
userPrompt,
dirStructure: dirStructure || void 0,
codeSoFar,
stop
}) : `You are an AI programmer augmenting a code file.
**File Details:**
- **Name:** ${fullFileName}
- **Path:** ${plan.path}
- **User Instructions:** ${userPrompt}
- **Code Brief:** ${plan.codeBrief}
**Current Code:**
\`\`\`
${codeSoFar}
\`\`\`
Your task is to add more code to this file according to the plan. Add the next set of functions or logic as a self-contained block.
Your response should be only the new code to be appended.
If you believe the file is now complete according to the plan, end your response with "[DONE]".
Otherwise, end your response with "[CONTINUE]".
`;
if (stopGeneration) {
log("Generation stopped by prepareChunkPrompt.");
break;
}
const { text: chunk } = await (0, import_ai3.generateText)({
model,
system: generationSystemPrompt,
prompt: chunkPrompt,
...args
});
let processedChunk = chunk;
processedChunk = processedChunk.replace(
/^\s*```(?:typescript|javascript|ts|js)?\s*\n/i,
""
);
processedChunk = processedChunk.replace(/```\s*$/, "");
const doneMatch = /\[DONE\]\s*$/.exec(processedChunk);
const continueMatch = /\[CONTINUE\]\s*$/.exec(processedChunk);
if (doneMatch) {
isDone = true;
processedChunk = processedChunk.substring(0, doneMatch.index);
} else if (continueMatch) {
processedChunk = processedChunk.substring(0, continueMatch.index);
}
if (onChunkSubmit) {
await onChunkSubmit({
chunk: processedChunk,
filePlan: plan,
planParams: params,
stop
});
}
codeSoFar += processedChunk;
if (stopGeneration) {
log("Generation stopped by onChunkSubmit or prepareChunkPrompt.");
break;
}
currentChunk++;
}
if (!isDone && !stopGeneration) {
log("Max chunks reached without completion. Submitting partial file.");
}
const finalPath = path3.join(dir || "", plan.path, fullFileName);
await onFileSubmit(finalPath, codeSoFar);
log(`File "${finalPath}" submitted successfully.`);
}
// src/generateProject.ts
var import_ai4 = require("ai");
var import_zod4 = require("zod");
var path4 = __toESM(require("path"));
function topologicalSort(files) {
const sorted = [];
const visited = /* @__PURE__ */ new Set();
const fullyVisited = /* @__PURE__ */ new Set();
const fileMap = new Map(
files.map((f) => [`${f.path}/${f.fileName}.${f.fileExtension}`, f])
);
function visit(file) {
const fullFileName = `${file.path}/${file.fileName}.${file.fileExtension}`;
if (fullyVisited.has(fullFileName)) return;
if (visited.has(fullFileName))
throw new Error(`Circular dependency detected: ${fullFileName}`);
visited.add(fullFileName);
if (file.dependencies) {
for (const depName of file.dependencies) {
const dependency = fileMap.get(depName);
if (dependency) {
visit(dependency);
}
}
}
visited.delete(fullFileName);
fullyVisited.add(fullFileName);
sorted.push(file);
}
for (const file of files) {
const fullFileName = `${file.path}/${file.fileName}.${file.fileExtension}`;
if (!fullyVisited.has(fullFileName)) {
visit(file);
}
}
return sorted;
}
async function generateProject(options) {
const {
model,
submodel,
systemPrompt,
subsystemPrompt,
userPrompt,
subuserPrompt,
dir,
verbose = false,
paramsSchema,
writeFile,
...args
} = options;
const log = (message, data) => verbose && console.log(`[generateProject] ${message}`, data || "");
log("Phase 1: Starting Project Planning...");
const planningModel = submodel || model;
const dirStructure = dir ? scanDirectoryStructure(dir) : void 0;
const planningSystemPrompt = subsystemPrompt || `You are a world-class Principal Software Architect. Your task is to analyze a user's request and create a detailed project plan, breaking it down into a set of files with dependencies. You must also extract any parameters from the request that match the provided schema.`;
const planningUserPrompt = subuserPrompt || `Please generate a detailed project plan based on the following instruction.
<user_instruction>
${userPrompt}
</user_instruction>
${dirStructure ? `<directory_structure>
${formatDirectoryStructure(dirStructure)}
</directory_structure>` : ""}`;
const ProjectPlanSchema = paramsSchema ? import_zod4.z.object({
files: import_zod4.z.array(FilePlanSchema),
params: paramsSchema
}) : import_zod4.z.object({
files: import_zod4.z.array(FilePlanSchema)
});
const { object } = await (0, import_ai4.generateObject)({
model: planningModel,
system: planningSystemPrompt,
prompt: planningUserPrompt,
schema: ProjectPlanSchema
});
const { files: plannedFiles, params } = object;
const sortedFiles = topologicalSort(plannedFiles);
log("Project planning complete.", { fileCount: sortedFiles.length });
log("Phase 2: Starting File Generation...");
const generatedFiles = /* @__PURE__ */ new Map();
for (const filePlan of sortedFiles) {
const fullFileName = `${filePlan.fileName}.${filePlan.fileExtension}`;
const filePath = path4.join(filePlan.path, fullFileName);
log(`Generating file: ${filePath}`);
const fileUserPrompt = `Generate the code for the file "${filePath}".
**File-specific Instructions:** ${filePlan.codeBrief}
**Overall Project Goal:** ${userPrompt}
**Dependencies (already generated):**
${(filePlan.dependencies || []).map((dep) => `// ${dep}
${generatedFiles.get(dep) || ""}`).join("\n\n")}
`;
const code = await generateCodeFile({
model,
systemPrompt,
userPrompt: fileUserPrompt,
...args
});
const finalPath = path4.join(dir || "", filePath);
await writeFile(finalPath, code);
generatedFiles.set(finalPath, code);
log(`File generated and submitted: ${finalPath}`);
}
return {
plan: { files: plannedFiles, params: params ?? null },
generatedFiles
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FilePlanSchema,
formatDirectoryStructure,
generateCodeFile,
generateCodeV0,
generateCodeV2,
generateProject,
scanDirectoryStructure
});
//# sourceMappingURL=index.js.map