Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 0 additions & 1 deletion dotnet/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<Project>

<PropertyGroup>
Comment thread
stephentoub marked this conversation as resolved.
<TargetFramework>net8.0</TargetFramework>
<LangVersion>14</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
Expand Down
6 changes: 6 additions & 0 deletions dotnet/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@

<ItemGroup>
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.2.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.102" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.2" />
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageVersion Include="System.Memory" Version="4.6.3" />
<PackageVersion Include="System.Text.Json" Version="10.0.2" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.2" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions dotnet/samples/Chat.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
Expand Down
11 changes: 9 additions & 2 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Data;
using System.Diagnostics;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
Expand Down Expand Up @@ -1239,7 +1240,7 @@ internal static async Task<T> InvokeRpcAsync<T>(JsonRpc rpc, string method, obje

if (!string.IsNullOrEmpty(stderrOutput))
{
throw new IOException(FormatCliExitedMessage("CLI process exited unexpectedly.", stderrOutput), ex);
throw new IOException(FormatCliExitedMessage("CLI process exited unexpectedly.", stderrOutput!), ex);
}
throw new IOException($"Communication error with Copilot CLI: {ex.Message}", ex);
}
Expand Down Expand Up @@ -1560,7 +1561,7 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex)
// Always use portable RID (e.g., linux-x64) to match the build-time placement,
// since distro-specific RIDs (e.g., ubuntu.24.04-x64) are normalized at build time.
var rid = GetPortableRid()
?? Path.GetFileName(System.Runtime.InteropServices.RuntimeInformation.RuntimeIdentifier);
?? Path.GetFileName(RuntimeInformation.RuntimeIdentifier);
searchedPath = Path.Combine(AppContext.BaseDirectory, "runtimes", rid, "native", binaryName);
return File.Exists(searchedPath) ? searchedPath : null;
}
Expand Down Expand Up @@ -2143,8 +2144,14 @@ internal record PermissionRequestResponseV2(
[JsonSerializable(typeof(UserInputResponse))]
internal partial class ClientJsonContext : JsonSerializerContext;

#if NET8_0_OR_GREATER
[GeneratedRegex(@"listening on port ([0-9]+)", RegexOptions.IgnoreCase)]
private static partial Regex ListeningOnPortRegex();
#else
private static readonly Regex s_listeningOnPortRegex = new(@"listening on port ([0-9]+)", RegexOptions.IgnoreCase);

private static Regex ListeningOnPortRegex() => s_listeningOnPortRegex;
#endif
}

/// <summary>
Expand Down
22 changes: 20 additions & 2 deletions dotnet/src/GitHub.Copilot.SDK.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0;net10.0;netstandard2.0</TargetFrameworks>
Comment thread
stephentoub marked this conversation as resolved.
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Version>0.1.0</Version>
<Description>SDK for programmatic control of GitHub Copilot CLI</Description>
Expand All @@ -13,7 +14,7 @@
<RepositoryUrl>https://github.com/github/copilot-sdk</RepositoryUrl>
<PackageIcon>copilot.png</PackageIcon>
<PackageTags>github;copilot;sdk;jsonrpc;agent</PackageTags>
<IsAotCompatible>true</IsAotCompatible>
<IsAotCompatible Condition="'$(TargetFramework)' != 'netstandard2.0'">true</IsAotCompatible>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
Expand All @@ -37,11 +38,28 @@
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="all" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != '' and !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net10.0'))">
<PackageReference Include="System.Text.Json" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="System.Collections.Immutable" />
<PackageReference Include="System.ComponentModel.Annotations" />
<PackageReference Include="System.Memory" />
<PackageReference Include="System.Threading.Channels" />
<PackageReference Include="System.Threading.Tasks.Extensions" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != 'netstandard2.0'">
<Compile Remove="Polyfills\**\*.cs" />
<Compile Include="Polyfills\IsExternalInit.cs" />
</ItemGroup>

