All files / src/providers openai.ts

95% Statements 38/40
55% Branches 11/20
100% Functions 11/11
100% Lines 38/38

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 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132    1x             1x         4x   4x 3x 3x   3x 3x 2x 2x   1x 1x       4x                   2x 2x 2x 2x 2x 2x 2x   2x                                                   1x 1x 1x                                     2x 2x 2x       2x 2x 2x 2x 2x       3x 3x             9x       5x       4x       1x    
import type { Batcher, LLMCallRecord, OpenAIClientLike } from '../types.js';
 
const OPENAI_COST_PER_1K: Record<string, { input: number; output: number }> = {
  'gpt-4o': { input: 0.005, output: 0.015 },
  'gpt-4o-mini': { input: 0.00015, output: 0.0006 },
  'gpt-4-turbo': { input: 0.01, output: 0.03 },
  'gpt-3.5-turbo': { input: 0.0005, output: 0.0015 },
};
 
export function wrapOpenAI<TClient extends OpenAIClientLike>(
  client: TClient,
  batcher: Batcher,
  projectId: string | null,
): TClient {
  const originalCreate = client.chat.completions.create.bind(client.chat.completions);
 
  client.chat.completions.create = async (...args: unknown[]): Promise<unknown> => {
    const start = Date.now();
    const timestamp = new Date(start).toISOString();
 
    try {
      const response = await originalCreate(...args);
      pushOpenAISuccess(response, batcher, projectId, timestamp, Date.now() - start);
      return response;
    } catch (error) {
      pushOpenAIError(args[0], error, batcher, projectId, timestamp, Date.now() - start);
      throw error;
    }
  };
 
  return client;
}
 
function pushOpenAISuccess(
  response: unknown,
  batcher: Batcher,
  projectId: string | null,
  timestamp: string,
  latencyMs: number,
): void {
  try {
    const responseRecord = asRecord(response);
    const usage = asRecord(responseRecord?.usage);
    const model = getString(responseRecord?.model) ?? 'unknown';
    const promptTokens = getNumber(usage?.prompt_tokens);
    const completionTokens = getNumber(usage?.completion_tokens);
    const preview = getOpenAIPreview(responseRecord?.choices);
 
    pushSafely(batcher, {
      project_id: projectId,
      timestamp,
      provider: 'openai',
      model,
      prompt_tokens: promptTokens,
      completion_tokens: completionTokens,
      latency_ms: latencyMs,
      cost_usd: computeCost(model, promptTokens, completionTokens),
      response_preview: preview,
      error: null,
      metadata: {},
    });
  } catch {
    // Logging must never affect the provider call.
  }
}
 
function pushOpenAIError(
  request: unknown,
  error: unknown,
  batcher: Batcher,
  projectId: string | null,
  timestamp: string,
  latencyMs: number,
): void {
  try {
    const requestRecord = asRecord(request);
    pushSafely(batcher, {
      project_id: projectId,
      timestamp,
      provider: 'openai',
      model: getString(requestRecord?.model) ?? 'unknown',
      prompt_tokens: 0,
      completion_tokens: 0,
      latency_ms: latencyMs,
      cost_usd: 0,
      response_preview: '',
      error: stringifyError(error),
      metadata: {},
    });
  } catch {
    // Preserve the original provider error exactly.
  }
}
 
function computeCost(model: string, promptTokens: number, completionTokens: number): number {
  const pricing = OPENAI_COST_PER_1K[model];
  Iif (!pricing) return 0;
  return (promptTokens / 1000) * pricing.input + (completionTokens / 1000) * pricing.output;
}
 
function getOpenAIPreview(choices: unknown): string {
  Iif (!Array.isArray(choices)) return '';
  const firstChoice = asRecord(choices[0]);
  const message = asRecord(firstChoice?.message);
  const content = getString(message?.content);
  return (content ?? '').slice(0, 200);
}
 
function pushSafely(batcher: Batcher, record: LLMCallRecord): void {
  try {
    batcher.push(record);
  } catch {
    // Explicitly isolate provider behavior from logging failures.
  }
}
 
function asRecord(value: unknown): Record<string, unknown> | undefined {
  return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : undefined;
}
 
function getString(value: unknown): string | undefined {
  return typeof value === 'string' ? value : undefined;
}
 
function getNumber(value: unknown): number {
  return typeof value === 'number' && Number.isFinite(value) ? value : 0;
}
 
function stringifyError(error: unknown): string {
  return error instanceof Error ? error.message : String(error);
}