[AI] Route AI calls through the Xray/V2Ray proxy (reach OpenAI from Iran)
Add AiUseProxy setting + a toggle in the AI settings section. ScrapeHttpClients.ForAi(settings) returns a proxied HttpClient (reusing IngestProxyUrl, 100s timeout) when AiUseProxy is on, otherwise direct; AI-cache keys are protected from the scrape-client cleanup. OpenAiCompatibleAuditor now uses it, so the AI auditor (e.g. api.openai.com) is reachable through the same Xray sidecar that serves Telegram. Migration adds the column. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -30,12 +30,12 @@ public interface IAiAuditor
|
||||
/// </summary>
|
||||
public class OpenAiCompatibleAuditor : IAiAuditor
|
||||
{
|
||||
private readonly IHttpClientFactory _http;
|
||||
private readonly ScrapeHttpClients _clients;
|
||||
private readonly ILogger<OpenAiCompatibleAuditor> _log;
|
||||
|
||||
public OpenAiCompatibleAuditor(IHttpClientFactory http, ILogger<OpenAiCompatibleAuditor> log)
|
||||
public OpenAiCompatibleAuditor(ScrapeHttpClients clients, ILogger<OpenAiCompatibleAuditor> log)
|
||||
{
|
||||
_http = http;
|
||||
_clients = clients;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
@@ -57,8 +57,7 @@ public class OpenAiCompatibleAuditor : IAiAuditor
|
||||
},
|
||||
};
|
||||
|
||||
var client = _http.CreateClient("ai");
|
||||
client.Timeout = TimeSpan.FromSeconds(30);
|
||||
var client = _clients.ForAi(s); // proxy-aware when AiUseProxy is on (e.g. OpenAI from Iran)
|
||||
using var req = new HttpRequestMessage(HttpMethod.Post, s.AiEndpoint)
|
||||
{
|
||||
Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"),
|
||||
|
||||
@@ -27,14 +27,29 @@ public sealed class ScrapeHttpClients : IDisposable
|
||||
? s.IngestProxyUrl.Trim()
|
||||
: "direct";
|
||||
|
||||
// Drop stale clients if the proxy URL changed (keep only "direct" + the current proxy).
|
||||
// Drop stale clients if the proxy URL changed (keep "direct", current proxy, and AI clients).
|
||||
foreach (var k in _cache.Keys)
|
||||
if (k != "direct" && k != key && _cache.TryRemove(k, out var stale))
|
||||
if (k != "direct" && k != key && !k.StartsWith("ai:") && _cache.TryRemove(k, out var stale))
|
||||
stale.Dispose();
|
||||
|
||||
return _cache.GetOrAdd(key, Build);
|
||||
}
|
||||
|
||||
/// <summary>HttpClient for AI calls — routed through the proxy when AiUseProxy is on (e.g. to
|
||||
/// reach api.openai.com from Iran). Longer timeout; cached per proxy URL.</summary>
|
||||
public HttpClient ForAi(AppSetting s)
|
||||
{
|
||||
var useProxy = s.AiUseProxy && !string.IsNullOrWhiteSpace(s.IngestProxyUrl);
|
||||
var url = useProxy ? s.IngestProxyUrl!.Trim() : null;
|
||||
var key = "ai:" + (url ?? "direct");
|
||||
return _cache.GetOrAdd(key, _ =>
|
||||
{
|
||||
var handler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All };
|
||||
if (url is not null) { handler.Proxy = new WebProxy(url); handler.UseProxy = true; }
|
||||
return new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(100) }; // LLMs can be slow
|
||||
});
|
||||
}
|
||||
|
||||
private static HttpClient Build(string key)
|
||||
{
|
||||
var handler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All };
|
||||
|
||||
@@ -34,6 +34,7 @@ public class SettingsService
|
||||
s.AiSystemPrompt = string.IsNullOrWhiteSpace(incoming.AiSystemPrompt)
|
||||
? AppSetting.DefaultPrompt : incoming.AiSystemPrompt;
|
||||
s.AiAutoApprove = incoming.AiAutoApprove;
|
||||
s.AiUseProxy = incoming.AiUseProxy;
|
||||
// Channel scraping sources
|
||||
s.AutoIngestEnabled = incoming.AutoIngestEnabled;
|
||||
s.IngestIntervalMinutes = Math.Max(1, incoming.IngestIntervalMinutes);
|
||||
|
||||
Reference in New Issue
Block a user