<!-- Generate version props file at build time (gitignored) -->
<Target Name="_GenerateVersionProps" BeforeTargets="BeforeBuild;Pack">
<Target Name="_GenerateVersionProps" BeforeTargets="_DownloadCopilotCli;BeforeBuild;Pack">
<Exec Command="node -e &quot;console.log(require('./nodejs/package-lock.json').packages['node_modules/@github/copilot'].version)&quot;" WorkingDirectory="$(MSBuildThisFileDirectory)../.." ConsoleToMSBuild="true" StandardOutputImportance="low">
<Output TaskParameter="ConsoleOutput" PropertyName="CopilotCliVersion" />
</Exec>
Expand Down
36 changes: 27 additions & 9 deletions dotnet/src/JsonRpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Buffers;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using System.Text;
Expand Down Expand Up @@ -609,12 +608,11 @@ private async Task HandleIncomingMethodAsync(string methodName, JsonElement mess
return null;
}

if (result is not null && registration.ReturnsValueTaskOfT)
if (result is not null && registration.ValueTaskAsTaskMethod is { } valueTaskAsTaskMethod)
{
var resultType = result.GetType();
var asTask = (Task)resultType.GetMethod("AsTask")!.Invoke(result, null)!;
var asTask = (Task)valueTaskAsTaskMethod.Invoke(result, null)!;
await asTask.ConfigureAwait(false);
return asTask.GetType().GetProperty("Result")!.GetValue(asTask);
return registration.TaskResultGetter!.Invoke(asTask, null);
}

return result;
Expand Down Expand Up @@ -756,22 +754,42 @@ await SendMessageAsync(new JsonRpcNotification

private sealed class PendingRequest() : TaskCompletionSource<JsonElement>(TaskCreationOptions.RunContinuationsAsynchronously);

private static readonly MethodInfo s_taskGetResult = typeof(Task<>).GetProperty(nameof(Task<int>.Result), BindingFlags.Instance | BindingFlags.Public)!.GetMethod!;
private static readonly MethodInfo s_valueTaskAsTask = typeof(ValueTask<>).GetMethod(nameof(ValueTask<int>.AsTask), BindingFlags.Instance | BindingFlags.Public)!;

private sealed class MethodRegistration
{
public MethodRegistration(Delegate handler, bool singleObjectParam)
{
Handler = handler;
SingleObjectParam = singleObjectParam;
Parameters = handler.Method.GetParameters();
ReturnsValueTaskOfT =
handler.Method.ReturnType.IsGenericType &&
handler.Method.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>);
var returnType = handler.Method.ReturnType;
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
{
ValueTaskAsTaskMethod = GetMethodFromGenericMethodDefinition(returnType, s_valueTaskAsTask);
TaskResultGetter = GetMethodFromGenericMethodDefinition(ValueTaskAsTaskMethod.ReturnType, s_taskGetResult);
}
}

public Delegate Handler { get; }
public bool SingleObjectParam { get; }
public ParameterInfo[] Parameters { get; }
public bool ReturnsValueTaskOfT { get; }
public MethodInfo? ValueTaskAsTaskMethod { get; }
public MethodInfo? TaskResultGetter { get; }
}

private static MethodInfo GetMethodFromGenericMethodDefinition(Type specializedType, MethodInfo genericMethodDefinition)
{
Debug.Assert(
specializedType.IsGenericType && specializedType.GetGenericTypeDefinition() == genericMethodDefinition.DeclaringType,
"Generic member definition doesn't match type.");
#if NET8_0_OR_GREATER
return (MethodInfo)specializedType.GetMemberWithSameMetadataDefinitionAs(genericMethodDefinition);
#else
const BindingFlags All = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
return specializedType.GetMethods(All).First(m => m.MetadataToken == genericMethodDefinition.MetadataToken);
#endif
}

