Files
MyAgent/MyAgent/Agents/OpenAiModelService.cs
T

86 lines
3.6 KiB
C#

using System.Net.Http.Headers;
using System.Text.Json;
using MyAgent.Storage;
namespace MyAgent.Agents;
public sealed class OpenAiModelService(LocalConfigStore localConfig, ILogger<OpenAiModelService> logger)
{
private readonly LocalConfigStore _localConfig = localConfig;
private readonly ILogger<OpenAiModelService> _logger = logger;
private readonly HttpClient _http = new();
public async Task<OpenAiModelsResponse> ListAsync(string? baseUrl = null, string? apiKey = null, CancellationToken ct = default)
{
var cfg = await _localConfig.ReadAsync(ct);
baseUrl = string.IsNullOrWhiteSpace(baseUrl) ? cfg.OpenAi.BaseUrl : baseUrl;
apiKey = string.IsNullOrWhiteSpace(apiKey) ? cfg.OpenAi.ApiKey : apiKey;
if (string.IsNullOrWhiteSpace(baseUrl))
return new OpenAiModelsResponse { Error = "Base URL is empty." };
baseUrl = baseUrl.TrimEnd('/');
var url = baseUrl + "/models";
try
{
using var resp = await SendModelsAsync(url, apiKey, ct);
var body = await resp.Content.ReadAsStringAsync(ct);
// 兼容 CLI Proxy 根地址:根返回 { endpoints: ["GET /v1/models"] },实际 models 在 /v1/models。
if ((resp.StatusCode == System.Net.HttpStatusCode.NotFound || resp.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed)
&& !baseUrl.EndsWith("/v1", StringComparison.OrdinalIgnoreCase))
{
using var resp2 = await SendModelsAsync(baseUrl + "/v1/models", apiKey, ct);
body = await resp2.Content.ReadAsStringAsync(ct);
if (!resp2.IsSuccessStatusCode)
return new OpenAiModelsResponse { BaseUrl = baseUrl, Error = $"HTTP {(int)resp2.StatusCode}: {body}" };
}
else if (!resp.IsSuccessStatusCode)
{
return new OpenAiModelsResponse { BaseUrl = baseUrl, Error = $"HTTP {(int)resp.StatusCode}: {body}" };
}
using var doc = JsonDocument.Parse(body);
var models = new List<string>();
if (doc.RootElement.TryGetProperty("data", out var data) && data.ValueKind == JsonValueKind.Array)
{
foreach (var item in data.EnumerateArray())
{
if (item.TryGetProperty("id", out var id) && id.ValueKind == JsonValueKind.String)
{
var value = id.GetString();
if (!string.IsNullOrWhiteSpace(value)) models.Add(value);
}
}
}
return new OpenAiModelsResponse
{
BaseUrl = baseUrl,
Models = models.Distinct().OrderBy(x => x).ToList()
};
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_logger.LogWarning(ex, "Failed to list OpenAI-compatible models");
return new OpenAiModelsResponse { BaseUrl = baseUrl, Error = ex.Message };
}
}
private async Task<HttpResponseMessage> SendModelsAsync(string url, string? apiKey, CancellationToken ct)
{
using var req = new HttpRequestMessage(HttpMethod.Get, url);
if (!string.IsNullOrWhiteSpace(apiKey))
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
return await _http.SendAsync(req, ct);
}
}
public sealed class OpenAiModelsResponse
{
public string BaseUrl { get; set; } = "";
public List<string> Models { get; set; } = [];
public string? Error { get; set; }
}