C# core 给Claude Code CLI 用上DeepSeek

Claude 太贵了,直接使用DeepSeek接口,懒人必备。

搜狗高速浏览器截图20260415162636

 

using System.Net.Http.Headers;
using System.Text.Json;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient("deepseek", client =>
{
    client.Timeout = TimeSpan.FromMinutes(10);
});

var app = builder.Build();

// ── API Key 读取 ──────────────────────────────────────────────────────────────
var deepSeekKey = Environment.GetEnvironmentVariable("DEEPSEEK_API_KEY");
var keyConfigPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "key.config");
if (string.IsNullOrWhiteSpace(deepSeekKey) && File.Exists(keyConfigPath))
{
    deepSeekKey = File.ReadAllText(keyConfigPath).Trim();
    Console.WriteLine($"[INFO] 已从 key.config 读取 API Key");
}
if (string.IsNullOrWhiteSpace(deepSeekKey))
    Console.WriteLine("[WARN] 未设置 DEEPSEEK_API_KEY");

const string DeepSeekAnthropicBase = "https://api.deepseek.com/anthropic";

app.MapMethods("/", ["HEAD"], () => Results.Ok());
app.MapGet("/", () => Results.Ok(new { status = "running" }));

// count_tokens — DeepSeek 不支持,返回假数据
app.MapPost("/v1/messages/count_tokens", () =>
    Results.Json(new { input_tokens = 1000 }));

app.Use(async (ctx, next) => { ctx.Request.EnableBuffering(); await next(); });

app.Map("/{**path}", async (HttpContext context, IHttpClientFactory factory) =>
{
    var path = context.Request.Path.Value ?? "";
    var targetUrl = DeepSeekAnthropicBase + path;
    if (context.Request.QueryString.HasValue)
        targetUrl += context.Request.QueryString.Value;

    Console.WriteLine($"[->] {context.Request.Method} {path}");

    // 读取 Body,限制 max_tokens 不超过 8192
    string requestBody = "";
    if (context.Request.ContentLength > 0 || context.Request.Headers.ContainsKey("Transfer-Encoding"))
    {
        using var bodyReader = new StreamReader(context.Request.Body, leaveOpen: true);
        requestBody = await bodyReader.ReadToEndAsync();
        context.Request.Body.Position = 0;

        try
        {
            var doc = JsonDocument.Parse(requestBody);
            if (doc.RootElement.TryGetProperty("max_tokens", out var mt) && mt.GetInt32() > 8192)
            {
                using var ms = new System.IO.MemoryStream();
                using var writer = new Utf8JsonWriter(ms);
                writer.WriteStartObject();
                foreach (var prop in doc.RootElement.EnumerateObject())
                {
                    if (prop.Name == "max_tokens") writer.WriteNumber("max_tokens", 8192);
                    else prop.WriteTo(writer);
                }
                writer.WriteEndObject();
                writer.Flush();
                requestBody = System.Text.Encoding.UTF8.GetString(ms.ToArray());
            }
        }
        catch { }
    }

    using var proxyReq = new HttpRequestMessage(new HttpMethod(context.Request.Method), targetUrl);
    proxyReq.Headers.TryAddWithoutValidation("x-api-key", deepSeekKey);
    proxyReq.Headers.TryAddWithoutValidation("anthropic-version", "2023-06-01");

    var skipHeaders = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        { "Host", "x-api-key", "Authorization", "Content-Length", "Transfer-Encoding" };
    foreach (var (key, value) in context.Request.Headers)
    {
        if (skipHeaders.Contains(key)) continue;
        proxyReq.Headers.TryAddWithoutValidation(key, value.ToArray());
    }

    if (!string.IsNullOrEmpty(requestBody))
    {
        proxyReq.Content = new StringContent(requestBody, System.Text.Encoding.UTF8);
        if (context.Request.ContentType is { } ct)
            proxyReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(ct);
    }

    var http = factory.CreateClient("deepseek");
    using var proxyResp = await http.SendAsync(
        proxyReq, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted);

    Console.WriteLine($"[<-] {(int)proxyResp.StatusCode} {path}");

    context.Response.StatusCode = (int)proxyResp.StatusCode;

    var skipRespHeaders = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        { "Transfer-Encoding", "Content-Length", "Content-Encoding" };
    foreach (var (key, value) in proxyResp.Headers)
    {
        if (skipRespHeaders.Contains(key)) continue;
        context.Response.Headers[key] = value.ToArray();
    }
    foreach (var (key, value) in proxyResp.Content.Headers)
    {
        if (skipRespHeaders.Contains(key)) continue;
        context.Response.Headers[key] = value.ToArray();
    }

    context.Response.Headers["X-Accel-Buffering"] = "no";
    context.Features.Get<Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature>()
        ?.DisableBuffering();

    await using var respStream = await proxyResp.Content.ReadAsStreamAsync(context.RequestAborted);
    using var lineReader = new StreamReader(respStream, System.Text.Encoding.UTF8);
    while (!lineReader.EndOfStream && !context.RequestAborted.IsCancellationRequested)
    {
        var line = await lineReader.ReadLineAsync();
        if (line is null) break;
        await context.Response.WriteAsync(line + "\n", context.RequestAborted);
        await context.Response.Body.FlushAsync(context.RequestAborted);
    }
});

app.Run("http://localhost:8082");

  

posted @ 2026-04-15 16:28  懒人境界  阅读(17)  评论(0)    收藏  举报