All files / src/ai/providers claude.ts

96.87% Statements 62/64
88.88% Branches 8/9
100% Functions 2/2
96.87% Lines 62/64

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 652x 2x 2x 2x 2x 2x 2x 2x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 1x 1x 1x 1x     2x 2x 2x 2x 2x 2x 2x 3x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 2x  
import { AIProvider, ChatOptions, EmptyContentError } from './base';
import { t } from '../../i18n/messages';
 
export class ClaudeProvider extends AIProvider {
  id = 'claude';
  name = 'Anthropic Claude';
 
  async chat(systemPrompt: string, userPrompt: string, options: ChatOptions): Promise<string> {
    const url = `${this.baseUrl}/messages`;
 
    const body = JSON.stringify({
      model: options.model,
      max_tokens: options.maxTokens,
      temperature: options.temperature,
      system: systemPrompt,
      messages: [
        { role: 'user', content: userPrompt },
      ],
    });
 
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
 
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': this.apiKey,
          'anthropic-version': '2023-06-01',
        },
        body,
        signal: controller.signal,
      });
 
      if (!response.ok) {
        const errorText = await response.text();
        if (response.status === 401) {
          throw new Error('API Key 无效,请重新设置');
        }
        throw new Error(`Claude API 请求失败 (${response.status}): ${errorText}`);
      }
 
      const data = await response.json() as {
        content?: Array<{ text?: string }>;
        stop_reason?: string;
        error?: { message?: string };
      };
 
      const text = data.content?.[0]?.text;
      if (text === undefined || text === null || text.trim() === '') {
        const parts = [`stop_reason=${data.stop_reason ?? 'unknown'}`];
        if (data.error?.message) {
          parts.push(data.error.message);
        }
        throw new EmptyContentError(t('adapter.emptyContent', { 0: parts.join(', ') }));
      }
 
      return text;
    } finally {
      clearTimeout(timeout);
    }
  }
}