All files / core/ai-providers openai.ts

0% Statements 0/36
0% Branches 0/7
0% Functions 0/7
0% Lines 0/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84                                                                                                                                                                       
// Copyright 2026 ForgeKit Contributors
// SPDX-License-Identifier: Apache-2.0
// https://github.com/SubhanshuMG/ForgeKit
import * as https from 'https';
import { AIProvider, ProjectSpec, Template } from '../../types';
import { buildSystemPrompt, parseAIResponse } from './shared';
 
export class OpenAIProvider implements AIProvider {
  name = 'openai';
  private apiKey: string;
  private model: string;
 
  constructor(apiKey: string, model?: string) {
    this.apiKey = apiKey;
    this.model = model || 'gpt-4o';
  }
 
  async generateProjectSpec(description: string, templates: Template[]): Promise<ProjectSpec> {
    const systemPrompt = buildSystemPrompt(templates);
 
    const payload = JSON.stringify({
      model: this.model,
      messages: [
        { role: 'system', content: systemPrompt },
        { role: 'user', content: description },
      ],
      temperature: 0.3,
      max_tokens: 500,
    });
 
    const response = await this.callAPI(payload);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const content = (response as any).choices?.[0]?.message?.content;
    Iif (!content) {
      throw new Error('OpenAI returned an empty response. Please try again.');
    }
    return parseAIResponse(content, templates);
  }
 
  private callAPI(payload: string): Promise<Record<string, unknown>> {
    return new Promise((resolve, reject) => {
      const req = https.request(
        {
          hostname: 'api.openai.com',
          path: '/v1/chat/completions',
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${this.apiKey}`,
            'Content-Length': Buffer.byteLength(payload),
          },
        },
        (res) => {
          const chunks: Buffer[] = [];
          res.on('data', (chunk: Buffer) => chunks.push(chunk));
          res.on('end', () => {
            const body = Buffer.concat(chunks).toString('utf-8');
            Iif (res.statusCode && res.statusCode >= 400) {
              let message = `OpenAI API error (HTTP ${res.statusCode})`;
              try {
                const parsed = JSON.parse(body);
                Iif (parsed.error?.message) message = parsed.error.message;
              } catch { /* use default message */ }
              reject(new Error(message));
              return;
            }
            try {
              resolve(JSON.parse(body));
            } catch {
              reject(new Error('Failed to parse OpenAI response'));
            }
          });
          res.on('error', reject);
        }
      );
      req.on('error', reject);
      // Validate payload is well-formed JSON before sending to external API
      JSON.parse(payload);
      req.write(payload);
      req.end();
    });
  }
}