diff --git a/README.md b/README.md index 52a5560..b9a727e 100644 --- a/README.md +++ b/README.md @@ -222,9 +222,9 @@ Passed as `SessionConfig.ReasoningEffort`. Not all models respect this setting. ### Hooks and User-Input Handlers -The Copilot SDK provides `OnPreToolUse`, `OnPostToolUse`, and `OnUserInputRequest` callbacks on `SessionConfig`. Coralph intentionally does not implement them: +The Copilot SDK exposes tool hooks through `SessionConfig.Hooks` and user-input callbacks through `SessionConfig.OnUserInputRequest`. Coralph intentionally does not configure them: -- **Tool-use hooks** are unnecessary because `CopilotSessionEventRouter` already captures all tool events via `session.On()`. +- **Tool-use hooks** are unnecessary because `CopilotSessionEventRouter` already captures all tool events via `SessionConfig.OnEvent`. - **User-input handlers** are inappropriate for an unattended loop; models should not prompt for interactive input during an automated run. ### OpenAI-Compatible Providers diff --git a/docs/architecture.md b/docs/architecture.md index 945747c..27bed55 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -55,7 +55,7 @@ flowchart TD end subgraph External["External Dependencies"] - CopilotSDK["GitHub.Copilot.SDK 0.1.32
AI Runtime"] + CopilotSDK["GitHub.Copilot.SDK 1.0.0
AI Runtime"] Hex1b["Hex1b 0.83.0
TUI Framework"] ConfigJson["Microsoft.Extensions.Configuration.Json 8.0.0
JSON Config Loading"] SpectreConsole["Spectre.Console 0.49.1
Rich Terminal UI"] @@ -169,7 +169,7 @@ sequenceDiagram participant Config as ConfigurationService participant Runner as LoopOrchestrator participant Tasks as TaskBacklog - participant SDK as GitHub.Copilot.SDK + participant SDK as GitHub.Copilot participant Output as ConsoleOutput participant Git as GitService participant Files as File System @@ -194,7 +194,7 @@ sequenceDiagram | Package | Version | Purpose | |---------|---------|---------| -| **GitHub.Copilot.SDK** | 0.1.32 | AI runtime for Copilot integration | +| **GitHub.Copilot.SDK** | 1.0.0 | AI runtime for Copilot integration | | **Hex1b** | 0.83.0 | Interactive split-pane TUI rendering | | **Microsoft.Extensions.Configuration.Json** | 8.0.0 | Load configuration from JSON files | | **Microsoft.Extensions.Options.ConfigurationExtensions** | 8.0.0 | Bind configuration to options classes | @@ -213,7 +213,7 @@ sequenceDiagram ## Key Design Decisions -1. **Streaming Architecture**: Uses event-based streaming from GitHub.Copilot.SDK for real-time output +1. **Streaming Architecture**: Uses event-based streaming from GitHub.Copilot for real-time output 2. **Pluggable Output Backends**: Runtime logic writes to a single facade; rendering is switched between classic and TUI modes 3. **Stream Compatibility Guardrail**: `--stream-events` forces classic output to preserve JSONL integrations 4. **Tool Extensibility**: Custom AI tools exposed via `AIFunctionFactory.Create()` pattern diff --git a/src/Coralph.Tests/CopilotClientFactoryTests.cs b/src/Coralph.Tests/CopilotClientFactoryTests.cs index c72cba8..808d08d 100644 --- a/src/Coralph.Tests/CopilotClientFactoryTests.cs +++ b/src/Coralph.Tests/CopilotClientFactoryTests.cs @@ -1,8 +1,11 @@ using Coralph; -using GitHub.Copilot.SDK; +using GitHub.Copilot; +using GitHub.Copilot.Rpc; namespace Coralph.Tests; +#pragma warning disable GHCP001 + public class CopilotClientFactoryTests { [Fact] @@ -20,10 +23,11 @@ public void CreateClientOptions_WithExplicitValues_CopiesKnownFields() var clientOptions = CopilotClientFactory.CreateClientOptions(options); - Assert.False(string.IsNullOrWhiteSpace(clientOptions.Cwd)); - Assert.True(Path.IsPathRooted(clientOptions.Cwd)); - Assert.Equal("/usr/local/bin/copilot", clientOptions.CliPath); - Assert.Equal("http://localhost:3000", clientOptions.CliUrl); + Assert.False(string.IsNullOrWhiteSpace(clientOptions.WorkingDirectory)); + Assert.True(Path.IsPathRooted(clientOptions.WorkingDirectory)); + var connection = Assert.IsType(clientOptions.Connection); + Assert.Equal("http://localhost:3000", connection.Url); + Assert.Null(connection.ConnectionToken); Assert.Equal("ghp_test_token", clientOptions.GitHubToken); Assert.NotNull(clientOptions.Telemetry); Assert.Equal("http://localhost:4318", clientOptions.Telemetry!.OtlpEndpoint); @@ -31,15 +35,27 @@ public void CreateClientOptions_WithExplicitValues_CopiesKnownFields() Assert.True(clientOptions.Telemetry.CaptureContent); } + [Fact] + public void CreateClientOptions_WithCliPath_UsesStdioConnection() + { + var clientOptions = CopilotClientFactory.CreateClientOptions(new LoopOptions + { + CliPath = "/usr/local/bin/copilot" + }); + + var connection = Assert.IsType(clientOptions.Connection); + Assert.Equal("/usr/local/bin/copilot", connection.Path); + Assert.Null(connection.Args); + } + [Fact] public void CreateClientOptions_WithNullOptionalValues_LeavesThemUnset() { var clientOptions = CopilotClientFactory.CreateClientOptions(new LoopOptions()); - Assert.False(string.IsNullOrWhiteSpace(clientOptions.Cwd)); - Assert.True(Path.IsPathRooted(clientOptions.Cwd)); - Assert.Null(clientOptions.CliPath); - Assert.Null(clientOptions.CliUrl); + Assert.False(string.IsNullOrWhiteSpace(clientOptions.WorkingDirectory)); + Assert.True(Path.IsPathRooted(clientOptions.WorkingDirectory)); + Assert.Null(clientOptions.Connection); Assert.Null(clientOptions.GitHubToken); Assert.Null(clientOptions.Telemetry); } @@ -48,7 +64,7 @@ public void CreateClientOptions_WithNullOptionalValues_LeavesThemUnset() public void CreateSessionConfig_WithToolsAndProvider_CopiesExpectedFields() { var tools = CustomTools.GetDefaultTools("issues.json", "progress.txt", "generated_tasks.json"); - SessionEventHandler onEvent = _ => { }; + Action onEvent = _ => { }; var options = new LoopOptions { Model = "GPT-5.1-Codex", @@ -77,10 +93,10 @@ public void CreateSessionConfig_WithToolsAndProvider_CopiesExpectedFields() Assert.NotNull(config.SystemMessage); Assert.Equal(SystemMessageMode.Customize, config.SystemMessage!.Mode); Assert.NotNull(config.SystemMessage.Sections); - Assert.Contains(SystemPromptSections.Tone, config.SystemMessage.Sections.Keys); - Assert.Contains(SystemPromptSections.Guidelines, config.SystemMessage.Sections.Keys); - Assert.Contains(SystemPromptSections.ToolInstructions, config.SystemMessage.Sections.Keys); - Assert.Contains(SystemPromptSections.Safety, config.SystemMessage.Sections.Keys); + Assert.Contains(SystemMessageSection.Tone, config.SystemMessage.Sections.Keys); + Assert.Contains(SystemMessageSection.Guidelines, config.SystemMessage.Sections.Keys); + Assert.Contains(SystemMessageSection.ToolInstructions, config.SystemMessage.Sections.Keys); + Assert.Contains(SystemMessageSection.Safety, config.SystemMessage.Sections.Keys); } [Fact] diff --git a/src/Coralph.Tests/CopilotModelDiscoveryTests.cs b/src/Coralph.Tests/CopilotModelDiscoveryTests.cs index 38983c9..87c1839 100644 --- a/src/Coralph.Tests/CopilotModelDiscoveryTests.cs +++ b/src/Coralph.Tests/CopilotModelDiscoveryTests.cs @@ -1,6 +1,6 @@ using System.Text.Json; using Coralph; -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Spectre.Console.Testing; namespace Coralph.Tests; diff --git a/src/Coralph.Tests/CopilotSessionEventRouterTests.cs b/src/Coralph.Tests/CopilotSessionEventRouterTests.cs index 40236d8..45a77a1 100644 --- a/src/Coralph.Tests/CopilotSessionEventRouterTests.cs +++ b/src/Coralph.Tests/CopilotSessionEventRouterTests.cs @@ -3,7 +3,7 @@ using System.Text.Json; using Coralph; using Coralph.Ui; -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Spectre.Console; using Spectre.Console.Testing; @@ -107,10 +107,10 @@ public async Task HandleEvent_ToolExecutionComplete_ForReportIntent_SuppressesCo { ToolCallId = "tool-1", ToolName = "report_intent", - Arguments = new Dictionary + Arguments = JsonSerializer.SerializeToElement(new Dictionary { ["intent"] = "test" - } + }) } }); router.HandleEvent(new ToolExecutionCompleteEvent @@ -344,10 +344,10 @@ public async Task HandleEvent_AssistantMessage_WithToolRequests_EmitsStableToolR ToolCallId = "tool-call-1", Name = "list_open_issues", Type = AssistantMessageToolRequestType.Function, - Arguments = new Dictionary + Arguments = JsonSerializer.SerializeToElement(new Dictionary { ["includeClosed"] = false - }, + }), ToolTitle = "List issues", McpServerName = "coralph", IntentionSummary = "Read current issue state" @@ -364,7 +364,7 @@ public async Task HandleEvent_AssistantMessage_WithToolRequests_EmitsStableToolR Assert.Equal("tool-call-1", request.GetProperty("toolCallId").GetString()); Assert.Equal("list_open_issues", request.GetProperty("name").GetString()); - Assert.Equal("Function", request.GetProperty("type").GetString()); + Assert.Equal("function", request.GetProperty("type").GetString()); Assert.False(request.GetProperty("arguments").GetProperty("includeClosed").GetBoolean()); Assert.Equal("List issues", request.GetProperty("toolTitle").GetString()); Assert.Equal("coralph", request.GetProperty("mcpServerName").GetString()); diff --git a/src/Coralph.Tests/CopilotSystemMessageFactoryTests.cs b/src/Coralph.Tests/CopilotSystemMessageFactoryTests.cs index 33d46a0..9d6a9b8 100644 --- a/src/Coralph.Tests/CopilotSystemMessageFactoryTests.cs +++ b/src/Coralph.Tests/CopilotSystemMessageFactoryTests.cs @@ -1,5 +1,5 @@ using Coralph; -using GitHub.Copilot.SDK; +using GitHub.Copilot; namespace Coralph.Tests; @@ -12,10 +12,10 @@ public void Create_UsesCustomizeModeWithExpectedSections() Assert.Equal(SystemMessageMode.Customize, config.Mode); Assert.NotNull(config.Sections); - Assert.Contains(SystemPromptSections.Tone, config.Sections.Keys); - Assert.Contains(SystemPromptSections.Guidelines, config.Sections.Keys); - Assert.Contains(SystemPromptSections.ToolInstructions, config.Sections.Keys); - Assert.Contains(SystemPromptSections.Safety, config.Sections.Keys); + Assert.Contains(SystemMessageSection.Tone, config.Sections.Keys); + Assert.Contains(SystemMessageSection.Guidelines, config.Sections.Keys); + Assert.Contains(SystemMessageSection.ToolInstructions, config.Sections.Keys); + Assert.Contains(SystemMessageSection.Safety, config.Sections.Keys); } [Fact] @@ -24,7 +24,7 @@ public void Create_WithDryRun_AppendsDryRunSafetyInstruction() var config = CopilotSystemMessageFactory.Create(new LoopOptions { DryRun = true }); Assert.NotNull(config.Sections); - Assert.True(config.Sections.TryGetValue(SystemPromptSections.Safety, out var safety)); + Assert.True(config.Sections.TryGetValue(SystemMessageSection.Safety, out var safety)); Assert.NotNull(safety); Assert.Contains("Dry-run mode is enabled", safety!.Content); } diff --git a/src/Coralph.Tests/PermissionPolicyTests.cs b/src/Coralph.Tests/PermissionPolicyTests.cs index 4e38137..ad3fa80 100644 --- a/src/Coralph.Tests/PermissionPolicyTests.cs +++ b/src/Coralph.Tests/PermissionPolicyTests.cs @@ -1,8 +1,11 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; +using GitHub.Copilot.Rpc; using Xunit; namespace Coralph.Tests; +#pragma warning disable GHCP001 + public sealed class PermissionPolicyTests { [Fact] @@ -262,14 +265,15 @@ private static PermissionRequest CreateRequest(string kind) return new PermissionRequest { Kind = kind }; } - private static void AssertApproved(PermissionRequestResult result) + private static void AssertApproved(PermissionDecision result) { - Assert.Equal(PermissionRequestResultKind.Approved, result.Kind); + Assert.IsType(result); } - private static void AssertRejected(PermissionRequestResult result) + private static void AssertRejected(PermissionDecision result) { - Assert.Equal(PermissionRequestResultKind.Rejected, result.Kind); + var rejected = Assert.IsType(result); + Assert.Equal("Rejected by Coralph permission policy.", rejected.Feedback); } private static PermissionInvocation CreateInvocation() diff --git a/src/Coralph.Tests/packages.lock.json b/src/Coralph.Tests/packages.lock.json index 72cf5db..624909e 100644 --- a/src/Coralph.Tests/packages.lock.json +++ b/src/Coralph.Tests/packages.lock.json @@ -41,12 +41,11 @@ }, "GitHub.Copilot.SDK": { "type": "Transitive", - "resolved": "0.3.0", - "contentHash": "zTmlbLWsmmZT/v/vbvP66d73t3sJ92gSavCe+52MtD1jwOJsGv5ExL8CY/jxA9/3GQt50LZs3p+ntjBpe/+Rvw==", + "resolved": "1.0.0", + "contentHash": "hAeuf54OVdFxEtf0AlMgiQBNpeMy7rSbr6Y1bJWjeln8U71D5QEryje8auVQATt3l5ubdbmWGFnO9VOnBhWCfw==", "dependencies": { "Microsoft.Extensions.AI.Abstractions": "10.2.0", - "Microsoft.Extensions.Logging.Abstractions": "10.0.2", - "StreamJsonRpc": "2.24.84" + "Microsoft.Extensions.Logging.Abstractions": "10.0.2" } }, "Hex1b": { @@ -58,20 +57,6 @@ "QRCoder": "1.7.0" } }, - "MessagePack": { - "type": "Transitive", - "resolved": "2.5.198", - "contentHash": "ul2rGISMatBL4AbXTteEml4xtOsKF8yClEkS0rnrMyFs6Cu3KTbdZwOXrcbEUjGFIDiCwHuYIGyS55FeiaYzBw==", - "dependencies": { - "MessagePack.Annotations": "2.5.198", - "Microsoft.NET.StringTools": "17.6.3" - } - }, - "MessagePack.Annotations": { - "type": "Transitive", - "resolved": "2.5.198", - "contentHash": "3U9OvqQGTra+Mz1k1zfNAScSdNHobnqtQ51qdMGUZppkNDZJl0X/igq6Qz5zDBLEZoYqZrFtZwFx6wBJHHI8BA==" - }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "17.14.1", @@ -192,11 +177,6 @@ "resolved": "8.0.0", "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" }, - "Microsoft.NET.StringTools": { - "type": "Transitive", - "resolved": "18.0.2", - "contentHash": "cTZw3GHkAlqZACYGeQT3niS3UfVQ8CH0O5+zUdhxstrg1Z8Q2ViXYFKjSxHmEXTX85mrOT/QnHZOeQhhSsIrkQ==" - }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "17.14.1", @@ -211,53 +191,16 @@ "Newtonsoft.Json": "13.0.3" } }, - "Microsoft.VisualStudio.Threading.Only": { - "type": "Transitive", - "resolved": "17.14.15", - "contentHash": "NqONyw1RXyj9P3k5e1uU2k9kc1ptwuU5NJQzG+MPq7vQVHUzBY8HLuJf/N2Rw5H/myD96CVxziDxmjawPuzntw==", - "dependencies": { - "Microsoft.VisualStudio.Validation": "17.8.8" - } - }, - "Microsoft.VisualStudio.Validation": { - "type": "Transitive", - "resolved": "17.13.22", - "contentHash": "fC20ITOxlUpGQ0ltAxITQbeFxwuB6OjLT3lByC4+/Gm46o/Mwv9hlIVrBhiUhnqdQzUUvr4EHkdiYe7WOSMLmw==" - }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" }, - "Nerdbank.MessagePack": { - "type": "Transitive", - "resolved": "1.0.2", - "contentHash": "mUcRO0trgc85qKPR360ybSvZTbm3nlSr1aK0JlKVWC5iC9OYxVLL1AbJxaW2Pbj4EQNCP8dKiz/SIlFPy67Ixw==", - "dependencies": { - "Microsoft.NET.StringTools": "18.0.2", - "Microsoft.VisualStudio.Validation": "17.13.22", - "PolyType": "1.0.0" - } - }, - "Nerdbank.Streams": { - "type": "Transitive", - "resolved": "2.13.16", - "contentHash": "GjifQ5M4IpQWjGNNbIrsj8M/M7ESb2328OeoGeqxk5rOJiuaAJ5zFc6CyqB+5UWKr1KEGK23hKoxA+z1gooAKA==", - "dependencies": { - "Microsoft.VisualStudio.Threading.Only": "17.13.61", - "Microsoft.VisualStudio.Validation": "17.8.8" - } - }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, - "PolyType": { - "type": "Transitive", - "resolved": "1.0.0", - "contentHash": "q8vTQfDoKa/vCNMT+TvKbzsl+Z/s4k/qaP8WQGvNDIILkxKhP+N3NbFAOiglQljNmAj/ei51wO9jwcAI9jhtHw==" - }, "QRCoder": { "type": "Transitive", "resolved": "1.7.0", @@ -308,19 +251,6 @@ "Spectre.Console": "0.49.1" } }, - "StreamJsonRpc": { - "type": "Transitive", - "resolved": "2.24.84", - "contentHash": "7PtQlA7wYFBzOrcuALWGs1uPJI712KEYOMZXxxSKel9C0y5TPyjxXLkpw7d4O175rUKNbLHHbB89fPzMC2SLPg==", - "dependencies": { - "MessagePack": "2.5.198", - "Microsoft.VisualStudio.Threading.Only": "17.14.15", - "Microsoft.VisualStudio.Validation": "17.13.22", - "Nerdbank.MessagePack": "1.0.2", - "Nerdbank.Streams": "2.13.16", - "Newtonsoft.Json": "13.0.3" - } - }, "System.CommandLine": { "type": "Transitive", "resolved": "2.0.0-beta4.22272.1", @@ -377,7 +307,7 @@ "coralph": { "type": "Project", "dependencies": { - "GitHub.Copilot.SDK": "[0.3.0, )", + "GitHub.Copilot.SDK": "[1.0.0, )", "Hex1b": "[0.83.0, )", "Microsoft.Extensions.Configuration.Json": "[8.0.0, )", "Microsoft.Extensions.Options.ConfigurationExtensions": "[8.0.0, )", diff --git a/src/Coralph/CopilotClientFactory.cs b/src/Coralph/CopilotClientFactory.cs index ac1125a..ddc3237 100644 --- a/src/Coralph/CopilotClientFactory.cs +++ b/src/Coralph/CopilotClientFactory.cs @@ -1,6 +1,9 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; +using GitHub.Copilot.Rpc; using Microsoft.Extensions.AI; +#pragma warning disable GHCP001 + namespace Coralph; internal static class CopilotClientFactory @@ -9,18 +12,18 @@ internal static CopilotClientOptions CreateClientOptions(LoopOptions options) { var clientOptions = new CopilotClientOptions { - Cwd = Directory.GetCurrentDirectory(), + WorkingDirectory = Directory.GetCurrentDirectory(), Telemetry = CreateTelemetryConfig(options) }; if (!string.IsNullOrWhiteSpace(options.CliPath)) { - clientOptions.CliPath = options.CliPath; + clientOptions.Connection = RuntimeConnection.ForStdio(options.CliPath, args: null); } if (!string.IsNullOrWhiteSpace(options.CliUrl)) { - clientOptions.CliUrl = options.CliUrl; + clientOptions.Connection = RuntimeConnection.ForUri(options.CliUrl, connectionToken: null); } if (!string.IsNullOrWhiteSpace(options.CopilotToken)) @@ -34,8 +37,8 @@ internal static CopilotClientOptions CreateClientOptions(LoopOptions options) internal static SessionConfig CreateSessionConfig( LoopOptions options, AIFunction[] tools, - PermissionRequestHandler onPermissionRequest, - SessionEventHandler? onEvent = null) + Func> onPermissionRequest, + Action? onEvent = null) { return new SessionConfig { diff --git a/src/Coralph/CopilotModelDiscovery.cs b/src/Coralph/CopilotModelDiscovery.cs index cde2e86..0478d5b 100644 --- a/src/Coralph/CopilotModelDiscovery.cs +++ b/src/Coralph/CopilotModelDiscovery.cs @@ -1,5 +1,5 @@ using System.Text.Json; -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Serilog; namespace Coralph; @@ -183,5 +183,5 @@ private sealed record ModelPolicyDto( string? Terms); private sealed record ModelBillingDto( - double Multiplier); + double? Multiplier); } diff --git a/src/Coralph/CopilotRunner.cs b/src/Coralph/CopilotRunner.cs index 762acf7..49cfbd6 100644 --- a/src/Coralph/CopilotRunner.cs +++ b/src/Coralph/CopilotRunner.cs @@ -1,6 +1,6 @@ using System.Linq; using Serilog; -using GitHub.Copilot.SDK; +using GitHub.Copilot; namespace Coralph; diff --git a/src/Coralph/CopilotSessionEventRouter.cs b/src/Coralph/CopilotSessionEventRouter.cs index 82c15d2..05e1d3b 100644 --- a/src/Coralph/CopilotSessionEventRouter.cs +++ b/src/Coralph/CopilotSessionEventRouter.cs @@ -1,9 +1,11 @@ using System.Text; -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Serilog; namespace Coralph; +#pragma warning disable GHCP001 + internal sealed class CopilotSessionEventRouter( LoopOptions opt, EventStreamWriter? eventStream, @@ -292,7 +294,7 @@ internal void HandleEvent(SessionEvent evt) ["cost"] = assistantUsage.Data.Cost, ["duration"] = assistantUsage.Data.Duration, ["initiator"] = assistantUsage.Data.Initiator, - ["quotaSnapshots"] = assistantUsage.Data.QuotaSnapshots + ["quotaSnapshots"] = TryGetPropertyValue(assistantUsage.Data, "QuotaSnapshots") }, state: state); break; case SystemNotificationEvent notification: @@ -480,6 +482,11 @@ private void Emit( }; } + private static object? TryGetPropertyValue(object target, string propertyName) + { + return target.GetType().GetProperty(propertyName)?.GetValue(target); + } + private string ResolveAssistantMessageId(TurnState state, string? messageId) { if (!string.IsNullOrWhiteSpace(messageId)) diff --git a/src/Coralph/CopilotSessionRunner.cs b/src/Coralph/CopilotSessionRunner.cs index 91e01b9..afbff4b 100644 --- a/src/Coralph/CopilotSessionRunner.cs +++ b/src/Coralph/CopilotSessionRunner.cs @@ -1,4 +1,4 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; using Serilog; namespace Coralph; diff --git a/src/Coralph/CopilotSystemMessageFactory.cs b/src/Coralph/CopilotSystemMessageFactory.cs index 655b6fc..e331908 100644 --- a/src/Coralph/CopilotSystemMessageFactory.cs +++ b/src/Coralph/CopilotSystemMessageFactory.cs @@ -1,4 +1,4 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; namespace Coralph; @@ -15,14 +15,14 @@ internal static SystemMessageConfig Create(LoopOptions options) return new SystemMessageConfig { Mode = SystemMessageMode.Customize, - Sections = new Dictionary + Sections = new Dictionary { - [SystemPromptSections.Tone] = new() + [SystemMessageSection.Tone] = new() { Action = SectionOverrideAction.Append, Content = "\n- Be concise, direct, and execution-focused." }, - [SystemPromptSections.Guidelines] = new() + [SystemMessageSection.Guidelines] = new() { Action = SectionOverrideAction.Append, Content = @@ -30,14 +30,14 @@ internal static SystemMessageConfig Create(LoopOptions options) "\n- Match the repository's existing conventions, libraries, and coding style." + "\n- Prefer small, targeted changes that preserve existing behavior unless the task requires broader edits." }, - [SystemPromptSections.ToolInstructions] = new() + [SystemMessageSection.ToolInstructions] = new() { Action = SectionOverrideAction.Append, Content = "\n- Prefer Coralph's internal read-only tools and available repository context before broader exploration when they can answer the question." + "\n- Do not stop to ask for interactive input during a loop run." }, - [SystemPromptSections.Safety] = new() + [SystemMessageSection.Safety] = new() { Action = SectionOverrideAction.Append, Content = diff --git a/src/Coralph/Coralph.csproj b/src/Coralph/Coralph.csproj index e3730a3..a7bc4b8 100644 --- a/src/Coralph/Coralph.csproj +++ b/src/Coralph/Coralph.csproj @@ -43,7 +43,7 @@ - + diff --git a/src/Coralph/PermissionPolicy.cs b/src/Coralph/PermissionPolicy.cs index 7594893..72e25c2 100644 --- a/src/Coralph/PermissionPolicy.cs +++ b/src/Coralph/PermissionPolicy.cs @@ -1,9 +1,12 @@ using System.Collections; +using GitHub.Copilot; +using GitHub.Copilot.Rpc; using Serilog; -using GitHub.Copilot.SDK; namespace Coralph; +#pragma warning disable GHCP001 + /// /// Coralph permission policy for Copilot tool requests. /// @@ -54,7 +57,7 @@ internal PermissionPolicy(LoopOptions opt, EventStreamWriter? eventStream) _eventStream = eventStream; } - internal Task HandleAsync(PermissionRequest request, PermissionInvocation invocation) + internal Task HandleAsync(PermissionRequest request, PermissionInvocation invocation) { var kind = request.Kind; if (string.IsNullOrWhiteSpace(kind)) @@ -66,50 +69,49 @@ internal Task HandleAsync(PermissionRequest request, Pe var candidates = BuildCandidates(kind, toolName); var decision = EvaluateDecision(candidates, out var matchedRule); - var resultKind = decision == PermissionDecision.Allow - ? PermissionRequestResultKind.Approved - : PermissionRequestResultKind.Rejected; EmitDecision(kind, toolName, candidates, decision, matchedRule); - return Task.FromResult(new PermissionRequestResult { Kind = resultKind }); + return Task.FromResult(decision == ToolPermissionDecision.Allow + ? PermissionDecision.ApproveOnce() + : PermissionDecision.Reject("Rejected by Coralph permission policy.")); } - private PermissionDecision EvaluateDecision(IReadOnlyList candidates, out string? matchedRule) + private ToolPermissionDecision EvaluateDecision(IReadOnlyList candidates, out string? matchedRule) { if (MatchesRuleSet(_userDeny, candidates, out matchedRule)) { - return PermissionDecision.Deny; + return ToolPermissionDecision.Deny; } if (MatchesRuleSet(_userAllow, candidates, out matchedRule)) { - return PermissionDecision.Allow; + return ToolPermissionDecision.Allow; } if (MatchesRuleSet(DefaultDangerousRules, candidates, out matchedRule)) { - return PermissionDecision.Deny; + return ToolPermissionDecision.Deny; } if (_hasExplicitAllowList) { matchedRule = null; - return PermissionDecision.Deny; + return ToolPermissionDecision.Deny; } matchedRule = null; - return PermissionDecision.Allow; + return ToolPermissionDecision.Allow; } private void EmitDecision( string? kind, string? toolName, IReadOnlyList candidates, - PermissionDecision decision, + ToolPermissionDecision decision, string? matchedRule) { - if (decision == PermissionDecision.Deny) + if (decision == ToolPermissionDecision.Deny) { Log.Warning( "Permission denied (Kind={Kind}, Tool={Tool}, Rule={Rule})", @@ -123,7 +125,7 @@ private void EmitDecision( ["kind"] = kind, ["toolName"] = toolName, ["candidates"] = candidates, - ["decision"] = decision == PermissionDecision.Allow ? "approved" : "denied", + ["decision"] = decision == ToolPermissionDecision.Allow ? "approved" : "denied", ["matchedRule"] = matchedRule }); } @@ -219,6 +221,12 @@ private static HashSet NormalizeEntries(IEnumerable? entries) private static string? TryGetToolName(PermissionRequest request) { + var toolNameProperty = TryGetStringProperty(request, "ToolName"); + if (!string.IsNullOrWhiteSpace(toolNameProperty)) + { + return toolNameProperty; + } + var extra = TryGetExtraDictionary(request); if (extra is null) { @@ -277,7 +285,7 @@ private static bool TryGetStringFromDictionary(IDictionary dict, string key, out return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } - private enum PermissionDecision + private enum ToolPermissionDecision { Allow, Deny diff --git a/src/Coralph/ProviderConfigFactory.cs b/src/Coralph/ProviderConfigFactory.cs index 218d222..3c105f0 100644 --- a/src/Coralph/ProviderConfigFactory.cs +++ b/src/Coralph/ProviderConfigFactory.cs @@ -1,4 +1,4 @@ -using GitHub.Copilot.SDK; +using GitHub.Copilot; namespace Coralph; diff --git a/src/Coralph/packages.lock.json b/src/Coralph/packages.lock.json index 51bbce0..8712189 100644 --- a/src/Coralph/packages.lock.json +++ b/src/Coralph/packages.lock.json @@ -4,13 +4,12 @@ "net10.0": { "GitHub.Copilot.SDK": { "type": "Direct", - "requested": "[0.3.0, )", - "resolved": "0.3.0", - "contentHash": "zTmlbLWsmmZT/v/vbvP66d73t3sJ92gSavCe+52MtD1jwOJsGv5ExL8CY/jxA9/3GQt50LZs3p+ntjBpe/+Rvw==", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "hAeuf54OVdFxEtf0AlMgiQBNpeMy7rSbr6Y1bJWjeln8U71D5QEryje8auVQATt3l5ubdbmWGFnO9VOnBhWCfw==", "dependencies": { "Microsoft.Extensions.AI.Abstractions": "10.2.0", - "Microsoft.Extensions.Logging.Abstractions": "10.0.2", - "StreamJsonRpc": "2.24.84" + "Microsoft.Extensions.Logging.Abstractions": "10.0.2" } }, "Hex1b": { @@ -99,20 +98,6 @@ "resolved": "2.0.0-beta4.22272.1", "contentHash": "1uqED/q2H0kKoLJ4+hI2iPSBSEdTuhfCYADeJrAqERmiGQ2NNacYKRNEQ+gFbU4glgVyK8rxI+ZOe1onEtr/Pg==" }, - "MessagePack": { - "type": "Transitive", - "resolved": "2.5.198", - "contentHash": "ul2rGISMatBL4AbXTteEml4xtOsKF8yClEkS0rnrMyFs6Cu3KTbdZwOXrcbEUjGFIDiCwHuYIGyS55FeiaYzBw==", - "dependencies": { - "MessagePack.Annotations": "2.5.198", - "Microsoft.NET.StringTools": "17.6.3" - } - }, - "MessagePack.Annotations": { - "type": "Transitive", - "resolved": "2.5.198", - "contentHash": "3U9OvqQGTra+Mz1k1zfNAScSdNHobnqtQ51qdMGUZppkNDZJl0X/igq6Qz5zDBLEZoYqZrFtZwFx6wBJHHI8BA==" - }, "Microsoft.Extensions.AI.Abstractions": { "type": "Transitive", "resolved": "10.2.0", @@ -205,58 +190,11 @@ "resolved": "8.0.0", "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" }, - "Microsoft.NET.StringTools": { - "type": "Transitive", - "resolved": "18.0.2", - "contentHash": "cTZw3GHkAlqZACYGeQT3niS3UfVQ8CH0O5+zUdhxstrg1Z8Q2ViXYFKjSxHmEXTX85mrOT/QnHZOeQhhSsIrkQ==" - }, - "Microsoft.VisualStudio.Threading.Only": { - "type": "Transitive", - "resolved": "17.14.15", - "contentHash": "NqONyw1RXyj9P3k5e1uU2k9kc1ptwuU5NJQzG+MPq7vQVHUzBY8HLuJf/N2Rw5H/myD96CVxziDxmjawPuzntw==", - "dependencies": { - "Microsoft.VisualStudio.Validation": "17.8.8" - } - }, - "Microsoft.VisualStudio.Validation": { - "type": "Transitive", - "resolved": "17.13.22", - "contentHash": "fC20ITOxlUpGQ0ltAxITQbeFxwuB6OjLT3lByC4+/Gm46o/Mwv9hlIVrBhiUhnqdQzUUvr4EHkdiYe7WOSMLmw==" - }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" }, - "Nerdbank.MessagePack": { - "type": "Transitive", - "resolved": "1.0.2", - "contentHash": "mUcRO0trgc85qKPR360ybSvZTbm3nlSr1aK0JlKVWC5iC9OYxVLL1AbJxaW2Pbj4EQNCP8dKiz/SIlFPy67Ixw==", - "dependencies": { - "Microsoft.NET.StringTools": "18.0.2", - "Microsoft.VisualStudio.Validation": "17.13.22", - "PolyType": "1.0.0" - } - }, - "Nerdbank.Streams": { - "type": "Transitive", - "resolved": "2.13.16", - "contentHash": "GjifQ5M4IpQWjGNNbIrsj8M/M7ESb2328OeoGeqxk5rOJiuaAJ5zFc6CyqB+5UWKr1KEGK23hKoxA+z1gooAKA==", - "dependencies": { - "Microsoft.VisualStudio.Threading.Only": "17.13.61", - "Microsoft.VisualStudio.Validation": "17.8.8" - } - }, - "Newtonsoft.Json": { - "type": "Transitive", - "resolved": "13.0.3", - "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" - }, - "PolyType": { - "type": "Transitive", - "resolved": "1.0.0", - "contentHash": "q8vTQfDoKa/vCNMT+TvKbzsl+Z/s4k/qaP8WQGvNDIILkxKhP+N3NbFAOiglQljNmAj/ei51wO9jwcAI9jhtHw==" - }, "QRCoder": { "type": "Transitive", "resolved": "1.7.0", @@ -265,19 +203,6 @@ "System.Drawing.Common": "6.0.0" } }, - "StreamJsonRpc": { - "type": "Transitive", - "resolved": "2.24.84", - "contentHash": "7PtQlA7wYFBzOrcuALWGs1uPJI712KEYOMZXxxSKel9C0y5TPyjxXLkpw7d4O175rUKNbLHHbB89fPzMC2SLPg==", - "dependencies": { - "MessagePack": "2.5.198", - "Microsoft.VisualStudio.Threading.Only": "17.14.15", - "Microsoft.VisualStudio.Validation": "17.13.22", - "Nerdbank.MessagePack": "1.0.2", - "Nerdbank.Streams": "2.13.16", - "Newtonsoft.Json": "13.0.3" - } - }, "System.Drawing.Common": { "type": "Transitive", "resolved": "6.0.0",