1
0

feat: Introduce a pluggable LLM provider system with token extraction, pricing, and updated setup configuration.

This commit is contained in:
2026-03-05 22:02:22 +01:00
parent 4476cc7f15
commit c7e7976d9d
12 changed files with 499 additions and 28 deletions

View File

@@ -1,4 +1,5 @@
using System.ClientModel;
using AnchorCli.Providers;
using Microsoft.Extensions.AI;
using OpenAI;
using AnchorCli;
@@ -15,10 +16,11 @@ if (args.Length > 0 && args[0].Equals("setup", StringComparison.OrdinalIgnoreCas
}
// ── Config ──────────────────────────────────────────────────────────────
const string endpoint = "https://openrouter.ai/api/v1";
var cfg = AnchorConfig.Load();
string apiKey = cfg.ApiKey;
string model = cfg.Model;
string provider = cfg.Provider ?? "openrouter";
string endpoint = cfg.Endpoint ?? "https://openrouter.ai/api/v1";
if (string.IsNullOrWhiteSpace(apiKey))
{
@@ -26,28 +28,33 @@ if (string.IsNullOrWhiteSpace(apiKey))
return;
}
// ── Fetch model pricing from OpenRouter ─────────────────────────────────
var pricingProvider = new PricingProvider();
var tokenTracker = new TokenTracker();
// ── Create token extractor for this provider ───────────────────────────
var tokenExtractor = ProviderFactory.CreateTokenExtractorForEndpoint(endpoint);
var tokenTracker = new TokenTracker { Provider = tokenExtractor.ProviderName };
// ── Fetch model pricing (only for supported providers) ─────────────────
ModelInfo? modelInfo = null;
await AnsiConsole.Status()
.Spinner(Spinner.Known.BouncingBar)
.SpinnerStyle(Style.Parse("cornflowerblue"))
.StartAsync("Fetching model pricing...", async ctx =>
{
try
if (ProviderFactory.IsOpenRouter(endpoint))
{
await AnsiConsole.Status()
.Spinner(Spinner.Known.BouncingBar)
.SpinnerStyle(Style.Parse("cornflowerblue"))
.StartAsync("Fetching model pricing...", async ctx =>
{
modelInfo = await pricingProvider.GetModelInfoAsync(model);
if (modelInfo?.Pricing != null)
try
{
tokenTracker.InputPrice = PricingProvider.ParsePrice(modelInfo.Pricing.Prompt);
tokenTracker.OutputPrice = PricingProvider.ParsePrice(modelInfo.Pricing.Completion);
tokenTracker.RequestPrice = PricingProvider.ParsePrice(modelInfo.Pricing.Request);
var pricingProvider = new OpenRouterProvider();
modelInfo = await pricingProvider.GetModelInfoAsync(model);
if (modelInfo?.Pricing != null)
{
tokenTracker.InputPrice = PricingProvider.ParsePrice(modelInfo.Pricing.Prompt);
tokenTracker.OutputPrice = PricingProvider.ParsePrice(modelInfo.Pricing.Completion);
tokenTracker.RequestPrice = PricingProvider.ParsePrice(modelInfo.Pricing.Request);
}
}
}
catch { /* pricing is best-effort */ }
});
catch { /* pricing is best-effort */ }
});
}
// ── Pretty header ───────────────────────────────────────────────────────
AnsiConsole.Write(
@@ -68,9 +75,12 @@ var infoTable = new Table()
.AddColumn(new TableColumn("[dim]Value[/]"));
infoTable.AddRow("[grey]Model[/]", $"[cyan]{Markup.Escape(modelInfo?.Name ?? model)}[/]");
infoTable.AddRow("[grey]Endpoint[/]", $"[blue]OpenRouter[/]");
infoTable.AddRow("[grey]Provider[/]", $"[blue]{tokenExtractor.ProviderName}[/]");
infoTable.AddRow("[grey]Endpoint[/]", $"[dim]{endpoint}[/]");
infoTable.AddRow("[grey]CWD[/]", $"[green]{Markup.Escape(Environment.CurrentDirectory)}[/]");
if (modelInfo?.Pricing != null)
if (modelInfo?.Pricing != null)
{
var inM = tokenTracker.InputPrice * 1_000_000m;