Initial commit: ClawdDotNet
Import des bestehenden Projektstands in Git. - .NET 10 WinForms Anwendung (Multi-Agent / Tool-System) - .gitignore fuer Build-Artefakte, Secrets und Runtime-Daten ergaenzt Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using ClawdDotNet.Core.Api.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ClawdDotNet.Core.Api;
|
||||
|
||||
public sealed class OpenRouterClient : IDisposable
|
||||
{
|
||||
private const string BaseUrl = "https://openrouter.ai/api/v1/";
|
||||
|
||||
private readonly HttpClient _http;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
public OpenRouterClient(string apiKey, ILogger logger, HttpClient? httpClient = null)
|
||||
{
|
||||
_logger = logger;
|
||||
_http = httpClient ?? new HttpClient();
|
||||
_http.BaseAddress = new Uri(BaseUrl);
|
||||
_http.Timeout = TimeSpan.FromMinutes(5); // LLM-Calls können bei großen Prompts lange dauern
|
||||
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
|
||||
_http.DefaultRequestHeaders.Add("HTTP-Referer", "ClawdDotNet");
|
||||
_http.DefaultRequestHeaders.Add("X-Title", "ClawdDotNet");
|
||||
}
|
||||
|
||||
public async Task<ChatResponse> CompleteAsync(ChatRequest request, CancellationToken ct)
|
||||
{
|
||||
request.Stream = false;
|
||||
|
||||
var json = JsonSerializer.Serialize(request, JsonOptions);
|
||||
using var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
_logger.LogDebug("Sending request to OpenRouter: model={Model}, messages={Count}",
|
||||
request.Model, request.Messages.Count);
|
||||
|
||||
using var response = await _http.PostAsync("chat/completions", content, ct);
|
||||
var responseBody = await response.Content.ReadAsStringAsync(ct);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogError("OpenRouter API error {StatusCode}: {Body}",
|
||||
(int)response.StatusCode, responseBody);
|
||||
throw new OpenRouterException(
|
||||
$"API request failed with status {(int)response.StatusCode}",
|
||||
(int)response.StatusCode,
|
||||
responseBody);
|
||||
}
|
||||
|
||||
var result = JsonSerializer.Deserialize<ChatResponse>(responseBody, JsonOptions)
|
||||
?? throw new OpenRouterException("Empty response from OpenRouter", 0, responseBody);
|
||||
|
||||
if (result.Error is not null)
|
||||
{
|
||||
_logger.LogError("OpenRouter returned error: {Error}", result.Error.Message);
|
||||
throw new OpenRouterException(result.Error.Message, result.Error.Code ?? 0, responseBody);
|
||||
}
|
||||
|
||||
_logger.LogDebug("OpenRouter response received: tokens={Tokens}",
|
||||
result.Usage?.TotalTokens ?? 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ruft die verfügbaren Modelle von OpenRouter ab (/models Endpoint).
|
||||
/// Gibt eine Liste von Modell-IDs zurück, sortiert nach Name.
|
||||
/// </summary>
|
||||
public async Task<List<ModelInfo>> GetAvailableModelsAsync(CancellationToken ct = default)
|
||||
{
|
||||
_logger.LogDebug("Fetching available models from OpenRouter...");
|
||||
|
||||
using var response = await _http.GetAsync("models", ct);
|
||||
var body = await response.Content.ReadAsStringAsync(ct);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogError("Failed to fetch models: {StatusCode} {Body}",
|
||||
(int)response.StatusCode, body);
|
||||
return [];
|
||||
}
|
||||
|
||||
var doc = JsonDocument.Parse(body);
|
||||
|
||||
if (!doc.RootElement.TryGetProperty("data", out var dataArray))
|
||||
return [];
|
||||
|
||||
var models = new List<ModelInfo>();
|
||||
|
||||
foreach (var item in dataArray.EnumerateArray())
|
||||
{
|
||||
var id = item.GetProperty("id").GetString() ?? "";
|
||||
var name = item.TryGetProperty("name", out var nameProp) ? nameProp.GetString() ?? id : id;
|
||||
|
||||
models.Add(new ModelInfo { Id = id, Name = name });
|
||||
}
|
||||
|
||||
models.Sort((a, b) => string.Compare(a.Id, b.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
_logger.LogDebug("Fetched {Count} models from OpenRouter", models.Count);
|
||||
return models;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_http.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class OpenRouterException(string message, int statusCode, string responseBody)
|
||||
: Exception(message)
|
||||
{
|
||||
public int StatusCode { get; } = statusCode;
|
||||
public string ResponseBody { get; } = responseBody;
|
||||
}
|
||||
Reference in New Issue
Block a user