Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/MauiSherpa.Core/Interfaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2510,6 +2510,8 @@ Task<MultiOperationResult> RunAsync(
event Action? OnModalClosed;
}

public sealed record CopilotModelOption(string Id, string Name);

/// <summary>
/// Service for interacting with GitHub Copilot
/// </summary>
Expand Down Expand Up @@ -2550,6 +2552,16 @@ public interface ICopilotService
/// </summary>
/// <param name="model">Optional model to use</param>
Task StartSessionAsync(string? model = null, string? systemPrompt = null);

/// <summary>
/// Get the models available to the authenticated user.
/// </summary>
Task<IReadOnlyList<CopilotModelOption>> ListModelsAsync();

/// <summary>
/// Change the model for the active chat session.
/// </summary>
Task SetModelAsync(string model);

/// <summary>
/// End the current chat session
Expand Down
2 changes: 1 addition & 1 deletion src/MauiSherpa.Core/MauiSherpa.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
<PackageReference Include="FluentValidation" Version="12.1.1" />
<PackageReference Include="GitHub.Copilot.SDK" Version="1.0.3" />
<PackageReference Include="GitHub.Copilot.SDK" Version="1.0.7" />
<PackageReference Include="Microsoft.TeamFoundation.DistributedTask.WebApi" Version="19.225.2" />
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.225.2" />
<PackageReference Include="Microsoft.VisualStudio.Services.Client" Version="19.225.2" />
Expand Down
29 changes: 27 additions & 2 deletions src/MauiSherpa.Core/Services/CopilotService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,8 @@ public async Task StartSessionAsync(string? model = null, string? systemPrompt =

try
{
_logger.LogInformation($"Starting Copilot session with model: {model ?? "default"}");
var selectedModel = string.IsNullOrWhiteSpace(model) ? "auto" : model;
_logger.LogInformation($"Starting Copilot session with model: {selectedModel}");

// Get tools from tools service
var tools = _toolsService.GetTools();
Expand All @@ -609,7 +610,7 @@ public async Task StartSessionAsync(string? model = null, string? systemPrompt =

var config = new SessionConfig
{
Model = model ?? "claude-opus-4.5", // Use Claude Opus 4.5 as default
Model = selectedModel,
Streaming = true,
Tools = tools.Cast<AIFunctionDeclaration>().ToList(),
OnPermissionRequest = HandleSdkPermissionRequest,
Expand Down Expand Up @@ -657,6 +658,30 @@ public async Task StartSessionAsync(string? model = null, string? systemPrompt =
throw;
}
}

public async Task<IReadOnlyList<CopilotModelOption>> ListModelsAsync()
{
if (_client == null)
{
throw new InvalidOperationException("Not connected to Copilot. Call ConnectAsync first.");
}

var models = await _client.ListModelsAsync();
return models
.Select(model => new CopilotModelOption(model.Id, model.Name ?? model.Id))
.ToList();
}

public async Task SetModelAsync(string model)
{
if (_session == null)
{
throw new InvalidOperationException("No active Copilot session. Call StartSessionAsync first.");
}

await _session.SetModelAsync(model);
_logger.LogInformation($"Copilot session model changed to: {model}");
}

#pragma warning disable GHCP001
private async Task<GitHub.Copilot.Rpc.PermissionDecision> HandleSdkPermissionRequest(PermissionRequest request, PermissionInvocation invocation)
Expand Down
2 changes: 1 addition & 1 deletion src/MauiSherpa.MacOS/MauiSherpa.MacOS.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
<PackageReference Include="Blazorise.Icons.FontAwesome" Version="1.7.2" />
<!-- Reference the SDK directly so its build targets run with the AppKit publish RID
instead of inheriting the build-host architecture from MauiSherpa.Core. -->
<PackageReference Include="GitHub.Copilot.SDK" Version="1.0.3" PrivateAssets="all" />
<PackageReference Include="GitHub.Copilot.SDK" Version="1.0.7" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.QuickGrid" Version="10.0.1" />
<PackageReference Include="Markdig" Version="0.44.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="10.0.1" />
Expand Down
83 changes: 81 additions & 2 deletions src/MauiSherpa/Pages/Copilot.razor
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,15 @@ else
<div class="chat-layout">
<!-- Chat Messages -->
<div class="chat-container">
<div class="model-selector">
<label for="copilot-model"><i class="fas fa-robot"></i> Model</label>
<select id="copilot-model" value="@selectedModel" @onchange="SelectModelAsync" disabled="@isBusy">
@foreach (var model in availableModels)
{
<option value="@model.Id">@model.Name</option>
}
</select>
</div>
<div class="chat-messages" @ref="chatMessagesRef" @onscroll="OnChatScroll">
@* Show suggestions as first item - collapsible *@
<div class="suggestions-bubble @(suggestionsCollapsed ? "collapsed" : "")">
Expand Down Expand Up @@ -806,6 +815,34 @@ else
gap: 1.25rem;
}

.model-selector {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
border-bottom: 1px solid var(--border-color);
color: var(--text-secondary);
font-size: 0.8rem;
}

.model-selector label {
display: flex;
align-items: center;
gap: 0.35rem;
font-weight: 600;
}

.model-selector select {
min-width: 10rem;
max-width: 16rem;
padding: 0.25rem 0.5rem;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--bg-tertiary);
color: var(--text-primary);
font: inherit;
}

.suggestions-section {
background: var(--card-bg);
border-radius: 0.75rem;
Expand Down Expand Up @@ -2180,6 +2217,8 @@ else
private CopilotAvailability? availability;
private bool isConnecting = false;
private bool isBusy = false;
private string selectedModel = "auto";
private List<CopilotModelOption> availableModels = [new("auto", "Auto")];
private bool suggestionsCollapsed = false;
private string userInput = "";
private string currentResponse = "";
Expand Down Expand Up @@ -2448,7 +2487,8 @@ else
try
{
await CopilotService.ConnectAsync();
await CopilotService.StartSessionAsync(systemPrompt: _systemPrompt);
await CopilotService.StartSessionAsync(selectedModel, _systemPrompt);
await LoadModelsAsync();
availability = availability is null
? new CopilotAvailability(true, true, null, null, null)
: availability with { IsInstalled = true, IsAuthenticated = true, ErrorMessage = null };
Expand Down Expand Up @@ -2534,11 +2574,12 @@ else
if (!CopilotService.IsConnected)
{
await CopilotService.ConnectAsync();
await LoadModelsAsync();
}

if (CopilotService.CurrentSessionId == null)
{
await CopilotService.StartSessionAsync(systemPrompt: _systemPrompt);
await CopilotService.StartSessionAsync(selectedModel, _systemPrompt);
}

await CopilotService.SendMessageAsync(message);
Expand All @@ -2553,6 +2594,44 @@ else
}
}

private async Task LoadModelsAsync()
{
try
{
var models = await CopilotService.ListModelsAsync();
availableModels = [
new CopilotModelOption("auto", "Auto"),
.. models.Where(model => !string.Equals(model.Id, "auto", StringComparison.OrdinalIgnoreCase))
];
}
catch (Exception ex)
{
_logger.LogWarning($"Unable to load Copilot models: {ex.Message}");
}
}

private async Task SelectModelAsync(ChangeEventArgs args)
{
var model = args.Value?.ToString();
if (string.IsNullOrWhiteSpace(model) || string.Equals(model, selectedModel, StringComparison.Ordinal))
return;

var previousModel = selectedModel;
selectedModel = model;

try
{
await CopilotService.SetModelAsync(selectedModel);
currentUsageInfo = null;
}
catch (Exception ex)
{
selectedModel = previousModel;
_logger.LogError($"Unable to change Copilot model: {ex.Message}", ex);
await AlertService.ShowAlertAsync("Model Change Failed", ex.Message);
}
}

private void OnNativeMessageSubmitted(string message)
{
InvokeAsync(async () =>
Expand Down
Loading