using System.ClientModel; using AnchorCli.Commands; using AnchorCli.OpenRouter; using AnchorCli.Providers; using AnchorCli.Tools; using Microsoft.Extensions.AI; using OpenAI; using Spectre.Console; namespace AnchorCli; /// /// Encapsulates application startup logic, including configuration loading, /// API client creation, and component initialization. /// internal sealed class ApplicationStartup { private readonly string[] _args; private AnchorConfig? _config; private ITokenExtractor? _tokenExtractor; private ModelInfo? _modelInfo; private IChatClient? _chatClient; private TokenTracker? _tokenTracker; public ApplicationStartup(string[] args) { _args = args; } public AnchorConfig Config => _config ?? throw new InvalidOperationException("Run InitializeAsync first"); public string ApiKey => _config?.ApiKey ?? throw new InvalidOperationException("API key not loaded"); public string Model => _config?.Model ?? throw new InvalidOperationException("Model not loaded"); public string Endpoint => _config?.Endpoint ?? "https://openrouter.ai/api/v1"; public string ProviderName => _tokenExtractor?.ProviderName ?? "Unknown"; public ITokenExtractor TokenExtractor => _tokenExtractor ?? throw new InvalidOperationException("Token extractor not initialized"); public ModelInfo? ModelInfo => _modelInfo; public IChatClient ChatClient => _chatClient ?? throw new InvalidOperationException("Chat client not initialized"); public TokenTracker TokenTracker => _tokenTracker ?? throw new InvalidOperationException("Token tracker not initialized"); /// /// Runs the setup TUI if the "setup" subcommand was passed. Returns true if setup was run. /// public bool HandleSetupSubcommand() { if (_args.Length > 0 && _args[0].Equals("setup", StringComparison.OrdinalIgnoreCase)) { SetupTui.Run(); return true; } return false; } /// /// Initializes the application by loading configuration and creating the chat client. /// public async Task InitializeAsync() { // Load configuration _config = AnchorConfig.Load(); if (string.IsNullOrWhiteSpace(_config.ApiKey)) { AnsiConsole.MarkupLine("[red]No API key configured. Run [bold]anchor setup[/] first.[/]"); throw new InvalidOperationException("API key not configured"); } // Create token extractor _tokenExtractor = ProviderFactory.CreateTokenExtractorForEndpoint(Endpoint); // Fetch model pricing (only for OpenRouter) if (ProviderFactory.IsOpenRouter(Endpoint)) { await AnsiConsole.Status() .Spinner(Spinner.Known.BouncingBar) .SpinnerStyle(Style.Parse("cornflowerblue")) .StartAsync("Fetching model pricing...", async ctx => { try { var pricingProvider = new OpenRouterProvider(); _modelInfo = await pricingProvider.GetModelInfoAsync(Model); } catch { // Pricing is best-effort } }); } // Create chat client var httpClient = new HttpClient(); OpenRouterHeaders.ApplyTo(httpClient); var openAiClient = new OpenAIClient( new ApiKeyCredential(ApiKey), new OpenAIClientOptions { Endpoint = new Uri(Endpoint), Transport = new System.ClientModel.Primitives.HttpClientPipelineTransport(httpClient) }); _chatClient = openAiClient.GetChatClient(Model).AsIChatClient(); // Initialize token tracker _tokenTracker = new TokenTracker(new ChatSession(_chatClient)) { Provider = _tokenExtractor.ProviderName }; 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); } if (_modelInfo != null) { _tokenTracker.ContextLength = _modelInfo.ContextLength; } } /// /// Creates a new ChatSession with the initialized chat client. /// public ChatSession CreateSession() { return new ChatSession(ChatClient); } /// /// Configures tool logging to use Spectre.Console. /// public void ConfigureToolLogging() { object consoleLock = new(); void ToolLog(string message) { lock (consoleLock) { Console.Write("\r" + new string(' ', 40) + "\r"); AnsiConsole.MarkupLine($"[dim grey]{Markup.Escape(message)}[/]"); } } CommandTool.Log = DirTools.Log = FileTools.Log = EditTools.Log = ToolLog; } /// /// Creates and populates a CommandRegistry with all available commands. /// public CommandRegistry CreateCommandRegistry(ChatSession session) { var registry = new CommandRegistry(); registry.Register(new ExitCommand()); registry.Register(new HelpCommand(registry)); registry.Register(new ClearCommand()); registry.Register(new StatusCommand(Model, Endpoint)); registry.Register(new CompactCommand(session.Compactor, session.History)); registry.Register(new SetupCommand()); registry.Register(new ResetCommand(session, TokenTracker)); return registry; } /// /// Creates a HeaderRenderer with the current configuration. /// public HeaderRenderer CreateHeaderRenderer() { return new HeaderRenderer(Model, Endpoint, ProviderName, _modelInfo, _tokenTracker); } }