Forge
csharp4405de34
1using System.Net.Http.Json;
2using System.Text.Json;
3using System.Runtime.CompilerServices;
4
5namespace CascadeIDE.Services;
6
7/// <summary>Провайдер чата через Anthropic API (Claude).</summary>
8public sealed class AnthropicProvider : IAiChatProvider
9{
10 private const string DefaultBaseUrl = "https://api.anthropic.com";
11 private readonly HttpClient _httpClient;
12 private readonly string _apiKey;
13 private readonly string _modelId;
14
15 public AnthropicProvider(string apiKey, string modelId, string? baseUrl = null)
16 {
17 _apiKey = apiKey ?? "";
18 _modelId = modelId ?? "claude-sonnet-4-20250514";
19 var baseUri = new Uri(baseUrl ?? DefaultBaseUrl);
20 _httpClient = new HttpClient { BaseAddress = baseUri };
21 _httpClient.DefaultRequestHeaders.Add("x-api-key", _apiKey);
22 _httpClient.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");
23 }
24
25 public async IAsyncEnumerable<string> StreamChatAsync(
26 string model,
27 IReadOnlyList<ChatMessage> messages,
28 [EnumeratorCancellation] CancellationToken cancellationToken = default,
29 ChatTurnUsageCollector? usageCollector = null)
30 {
31 var modelId = string.IsNullOrEmpty(model) ? _modelId : model;
32 if (string.IsNullOrEmpty(_apiKey))
33 {
34 yield return "[Error: Anthropic API key not set.]";
35 yield break;
36 }
37
38 var requestBody = new
39 {
40 model = modelId,
41 max_tokens = 8192,
42 stream = true,
43 messages = messages.Select(m => new { role = m.Role == "assistant" ? "assistant" : "user", content = m.Content }).ToArray()
44 };
45
46 using var request = new HttpRequestMessage(HttpMethod.Post, "v1/messages");
47 request.Content = JsonContent.Create(requestBody);
48
49 using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
50 if (!response.IsSuccessStatusCode)
51 {
52 var err = await response.Content.ReadAsStringAsync(cancellationToken);
53 yield return $"[Anthropic error {(int)response.StatusCode}: {err}]";
54 yield break;
55 }
56
57 await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
58 using var reader = new StreamReader(stream);
59 while (await reader.ReadLineAsync(cancellationToken) is { } line)
60 {
61 if (!line.StartsWith("data:", StringComparison.Ordinal)) continue;
62 var json = line.Length > 5 ? line[5..].Trim() : "";
63 if (json == "[DONE]" || string.IsNullOrEmpty(json)) continue;
64 string? text = null;
65 try
66 {
67 using var doc = JsonDocument.Parse(json);
68 var root = doc.RootElement;
69 if (root.TryGetProperty("type", out var typeEl) && typeEl.GetString() == "content_block_delta"
70 && root.TryGetProperty("delta", out var deltaEl)
71 && deltaEl.TryGetProperty("type", out var deltaType) && deltaType.GetString() == "text_delta"
72 && deltaEl.TryGetProperty("text", out var textEl))
73 text = textEl.GetString();
74 }
75 catch
76 {
77 // skip invalid line
78 }
79 if (!string.IsNullOrEmpty(text))
80 yield return text;
81 }
82 }
83}
84
View only · write via MCP/CIDE