All files / src/ai/providers base.ts

100% Statements 59/59
100% Branches 17/17
100% Functions 6/6
100% Lines 59/59

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 602x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 21x 21x 21x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 11x 11x 11x 2x 2x 2x 2x 2x 2x 21x 21x 21x 21x 2x 2x 26x 26x 5x 5x 26x 15x 1x 1x 15x 15x 26x 2x 2x 26x 2x 2x 2x 2x  
export interface ChatOptions {
  model: string;
  temperature: number;
  maxTokens: number;
  timeoutMs: number;
  seed?: number;
}
 
export abstract class AIProvider {
  abstract id: string;
  abstract name: string;
 
  constructor(
    protected apiKey: string,
    protected baseUrl: string
  ) {}
 
  abstract chat(
    systemPrompt: string,
    userPrompt: string,
    options: ChatOptions
  ): Promise<string>;
}
 
export class EmptyContentError extends Error {
  constructor(detail: string) {
    super(detail);
    this.name = 'EmptyContentError';
  }
}
 
export class ApiRequestError extends Error {
  readonly status?: number;
 
  constructor(message: string, status?: number) {
    super(message);
    this.name = 'ApiRequestError';
    this.status = status;
  }
}
 
export function isRetryableError(err: unknown): boolean {
  if (err instanceof EmptyContentError) {
    return true;
  }
  if (err instanceof ApiRequestError) {
    if (err.status === undefined) {
      return false;
    }
    return err.status === 429 || err.status >= 500;
  }
  if (err instanceof DOMException && err.name === 'AbortError') {
    return true;
  }
  if (err instanceof TypeError) {
    return true;
  }
  return false;
}