import { CairoCodeGenerationResponse } from '../types/index.js';
import { generateCairoCodeSchema } from '../schema/schema.js';
import axios from 'axios';
import fs from 'fs';
import fsPromises from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { z } from 'zod';
import { resolveContractPath } from './path.js';
// Get current file's directory (ES module equivalent of __dirname)
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/**
* Validates the input parameters for code generation
* @param params The parameters to validate
* @throws Error if parameters are invalid
*/
export function validateParams(
params: z.infer<typeof generateCairoCodeSchema>
): void {
if (!params?.prompt) {
throw new Error('Prompt is required for generating Cairo code');
}
if (!params?.programName || !params.programName.endsWith('.cairo')) {
throw new Error('Program name is required and must end with .cairo');
}
}
/**
* Calls the Cairo code generation API
* @param prompt The prompt to send to the API
* @returns The content generated by the API
* @throws Error if API call fails or returns an error
*/
export async function callCairoGenerationAPI(prompt: string): Promise<string> {
const apiUrl = process.env.CAIRO_GENERATION_API_URL as string;
if (!apiUrl) {
throw new Error('CAIRO_GENERATION_API_URL is not set');
}
const response = await axios.post<CairoCodeGenerationResponse>(
apiUrl,
{
model: 'gemini-2.0-flash',
messages: [
{
role: 'system',
content:
'You are a Cairo programming expert. Generate Cairo code that follows best practices.',
},
{
role: 'user',
content: `${prompt}
Avoid using the println! function.
`,
},
],
temperature: 0.7,
},
{
headers: {
'Content-Type': 'application/json',
},
}
);
if (response.data.error) {
throw new Error(
`API Error: ${response.data.error.message || 'Unknown error'}`
);
}
const generatedContent = response.data.choices?.[0]?.message?.content;
if (!generatedContent) {
throw new Error('No content was generated from the API');
}
return generatedContent;
}
/**
* Extracts Cairo code from the generated content
* @param generatedContent The raw content returned by the API
* @returns The extracted Cairo code
*/
export function extractCairoCode(generatedContent: string): string {
const cairoCodePattern = /```cairo\s*([\s\S]*?)```/;
const match = generatedContent.match(cairoCodePattern);
if (match && match[1]) {
return match[1].trim();
} else {
throw new Error(
'API did not return Cairo code: ' +
generatedContent.substring(0, 100) +
'...'
);
}
}
/**
* Saves Cairo code to a local file for debugging
* @param contractName The name of the contract
* @param cairoCode The Cairo code to save
* @returns The path to the saved file
*/
export function saveToDebugFile(
contractName: string,
cairoCode: string
): string {
const debugDir = path.join(__dirname, '../..', 'contract');
if (!fs.existsSync(debugDir)) {
fs.mkdirSync(debugDir, { recursive: true });
}
const debugFile = path.join(debugDir, contractName);
fs.writeFileSync(debugFile, cairoCode);
return debugFile;
}
/**
* Encodes Cairo source code for database storage
* @param code The source code to encode
* @returns The encoded source code
*/
export function encodeSourceCode(code: string): string {
return code.replace(/\0/g, '');
}
/**
* Extracts the source code from a contract file
* @param sourcePath The path to the contract file
* @returns The source code of the contract file
*/
export async function extractFile(sourcePath: string): Promise<string> {
const resolvedPath = await resolveContractPath(sourcePath);
const sourceCode = await fsPromises.readFile(resolvedPath, 'utf-8');
return sourceCode;
}