[JsonSourceGenerationOptions(
Expand Down
92 changes: 92 additions & 0 deletions dotnet/src/Polyfills/ArrayBufferWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/

namespace System.Buffers;

internal sealed class ArrayBufferWriter<T> : IBufferWriter<T>
{
private const int DefaultInitialBufferSize = 256;
private T[] _buffer;
private int _index;

public ArrayBufferWriter()
: this(DefaultInitialBufferSize)
{
}

public ArrayBufferWriter(int initialCapacity)
{
if (initialCapacity < 0)
{
throw new ArgumentOutOfRangeException(nameof(initialCapacity));
}

_buffer = initialCapacity == 0 ? [] : new T[initialCapacity];
}

public ReadOnlyMemory<T> WrittenMemory => _buffer.AsMemory(0, _index);

public ReadOnlySpan<T> WrittenSpan => _buffer.AsSpan(0, _index);

public int WrittenCount => _index;

public int Capacity => _buffer.Length;

public int FreeCapacity => _buffer.Length - _index;

public void Clear()
{
_buffer.AsSpan(0, _index).Clear();
_index = 0;
}

public void Advance(int count)
{
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count));
}

if (count > FreeCapacity)
{
throw new InvalidOperationException("Cannot advance past the end of the buffer.");
}

_index += count;
}

public Memory<T> GetMemory(int sizeHint = 0)
{
CheckAndResizeBuffer(sizeHint);
return _buffer.AsMemory(_index);
}

public Span<T> GetSpan(int sizeHint = 0)
{
CheckAndResizeBuffer(sizeHint);
return _buffer.AsSpan(_index);
}

private void CheckAndResizeBuffer(int sizeHint)
{
if (sizeHint < 0)
{
throw new ArgumentOutOfRangeException(nameof(sizeHint));
}

if (sizeHint == 0)
{
sizeHint = 1;
}

if (sizeHint <= FreeCapacity)
{
return;
}

var growBy = Math.Max(sizeHint, _buffer.Length);
var newSize = checked(_buffer.Length + growBy);
Array.Resize(ref _buffer, newSize);
}
}
29 changes: 29 additions & 0 deletions dotnet/src/Polyfills/BclAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/

namespace System.Runtime.CompilerServices;

[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
internal sealed class CallerArgumentExpressionAttribute : Attribute
{
public CallerArgumentExpressionAttribute(string parameterName) => ParameterName = parameterName;

public string ParameterName { get; }
}

[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
internal sealed class CompilerFeatureRequiredAttribute : Attribute
{
public const string RefStructs = nameof(RefStructs);
public const string RequiredMembers = nameof(RequiredMembers);

public CompilerFeatureRequiredAttribute(string featureName) => FeatureName = featureName;

public string FeatureName { get; }

public bool IsOptional { get; set; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal sealed class RequiredMemberAttribute : Attribute;
70 changes: 70 additions & 0 deletions dotnet/src/Polyfills/CodeAnalysisAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/

namespace System.Diagnostics.CodeAnalysis;

[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class ExperimentalAttribute : Attribute
{
public ExperimentalAttribute(string diagnosticId) => DiagnosticId = diagnosticId;

public string DiagnosticId { get; }

public string? UrlFormat { get; set; }
}

[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
internal sealed class NotNullWhenAttribute : Attribute
{
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;

public bool ReturnValue { get; }
}

[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
internal sealed class SetsRequiredMembersAttribute : Attribute;

[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal sealed class StringSyntaxAttribute : Attribute
{
public const string Uri = nameof(Uri);

public StringSyntaxAttribute(string syntax)
{
Syntax = syntax;
Arguments = [];
}

public StringSyntaxAttribute(string syntax, params object?[] arguments)
{
Syntax = syntax;
Arguments = arguments;
}

public string Syntax { get; }

public object?[] Arguments { get; }
}

[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
internal sealed class UnconditionalSuppressMessageAttribute : Attribute
{
public UnconditionalSuppressMessageAttribute(string category, string checkId)
{
Category = category;
CheckId = checkId;
}

public string Category { get; }

public string CheckId { get; }

public string? Scope { get; set; }

public string? Target { get; set; }

public string? MessageId { get; set; }

public string? Justification { get; set; }
}
Loading
Loading