C# core 给Claude Code CLI 用上DeepSeek
Claude 太贵了,直接使用DeepSeek接口,懒人必备。

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");

浙公网安备 33010602011771号