UNPKG

@microfox/ai-code

Version:

A TypeScript SDK for Ai Code.

478 lines (461 loc) 16.9 kB
"use strict"; 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