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
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
26 changes: 24 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,11 +14,12 @@
<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>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<_CopilotCliVersionTarget>_GetCopilotCliVersion</_CopilotCliVersionTarget>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -37,15 +39,35 @@
<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="_GetCopilotCliVersion" Condition="'$(CopilotCliVersion)' == ''">
<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>
<Error Condition="'$(CopilotCliVersion)' == ''" Text="CopilotCliVersion could not be read from nodejs/package-lock.json" />
</Target>

<Target Name="_GenerateVersionProps" DependsOnTargets="_GetCopilotCliVersion" BeforeTargets="Pack">
<PropertyGroup>
<_VersionPropsContent>
<![CDATA[<Project>
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