import * as https from 'https';
import * as http from 'http';
import { ChatMessage, AIConfig, AIResponse, ToolDefinition } from '../common/types';
import { OpenAICompatClient } from './openai-compat';
import { AntCompatClient } from './ant-compat';
export class AIService {
private openaiClient: OpenAICompatClient;
private antClient: AntCompatClient;
constructor() {
this.openaiClient = new OpenAICompatClient();
this.antClient = new AntCompatClient();
}
async sendMessage(
messages: ChatMessage[],
config: AIConfig,
tools?: ToolDefinition[],
onChunk?: (content: string) => void
): Promise<AIResponse> {
if (config.provider === 'anthropic') {
return this.antClient.chat(messages, config, tools, onChunk);
}
return this.openaiClient.chat(messages, config, tools, onChunk);
}
async debugRawStream(messages: ChatMessage[], config: AIConfig): Promise<void> {
const url = `${config.baseUrl}/v1/chat/completions`;
const isHttps = url.startsWith('https');
const mod = isHttps ? https : http;
const bodyObj = {
model: config.model,
messages: messages.map(m => {
const msg: Record<string, unknown> = { role: m.role, content: m.content };
if (m.toolCalls && m.toolCalls.length > 0) {
msg.tool_calls = m.toolCalls.map(tc => ({
id: tc.id, type: 'function',
function: { name: tc.name, arguments: tc.arguments }
}));
}
if (m.toolCallId) msg.tool_call_id = m.toolCallId;
return msg;
}),
max_tokens: 100,
temperature: config.temperature,
stream: true
};
return new Promise<void>((resolve) => {
const req = mod.request(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${config.apiKey}`,
'Accept': 'text/event-stream'
}
}, (res) => {
let raw = '';
res.on('data', (chunk: Buffer) => { raw += chunk.toString(); });
res.on('end', () => {
console.log(`[VibeDev-RAW] Status: ${res.statusCode}, URL: ${url}`);
console.log(`[VibeDev-RAW] Raw SSE (${raw.length} chars):`, raw.slice(0, 3000));
if (raw.includes('reason')) {
const reasonLines = raw.split('\n').filter(l => l.includes('reason'));
console.log(`[VibeDev-RAW] Lines with "reason":`, reasonLines.join('\n'));
} else {
console.log(`[VibeDev-RAW] No "reason" found in raw data`);
}
resolve();
});
res.on('error', (e) => { console.error('[VibeDev-RAW] Error:', e.message); resolve(); });
});
req.on('error', (e) => { console.error('[VibeDev-RAW] Req error:', e.message); resolve(); });
req.write(JSON.stringify(bodyObj));
req.end();
});
}
}