From bc25dc7b072e2726e405824f03c7c73b865672aa Mon Sep 17 00:00:00 2001 From: Erik Simon Date: Sat, 4 Apr 2026 04:10:58 +0200 Subject: [PATCH] Add Luna.Core server with SignalR hub, session management, and tool system Implement the core application server as an ASP.NET web host with: - SignalR ChatHub for real-time client communication - Session management with token estimation for context windowing - Extensible tool system with attribute-based tool discovery - Chat stream update builder for streaming AI responses - App configuration with appsettings for development and production --- Luna.Core.Abstractions/IChatHubContext.cs | 13 ++ Luna.Core.Abstractions/ISessionManager.cs | 12 ++ .../Luna.Core.Abstractions.csproj | 13 ++ Luna.Core/Hubs/ChatHub.cs | 49 ++++++ Luna.Core/Hubs/ChatHubContext.cs | 30 ++++ Luna.Core/Luna.Core.csproj | 28 +++ Luna.Core/Luna.Core.http | 6 + Luna.Core/Program.cs | 50 ++++++ Luna.Core/Properties/launchSettings.json | 23 +++ Luna.Core/Services/ChatStreamUpdateBuilder.cs | 29 ++++ .../Extensions/ServiceCollectionExtensions.cs | 21 +++ .../Services/IChatStreamUpdateBuilder.cs | 11 ++ Luna.Core/Session/Session.cs | 11 ++ Luna.Core/Session/SessionManager.cs | 160 ++++++++++++++++++ Luna.Core/Session/TokenEstimator.cs | 11 ++ Luna.Core/Tools/BuiltIn/CoreTools.cs | 24 +++ .../Extensions/ServiceCollectionExtensions.cs | 19 +++ Luna.Core/Tools/IToolbox.cs | 30 ++++ Luna.Core/Tools/IToolsProvider.cs | 8 + Luna.Core/Tools/ToolAttribute.cs | 8 + Luna.Core/Tools/ToolsProvider.cs | 9 + Luna.Core/appsettings.Development.json | 8 + Luna.Core/appsettings.json | 13 ++ 23 files changed, 586 insertions(+) create mode 100644 Luna.Core.Abstractions/IChatHubContext.cs create mode 100644 Luna.Core.Abstractions/ISessionManager.cs create mode 100644 Luna.Core.Abstractions/Luna.Core.Abstractions.csproj create mode 100644 Luna.Core/Hubs/ChatHub.cs create mode 100644 Luna.Core/Hubs/ChatHubContext.cs create mode 100644 Luna.Core/Luna.Core.csproj create mode 100644 Luna.Core/Luna.Core.http create mode 100644 Luna.Core/Program.cs create mode 100644 Luna.Core/Properties/launchSettings.json create mode 100644 Luna.Core/Services/ChatStreamUpdateBuilder.cs create mode 100644 Luna.Core/Services/Extensions/ServiceCollectionExtensions.cs create mode 100644 Luna.Core/Services/IChatStreamUpdateBuilder.cs create mode 100644 Luna.Core/Session/Session.cs create mode 100644 Luna.Core/Session/SessionManager.cs create mode 100644 Luna.Core/Session/TokenEstimator.cs create mode 100644 Luna.Core/Tools/BuiltIn/CoreTools.cs create mode 100644 Luna.Core/Tools/Extensions/ServiceCollectionExtensions.cs create mode 100644 Luna.Core/Tools/IToolbox.cs create mode 100644 Luna.Core/Tools/IToolsProvider.cs create mode 100644 Luna.Core/Tools/ToolAttribute.cs create mode 100644 Luna.Core/Tools/ToolsProvider.cs create mode 100644 Luna.Core/appsettings.Development.json create mode 100644 Luna.Core/appsettings.json diff --git a/Luna.Core.Abstractions/IChatHubContext.cs b/Luna.Core.Abstractions/IChatHubContext.cs new file mode 100644 index 0000000..83993f6 --- /dev/null +++ b/Luna.Core.Abstractions/IChatHubContext.cs @@ -0,0 +1,13 @@ +using Luna.Shared; + +namespace Luna.Core.Abstractions; + +public interface IChatHubContext +{ + Task SendResponseAsync(string connectionId, ChannelMessage message, CancellationToken cancellationToken = default); + + Task SendStreamingResponseAsync( + string connectionId, + IAsyncEnumerable messageStream, + CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Luna.Core.Abstractions/ISessionManager.cs b/Luna.Core.Abstractions/ISessionManager.cs new file mode 100644 index 0000000..cba8951 --- /dev/null +++ b/Luna.Core.Abstractions/ISessionManager.cs @@ -0,0 +1,12 @@ +using Luna.Shared; + +namespace Luna.Core.Abstractions; + +public interface ISessionManager +{ + IAsyncEnumerable RouteMessagesAsync(string content, + string conversationId, + string connectionId, + CancellationToken ct = default); + Task ClientDisconnectedAsync(string connectionId); +} \ No newline at end of file diff --git a/Luna.Core.Abstractions/Luna.Core.Abstractions.csproj b/Luna.Core.Abstractions/Luna.Core.Abstractions.csproj new file mode 100644 index 0000000..86df37a --- /dev/null +++ b/Luna.Core.Abstractions/Luna.Core.Abstractions.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/Luna.Core/Hubs/ChatHub.cs b/Luna.Core/Hubs/ChatHub.cs new file mode 100644 index 0000000..79d6d9d --- /dev/null +++ b/Luna.Core/Hubs/ChatHub.cs @@ -0,0 +1,49 @@ +using Luna.Channels.Abstractions; +using Luna.Channels.Cli; +using Luna.Channels.Web; +using Luna.Core.Abstractions; +using Luna.Shared; +using Microsoft.AspNetCore.SignalR; + +namespace Luna.Core.Hubs; + +public class ChatHub(IChannelManager channelManager, IChatHubContext hubContext) : Hub +{ + public override Task OnConnectedAsync() + { + var clientType = Context.GetHttpContext()?.Request.Query["clientType"].ToString(); + + IChannel channel = clientType switch + { + "web" => new WebChannel(Context.ConnectionId, hubContext), + _ => new CliChannel(Context.ConnectionId, hubContext) + }; + + channelManager.RegisterChannel(channel); + return base.OnConnectedAsync(); + } + + public override Task OnDisconnectedAsync(Exception? exception) + { + channelManager.UnregisterChannel(Context.ConnectionId); + return base.OnDisconnectedAsync(exception); + } + + public void OnMessageReceived(ChannelMessage message) + { + var channel = channelManager.GetChannel(Context.ConnectionId); + + switch (channel) + { + case CliChannel cli: + cli.RaiseMessageReceived(this, message); + break; + case WebChannel web: + web.RaiseMessageReceived(this, message); + break; + default: + throw new InvalidOperationException( + $"ChannelManager returned channel of unexpected type: {channel?.GetType().Name ?? "null"}"); + } + } +} diff --git a/Luna.Core/Hubs/ChatHubContext.cs b/Luna.Core/Hubs/ChatHubContext.cs new file mode 100644 index 0000000..961a905 --- /dev/null +++ b/Luna.Core/Hubs/ChatHubContext.cs @@ -0,0 +1,30 @@ +using Luna.Core.Abstractions; +using Luna.Shared; +using Microsoft.AspNetCore.SignalR; + +namespace Luna.Core.Hubs; + +public class ChatHubContext(IHubContext hubContext) : IChatHubContext +{ + public async Task SendResponseAsync(string connectionId, ChannelMessage message, CancellationToken cancellationToken = default) + { + await GetClient(connectionId) + .SendAsync("OnMessageReceived", message, cancellationToken); + } + + public async Task SendStreamingResponseAsync( + string connectionId, + IAsyncEnumerable messageStream, + CancellationToken cancellationToken = default) + { + var client = GetClient(connectionId); + + await foreach (var update in messageStream.WithCancellation(cancellationToken)) + { + await client.SendAsync("OnMessageStreamReceived", update, cancellationToken); + } + } + + private ISingleClientProxy GetClient(string connectionId) + => hubContext.Clients.Client(connectionId); +} \ No newline at end of file diff --git a/Luna.Core/Luna.Core.csproj b/Luna.Core/Luna.Core.csproj new file mode 100644 index 0000000..d640461 --- /dev/null +++ b/Luna.Core/Luna.Core.csproj @@ -0,0 +1,28 @@ + + + + net10.0 + enable + enable + true + + + + + + + + + + + + + + + + + + + + + diff --git a/Luna.Core/Luna.Core.http b/Luna.Core/Luna.Core.http new file mode 100644 index 0000000..a41a2dc --- /dev/null +++ b/Luna.Core/Luna.Core.http @@ -0,0 +1,6 @@ +@Luna.Core_HostAddress = http://localhost:5247 + +GET {{Luna.Core_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/Luna.Core/Program.cs b/Luna.Core/Program.cs new file mode 100644 index 0000000..c58d2df --- /dev/null +++ b/Luna.Core/Program.cs @@ -0,0 +1,50 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Luna.Agents.Extensions; +using Luna.Channels.Extensions; +using Luna.Configuration.Extensions; +using Luna.Core.Abstractions; +using Luna.Core.Hubs; +using Luna.Core.Services.Extensions; +using Luna.Core.Tools.Extensions; +using Luna.Memory.Extensions; +using Luna.Providers.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +builder.Configuration + .AddLunaConfiguration(); + +builder.Services + .AddOpenApi() + .AddProviders() + .AddAgents() + .AddTools() + .AddMemoryStore() + .AddSessionManager() + .AddChannels(); + +builder.Services.AddSignalR() + .AddJsonProtocol(options => + { + options.PayloadSerializerOptions.Converters.Add( + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); + }); +builder.Services.AddTransient(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.MapHub("/chat"); +app.Run(); + +// Expose Program type for WebApplicationFactory in integration tests +namespace Luna.Core +{ + public partial class Program { } +} diff --git a/Luna.Core/Properties/launchSettings.json b/Luna.Core/Properties/launchSettings.json new file mode 100644 index 0000000..36e643a --- /dev/null +++ b/Luna.Core/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5247", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7203;http://localhost:5247", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Luna.Core/Services/ChatStreamUpdateBuilder.cs b/Luna.Core/Services/ChatStreamUpdateBuilder.cs new file mode 100644 index 0000000..dd9473c --- /dev/null +++ b/Luna.Core/Services/ChatStreamUpdateBuilder.cs @@ -0,0 +1,29 @@ +using Luna.Shared; +using Microsoft.Extensions.AI; + +namespace Luna.Core.Services; + +public class ChatStreamUpdateBuilder : IChatStreamUpdateBuilder +{ + public ChatStreamUpdate FromAiContent(AIContent content, string messageId) + => content switch + { + TextReasoningContent reasoning + => new ChatStreamUpdate(ChatStreamUpdateType.Text, reasoning.Text, "assistant", false, messageId), + TextContent text + => new ChatStreamUpdate(ChatStreamUpdateType.Text, text.Text, "assistant", false, messageId), + ToolCallContent toolCall + => new ChatStreamUpdate(ChatStreamUpdateType.ToolCall, toolCall.ToString(), "assistant", false, messageId), + ToolResultContent toolResult + => new ChatStreamUpdate(ChatStreamUpdateType.ToolResult, toolResult.ToString(), "assistant", false, messageId), + UsageContent usage + => new ChatStreamUpdate(ChatStreamUpdateType.Usage, usage.ToString(), "system", false, messageId), + _ => throw new NotSupportedException($"Unsupported content type: {content.GetType().Name}") + }; + + public ChatStreamUpdate FromString(string content, string messageId) + => new(ChatStreamUpdateType.Text, content, "assistant", false, messageId); + + public ChatStreamUpdate CreateCompleteUpdate(string messageId) + => new(ChatStreamUpdateType.Complete, null, null, true, messageId); +} \ No newline at end of file diff --git a/Luna.Core/Services/Extensions/ServiceCollectionExtensions.cs b/Luna.Core/Services/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..c7a7fdd --- /dev/null +++ b/Luna.Core/Services/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +using Luna.Configuration.Extensions; +using Luna.Core.Abstractions; +using Luna.Core.Session; + +namespace Luna.Core.Services.Extensions; + +public static class ServiceCollectionExtensions +{ + extension(IServiceCollection services) + { + public IServiceCollection AddSessionManager() + { + services.AddTransient(); + + services.AddSessionOptions(); + services.AddSingleton(); + + return services; + } + } +} diff --git a/Luna.Core/Services/IChatStreamUpdateBuilder.cs b/Luna.Core/Services/IChatStreamUpdateBuilder.cs new file mode 100644 index 0000000..7bdf7d5 --- /dev/null +++ b/Luna.Core/Services/IChatStreamUpdateBuilder.cs @@ -0,0 +1,11 @@ +using Luna.Shared; +using Microsoft.Extensions.AI; + +namespace Luna.Core.Services; + +public interface IChatStreamUpdateBuilder +{ + ChatStreamUpdate FromAiContent(AIContent content, string messageId); + ChatStreamUpdate FromString(string content, string messageId); + ChatStreamUpdate CreateCompleteUpdate(string messageId); +} \ No newline at end of file diff --git a/Luna.Core/Session/Session.cs b/Luna.Core/Session/Session.cs new file mode 100644 index 0000000..7194979 --- /dev/null +++ b/Luna.Core/Session/Session.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.AI; + +namespace Luna.Core.Session; + +public class Session +{ + public required string SessionId { get; set; } + public required List Messages { get; set; } + + public int TokenAmount => TokenEstimator.EstimateTokens(Messages); +} \ No newline at end of file diff --git a/Luna.Core/Session/SessionManager.cs b/Luna.Core/Session/SessionManager.cs new file mode 100644 index 0000000..bd86ae6 --- /dev/null +++ b/Luna.Core/Session/SessionManager.cs @@ -0,0 +1,160 @@ +using System.Collections.Concurrent; +using System.Globalization; +using System.Text; +using Luna.Agents.Abstractions; +using Luna.Core.Abstractions; +using Luna.Core.Services; +using Luna.Memory; +using Luna.Shared; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Options; +using SessionOptions = Luna.Configuration.SessionOptions; + +namespace Luna.Core.Session; + +public class SessionManager( + [FromKeyedServices("Core")] IAgent coreAgent, + [FromKeyedServices("Librarian")] IAgent librarianAgent, + IOptions sessionOptions, + IChatStreamUpdateBuilder streamUpdateBuilder, + IMemoryStore memoryStore) : ISessionManager +{ + private readonly Dictionary sessions = new(); + private readonly ConcurrentDictionary connectedClients = new(); + + private const string CompactionPrompt = + "Summarize the following conversation history for context preservation. Keep it short (max 12 bullet points)."; + + private SessionOptions Options => sessionOptions.Value; + + public async IAsyncEnumerable RouteMessagesAsync( + string content, + string conversationId, + string connectionId, + CancellationToken ct = default) + { + if (!connectedClients.ContainsKey(connectionId)) + connectedClients[connectionId] = conversationId; + + if (!sessions.ContainsKey(conversationId)) + { + var memory = new ChatMessage(ChatRole.System, await memoryStore.GetMemoriesAsync()); + sessions[conversationId] = new Session + { + SessionId = conversationId, + Messages = string.IsNullOrWhiteSpace(memory.Text) + ? [] + : [memory] + }; + } + + var session = sessions[conversationId]; + + var coreOptions = coreAgent.Options; + if (session.TokenAmount > coreOptions.MaxContextTokens * Options.ContextTokenThreshold) + await CompactSessionAsync(conversationId, Options.RetainedMessagesAfterCompacting, ct); + + session.Messages.Add(new ChatMessage(ChatRole.User, content)); + + var messageId = Guid.NewGuid().ToString(); + var responseBuilder = new StringBuilder(); + await foreach (var update in coreAgent.ProcessStreamingAsync(session.Messages, ct)) + { + if (update.Contents is { Count: > 0 }) + { + foreach (var chunk in update.Contents) + { + var streamUpdate = streamUpdateBuilder.FromAiContent(chunk, messageId); + responseBuilder.Append(streamUpdate.Content); + yield return streamUpdate; + } + } + else if (update.Text is { Length: > 0 } text) + { + var streamUpdate = streamUpdateBuilder.FromString(text, messageId); + responseBuilder.Append(streamUpdate.Content); + yield return streamUpdate; + } + } + yield return streamUpdateBuilder.CreateCompleteUpdate(messageId); + + session.Messages.Add(new ChatMessage(ChatRole.Assistant, responseBuilder.ToString())); + } + + public async Task ClientDisconnectedAsync(string connectionId) + { + if (!connectedClients.TryRemove(connectionId, out var conversationId)) + return; + + await SaveSessionLogAsync(conversationId); + } + + private async Task SaveSessionLogAsync(string conversationId) + { + var session = sessions[conversationId]; + var logBuilder = new StringBuilder(); + + foreach (var message in session.Messages + .Where(m => m.Role != ChatRole.System || m.Role != ChatRole.Tool)) + { + logBuilder.AppendLine($"[{message.Role.Value}]:"); + logBuilder.AppendLine(message.Text); + logBuilder.AppendLine(); + } + + await memoryStore.AddMemoryAsync(logBuilder.ToString()); + + sessions.Remove(conversationId); + } + + private async Task CompactSessionAsync(string conversationId, int retainMessages = 0, CancellationToken ct = default) + { + var session = sessions[conversationId]; + var retainedMessages = session.Messages.Count > retainMessages + ? session.Messages.TakeLast(retainMessages) + : []; + var messagesToCompact = session.Messages.Count > retainMessages + ? session.Messages.SkipLast(retainMessages) + : session.Messages; + + var compactionMessage = BuildCompactionMessage(messagesToCompact.ToList()); + var response = await librarianAgent.ProcessAsync([compactionMessage], ct); + + var memoryBuilder = new StringBuilder(); + memoryBuilder.AppendLine("<---- MEMORY BEGIN ---->"); + memoryBuilder.AppendLine($"[Meta] Conversation Recorded at: {DateTime.Now.ToString(CultureInfo.CurrentCulture)}"); + foreach (var message in response) + { + if (message.Role != ChatRole.System) + { + memoryBuilder.AppendLine(message.Text); + } + } + memoryBuilder.AppendLine("<---- MEMORY END ---->"); + + session.Messages = [ + new ChatMessage(ChatRole.Assistant, memoryBuilder.ToString()), + ]; + + session.Messages.AddRange(retainedMessages); + } + + private ChatMessage BuildCompactionMessage(IReadOnlyList messages) + { + var builder = new StringBuilder(); + builder.AppendLine(CompactionPrompt); + builder.AppendLine(); + builder.AppendLine("<---- CONVERSATION BEGIN ---->"); + builder.AppendLine(); + foreach (var message in messages) + { + if (message.Text.Contains("<---- MEMORY BEGIN ---->")) + continue; + + builder.AppendLine($"{message.Role}: {message.Text}"); + } + builder.AppendLine(); + builder.AppendLine("<---- CONVERSATION END ---->"); + return new ChatMessage(ChatRole.User, builder.ToString()); + } +} \ No newline at end of file diff --git a/Luna.Core/Session/TokenEstimator.cs b/Luna.Core/Session/TokenEstimator.cs new file mode 100644 index 0000000..0306b32 --- /dev/null +++ b/Luna.Core/Session/TokenEstimator.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.AI; + +namespace Luna.Core.Session; + +public static class TokenEstimator +{ + public static int EstimateTokens(string text) + => text.Length / 4; + public static int EstimateTokens(IReadOnlyList messages) + => messages.Sum(m => EstimateTokens(m.Text)); +} \ No newline at end of file diff --git a/Luna.Core/Tools/BuiltIn/CoreTools.cs b/Luna.Core/Tools/BuiltIn/CoreTools.cs new file mode 100644 index 0000000..7edbd53 --- /dev/null +++ b/Luna.Core/Tools/BuiltIn/CoreTools.cs @@ -0,0 +1,24 @@ +namespace Luna.Core.Tools.BuiltIn; + +public class CoreTools : IToolbox +{ + public string Name => "core"; + + public record Channel(string Name, string Health, bool IsConnected); + public record ListChannelsResult(IEnumerable Channels); + + [Tool("list_channels", "List available communication channels")] + public ListChannelsResult ListChannels() + { + return new ListChannelsResult([ + new Channel("cli", "healthy", true), + new Channel("web", "healthy", false), + new Channel("telegram", "healthy", false) + ]); + } + + [Tool("change_channel", "Change the active communication channel")] + public void ChangeChannel(string channelName) + { + } +} \ No newline at end of file diff --git a/Luna.Core/Tools/Extensions/ServiceCollectionExtensions.cs b/Luna.Core/Tools/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..71352a6 --- /dev/null +++ b/Luna.Core/Tools/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,19 @@ +namespace Luna.Core.Tools.Extensions; + +public static class ServiceCollectionExtensions +{ + extension(IServiceCollection services) + { + public IServiceCollection AddTools() + { + services.AddSingleton(); + + typeof(ToolsProvider).Assembly.GetTypes() + .Where(t => typeof(IToolbox).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract) + .ToList() + .ForEach(t => services.AddTransient(typeof(IToolbox), t)); + + return services; + } + } +} \ No newline at end of file diff --git a/Luna.Core/Tools/IToolbox.cs b/Luna.Core/Tools/IToolbox.cs new file mode 100644 index 0000000..04089f1 --- /dev/null +++ b/Luna.Core/Tools/IToolbox.cs @@ -0,0 +1,30 @@ +using System.Reflection; +using Microsoft.Extensions.AI; + +namespace Luna.Core.Tools; + +public interface IToolbox +{ + public string Name { get; } + + public sealed IEnumerable GetTools() + { + var type = GetType(); + + List<(ToolAttribute Attribute, MethodInfo Method)> tools = []; + foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) + { + var attribute = method.GetCustomAttribute(); + if (attribute is null) continue; + + tools.Add((attribute, method)); + } + + return tools.Select(tool => AIFunctionFactory.Create( + tool.Method, + this, + $"{this.Name}/{tool.Attribute.Name}", + tool.Attribute.Description + )); + } +} \ No newline at end of file diff --git a/Luna.Core/Tools/IToolsProvider.cs b/Luna.Core/Tools/IToolsProvider.cs new file mode 100644 index 0000000..6cdfcf3 --- /dev/null +++ b/Luna.Core/Tools/IToolsProvider.cs @@ -0,0 +1,8 @@ +using Microsoft.Extensions.AI; + +namespace Luna.Core.Tools; + +public interface IToolsProvider +{ + IEnumerable GetTools(); +} \ No newline at end of file diff --git a/Luna.Core/Tools/ToolAttribute.cs b/Luna.Core/Tools/ToolAttribute.cs new file mode 100644 index 0000000..2d8641b --- /dev/null +++ b/Luna.Core/Tools/ToolAttribute.cs @@ -0,0 +1,8 @@ +namespace Luna.Core.Tools; + +[AttributeUsage(AttributeTargets.Method)] +public class ToolAttribute(string name, string description) : Attribute +{ + public string Name { get; } = name; + public string Description { get; } = description; +} \ No newline at end of file diff --git a/Luna.Core/Tools/ToolsProvider.cs b/Luna.Core/Tools/ToolsProvider.cs new file mode 100644 index 0000000..a1b893a --- /dev/null +++ b/Luna.Core/Tools/ToolsProvider.cs @@ -0,0 +1,9 @@ +using Microsoft.Extensions.AI; + +namespace Luna.Core.Tools; + +public sealed class ToolsProvider(IEnumerable toolboxes) : IToolsProvider +{ + public IEnumerable GetTools() + => toolboxes.SelectMany(t => t.GetTools()); +} \ No newline at end of file diff --git a/Luna.Core/appsettings.Development.json b/Luna.Core/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Luna.Core/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Luna.Core/appsettings.json b/Luna.Core/appsettings.json new file mode 100644 index 0000000..e9626dc --- /dev/null +++ b/Luna.Core/appsettings.json @@ -0,0 +1,13 @@ +{ + "Session": { + "ContextTokenThreshold": 0.75, + "RetainedMessagesAfterCompacting": 2 + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +}