import OpenAI from 'openai'; import { startSpan } from '@/remote/monitor'; import { strings } from '@/common/strings'; import { AIProvider } from './types'; const TIMEOUT_MS = 30000; const MODEL = 'gpt-5.2'; function withTimeout(promise: Promise, ms: number): Promise { return Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new Error(strings.addLog.detection.requestTimeout)), ms) ), ]); } export class OpenAIProvider implements AIProvider { private openai: OpenAI; constructor() { this.openai = new OpenAI({ apiKey: process.env.EXPO_PUBLIC_OPENAI_API_KEY || '', dangerouslyAllowBrowser: true, }); } async query(prompt: string, base64?: string): Promise { return startSpan({ name: 'ai.openai', op: 'ai.inference' }, async span => { const start = Date.now(); const userContent: OpenAI.Chat.Completions.ChatCompletionContentPart[] = [ { type: 'text', text: prompt }, ]; if (base64) { userContent.push({ type: 'image_url', image_url: { url: `data:image/jpeg;base64,${base64}` }, }); } const completion = await withTimeout( this.openai.chat.completions.create({ model: MODEL, messages: [{ role: 'user', content: userContent }], response_format: { type: 'json_object' }, temperature: 0.3, }), TIMEOUT_MS ); span.setAttributes({ 'ai.model': MODEL, 'ai.input_tokens': completion.usage?.prompt_tokens ?? 0, 'ai.output_tokens': completion.usage?.completion_tokens ?? 0, 'ai.latency_ms': Date.now() - start, 'ai.has_image': !!base64, }); const content = completion.choices[0].message.content; if (!content) throw new Error(strings.addLog.detection.noResponse); return content; }); } }