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
7 changes: 2 additions & 5 deletions .github/workflows/dotnet-sdk-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ on:
- 'test/**'
- 'nodejs/package.json'
- '.github/workflows/dotnet-sdk-tests.yml'
- '.github/actions/setup-copilot/**'
- '!**/*.md'
- '!**/LICENSE*'
- '!**/.gitignore'
Expand Down Expand Up @@ -39,17 +38,16 @@ jobs:
working-directory: ./dotnet
steps:
- uses: actions/checkout@v6.0.2
- uses: ./.github/actions/setup-copilot
id: setup-copilot
- uses: actions/setup-dotnet@v5
with:
dotnet-version: "8.0.x"
- uses: actions/setup-node@v6
with:
node-version: "24"
cache: "npm"
cache-dependency-path: "./nodejs/package-lock.json"

- name: Install Node.js dependencies (for CLI)
- name: Install Node.js dependencies (for CLI version extraction)
working-directory: ./nodejs
run: npm ci --ignore-scripts

Expand Down Expand Up @@ -80,5 +78,4 @@ jobs:
- name: Run .NET SDK tests
env:
COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }}
COPILOT_CLI_PATH: ${{ steps.setup-copilot.outputs.cli-path }}
run: dotnet test --no-build -v n
6 changes: 1 addition & 5 deletions .github/workflows/nodejs-sdk-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ on:
- 'nodejs/**'
- 'test/**'
- '.github/workflows/nodejs-sdk-tests.yml'
- '.github/actions/setup-copilot/**'
- '!**/*.md'
- '!**/LICENSE*'
- '!**/.gitignore'
Expand Down Expand Up @@ -45,9 +44,7 @@ jobs:
with:
cache: "npm"
cache-dependency-path: "./nodejs/package-lock.json"
node-version: 22
- uses: ./.github/actions/setup-copilot
id: setup-copilot
node-version: 24
- name: Install dependencies
run: npm ci --ignore-scripts

Expand All @@ -72,5 +69,4 @@ jobs:
- name: Run Node.js SDK tests
env:
COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }}
COPILOT_CLI_PATH: ${{ steps.setup-copilot.outputs.cli-path }}
run: npm test
14 changes: 12 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ jobs:
name: nodejs-package
path: nodejs/*.tgz
- name: Publish to npm
if: github.ref == 'refs/heads/main'
run: npm publish --tag ${{ github.event.inputs.dist-tag }} --access public --registry https://registry.npmjs.org

publish-dotnet:
Expand All @@ -130,6 +131,7 @@ jobs:
name: dotnet-package
path: dotnet/artifacts/*.nupkg
- name: NuGet login (OIDC)
if: github.ref == 'refs/heads/main'
uses: NuGet/login@v1
id: nuget-login
with:
Expand All @@ -139,6 +141,7 @@ jobs:
# are associated with individual maintainers' accounts too.
user: stevesanderson
- name: Publish to NuGet
if: github.ref == 'refs/heads/main'
run: dotnet nuget push ./artifacts/*.nupkg --api-key ${{ steps.nuget-login.outputs.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate

publish-python:
Expand All @@ -153,18 +156,25 @@ jobs:
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- uses: actions/setup-node@v6
with:
node-version: "22.x"
- name: Set up uv
uses: astral-sh/setup-uv@v7
- name: Install Node.js dependencies (for CLI version)
working-directory: ./nodejs
run: npm ci --ignore-scripts
- name: Set version
run: sed -i "s/^version = .*/version = \"${{ needs.version.outputs.version }}\"/" pyproject.toml
- name: Build package
run: uv build
- name: Build platform wheels
run: node scripts/build-wheels.mjs --output-dir dist
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: python-package
path: python/dist/*
- name: Publish to PyPI
if: github.ref == 'refs/heads/main'
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: python/dist/
Expand Down
13 changes: 9 additions & 4 deletions .github/workflows/python-sdk-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ on:
- 'test/**'
- 'nodejs/package.json'
- '.github/workflows/python-sdk-tests.yml'
- '.github/actions/setup-copilot/**'
- '!**/*.md'
- '!**/LICENSE*'
- '!**/.gitignore'
Expand Down Expand Up @@ -42,11 +41,14 @@ jobs:
working-directory: ./python
steps:
- uses: actions/checkout@v6.0.2
- uses: ./.github/actions/setup-copilot
id: setup-copilot
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- uses: actions/setup-node@v6
with:
node-version: "24"
cache: "npm"
cache-dependency-path: "./nodejs/package-lock.json"

- name: Set up uv
uses: astral-sh/setup-uv@v7
Expand All @@ -56,6 +58,10 @@ jobs:
- name: Install Python dev dependencies
run: uv sync --locked --all-extras --dev

- name: Install Node.js dependencies (for CLI in tests)
working-directory: ./nodejs
run: npm ci --ignore-scripts

- name: Run ruff format check
run: uv run ruff format --check .

Expand All @@ -76,5 +82,4 @@ jobs:
- name: Run Python SDK tests
env:
COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }}
COPILOT_CLI_PATH: ${{ steps.setup-copilot.outputs.cli-path }}
run: uv run pytest -v -s
3 changes: 3 additions & 0 deletions dotnet/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
bin/
obj/

# Generated build props (contains CLI version)
src/build/GitHub.Copilot.SDK.props

# NuGet packages
*.nupkg
*.snupkg
Expand Down
19 changes: 11 additions & 8 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -867,7 +867,9 @@ private async Task VerifyProtocolVersionAsync(Connection connection, Cancellatio

private static async Task<(Process Process, int? DetectedLocalhostTcpPort)> StartCliServerAsync(CopilotClientOptions options, ILogger logger, CancellationToken cancellationToken)
{
var cliPath = options.CliPath ?? "copilot";
// Use explicit path or bundled CLI - no PATH fallback
var cliPath = options.CliPath ?? GetBundledCliPath(out var searchedPath)
?? throw new InvalidOperationException($"Copilot CLI not found at '{searchedPath}'. Ensure the SDK NuGet package was restored correctly or provide an explicit CliPath.");
var args = new List<string>();

if (options.CliArgs != null)
Expand Down Expand Up @@ -970,6 +972,14 @@ private async Task VerifyProtocolVersionAsync(Connection connection, Cancellatio
return (cliProcess, detectedLocalhostTcpPort);
}

private static string? GetBundledCliPath(out string searchedPath)
{
var binaryName = OperatingSystem.IsWindows() ? "copilot.exe" : "copilot";
var rid = Path.GetFileName(System.Runtime.InteropServices.RuntimeInformation.RuntimeIdentifier);
searchedPath = Path.Combine(AppContext.BaseDirectory, "runtimes", rid, "native", binaryName);
return File.Exists(searchedPath) ? searchedPath : null;
}

private static (string FileName, IEnumerable<string> Args) ResolveCliCommand(string cliPath, IEnumerable<string> args)
{
var isJsFile = cliPath.EndsWith(".js", StringComparison.OrdinalIgnoreCase);
Expand All @@ -979,13 +989,6 @@ private static (string FileName, IEnumerable<string> Args) ResolveCliCommand(str
return ("node", new[] { cliPath }.Concat(args));
}

// On Windows with UseShellExecute=false, Process.Start doesn't search PATHEXT,
// so use cmd /c to let the shell resolve the executable
if (OperatingSystem.IsWindows() && !Path.IsPathRooted(cliPath))
{
return ("cmd", new[] { "/c", cliPath }.Concat(args));
}

return (cliPath, args);
}

Expand Down
73 changes: 49 additions & 24 deletions dotnet/src/GitHub.Copilot.SDK.csproj
Original file line number Diff line number Diff line change
@@ -1,31 +1,56 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>0.1.0</Version>
<Description>SDK for programmatic control of GitHub Copilot CLI</Description>
<Authors>GitHub</Authors>
<Company>GitHub</Company>
<Copyright>Copyright (c) Microsoft Corporation. All rights reserved.</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/github/copilot-sdk</RepositoryUrl>
<PackageTags>github;copilot;sdk;jsonrpc;agent</PackageTags>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>

<ItemGroup>
<None Include="../README.md" Pack="true" PackagePath="/" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" Version="10.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.2" />
<PackageReference Include="StreamJsonRpc" Version="2.24.84" PrivateAssets="compile" />
<PackageReference Include="System.Text.Json" Version="10.0.2" />
</ItemGroup>

<!-- Generate version props file at build time (gitignored) -->
<Target Name="_GenerateVersionProps" BeforeTargets="BeforeBuild">
<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" />
<PropertyGroup>
<_VersionPropsContent>
<![CDATA[<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>0.1.0</Version>
<Description>SDK for programmatic control of GitHub Copilot CLI</Description>
<Authors>GitHub</Authors>
<Company>GitHub</Company>
<Copyright>Copyright (c) Microsoft Corporation. All rights reserved.</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/github/copilot-sdk</RepositoryUrl>
<PackageTags>github;copilot;sdk;jsonrpc;agent</PackageTags>
<IsAotCompatible>true</IsAotCompatible>
<CopilotCliVersion>$(CopilotCliVersion)</CopilotCliVersion>
</PropertyGroup>
</Project>]]>
</_VersionPropsContent>
</PropertyGroup>
<WriteLinesToFile File="$(MSBuildThisFileDirectory)build\GitHub.Copilot.SDK.props" Lines="$(_VersionPropsContent)" Overwrite="true" WriteOnlyWhenDifferent="true" />
</Target>

<ItemGroup>
<None Include="../README.md" Pack="true" PackagePath="/" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" Version="10.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.2" />
<PackageReference Include="StreamJsonRpc" Version="2.24.84" PrivateAssets="compile" />
<PackageReference Include="System.Text.Json" Version="10.0.2" />
</ItemGroup>
<!-- Include .targets and .props files in package -->
<!-- Also import the .targets for local dev (same logic consumers get) -->
<ItemGroup>
<None Include="build\GitHub.Copilot.SDK.*" Pack="true" PackagePath="build\" CopyToOutputDirectory="Never" />
</ItemGroup>
<Import Project="build\GitHub.Copilot.SDK.targets" />

</Project>
3 changes: 3 additions & 0 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public enum ConnectionState

public class CopilotClientOptions
{
/// <summary>
/// Path to the Copilot CLI executable. If not specified, uses the bundled CLI from the SDK.
/// </summary>
public string? CliPath { get; set; }
public string[]? CliArgs { get; set; }
public string? Cwd { get; set; }
Expand Down
84 changes: 84 additions & 0 deletions dotnet/src/build/GitHub.Copilot.SDK.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<Project>
<!-- These targets run in consuming projects when they build -->
<!-- CopilotCliVersion is imported from GitHub.Copilot.SDK.props (generated at SDK build time, packaged alongside) -->
<Import Project="$(MSBuildThisFileDirectory)GitHub.Copilot.SDK.props" Condition="'$(CopilotCliVersion)' == '' And Exists('$(MSBuildThisFileDirectory)GitHub.Copilot.SDK.props')" />

<!-- Resolve RID: use explicit RuntimeIdentifier, or infer from current machine -->
<PropertyGroup>
<_CopilotRid Condition="'$(RuntimeIdentifier)' != ''">$(RuntimeIdentifier)</_CopilotRid>
<_CopilotRid Condition="'$(_CopilotRid)' == '' And $([MSBuild]::IsOSPlatform('Windows')) And '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'X64'">win-x64</_CopilotRid>
<_CopilotRid Condition="'$(_CopilotRid)' == '' And $([MSBuild]::IsOSPlatform('Windows')) And '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'Arm64'">win-arm64</_CopilotRid>
<_CopilotRid Condition="'$(_CopilotRid)' == '' And $([MSBuild]::IsOSPlatform('Linux')) And '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'X64'">linux-x64</_CopilotRid>
<_CopilotRid Condition="'$(_CopilotRid)' == '' And $([MSBuild]::IsOSPlatform('Linux')) And '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'Arm64'">linux-arm64</_CopilotRid>
<_CopilotRid Condition="'$(_CopilotRid)' == '' And $([MSBuild]::IsOSPlatform('OSX')) And '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'X64'">osx-x64</_CopilotRid>
<_CopilotRid Condition="'$(_CopilotRid)' == '' And $([MSBuild]::IsOSPlatform('OSX')) And '$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)' == 'Arm64'">osx-arm64</_CopilotRid>
</PropertyGroup>

<!-- Map RID to platform name used in npm packages -->
<PropertyGroup>
<_CopilotPlatform Condition="'$(_CopilotRid)' == 'win-x64'">win32-x64</_CopilotPlatform>
<_CopilotPlatform Condition="'$(_CopilotRid)' == 'win-arm64'">win32-arm64</_CopilotPlatform>
<_CopilotPlatform Condition="'$(_CopilotRid)' == 'linux-x64'">linux-x64</_CopilotPlatform>
<_CopilotPlatform Condition="'$(_CopilotRid)' == 'linux-arm64'">linux-arm64</_CopilotPlatform>
<_CopilotPlatform Condition="'$(_CopilotRid)' == 'osx-x64'">darwin-x64</_CopilotPlatform>
<_CopilotPlatform Condition="'$(_CopilotRid)' == 'osx-arm64'">darwin-arm64</_CopilotPlatform>
<_CopilotBinary Condition="$(_CopilotRid.StartsWith('win-'))">copilot.exe</_CopilotBinary>
<_CopilotBinary Condition="'$(_CopilotBinary)' == ''">copilot</_CopilotBinary>
</PropertyGroup>

<!-- Download and extract CLI binary -->
<Target Name="_DownloadCopilotCli" BeforeTargets="BeforeBuild" Condition="'$(_CopilotPlatform)' != ''">
<Error Condition="'$(CopilotCliVersion)' == ''" Text="CopilotCliVersion is not set. The GitHub.Copilot.SDK.props file may be missing from the NuGet package." />

<!-- Compute paths using version (now available) -->
<PropertyGroup>
<_CopilotCacheDir>$(IntermediateOutputPath)copilot-cli\$(CopilotCliVersion)\$(_CopilotPlatform)</_CopilotCacheDir>
<_CopilotCliBinaryPath>$(_CopilotCacheDir)\$(_CopilotBinary)</_CopilotCliBinaryPath>
<_CopilotArchivePath>$(_CopilotCacheDir)\copilot.tgz</_CopilotArchivePath>
<_CopilotDownloadUrl>https://registry.npmjs.org/@github/copilot-$(_CopilotPlatform)/-/copilot-$(_CopilotPlatform)-$(CopilotCliVersion).tgz</_CopilotDownloadUrl>
</PropertyGroup>

<!-- Delete archive if binary missing (handles partial/corrupted downloads) -->
<Delete Files="$(_CopilotArchivePath)" Condition="!Exists('$(_CopilotCliBinaryPath)') And Exists('$(_CopilotArchivePath)')" />

<!-- Download if not cached -->
<MakeDir Directories="$(_CopilotCacheDir)" Condition="!Exists('$(_CopilotCliBinaryPath)')" />
<Message Importance="high" Text="Downloading Copilot CLI $(CopilotCliVersion) for $(_CopilotPlatform)..." Condition="!Exists('$(_CopilotCliBinaryPath)')" />
<DownloadFile SourceUrl="$(_CopilotDownloadUrl)" DestinationFolder="$(_CopilotCacheDir)" DestinationFileName="copilot.tgz"
Condition="!Exists('$(_CopilotCliBinaryPath)')" />

<!-- Extract using tar (use Windows system tar explicitly to avoid Git bash tar issues) -->
<PropertyGroup>
<_TarCommand Condition="$([MSBuild]::IsOSPlatform('Windows'))">$(SystemRoot)\System32\tar.exe</_TarCommand>
<_TarCommand Condition="'$(_TarCommand)' == ''">tar</_TarCommand>
</PropertyGroup>
<Exec Command="&quot;$(_TarCommand)&quot; -xzf &quot;$(_CopilotArchivePath)&quot; --strip-components=1 -C &quot;$(_CopilotCacheDir)&quot;"
Condition="!Exists('$(_CopilotCliBinaryPath)')" />

<Error Condition="!Exists('$(_CopilotCliBinaryPath)')" Text="Failed to extract Copilot CLI binary to $(_CopilotCliBinaryPath)" />
</Target>

<!-- Copy CLI binary to output runtimes folder and register for transitive copy -->
<Target Name="_CopyCopilotCliToOutput" AfterTargets="Build" DependsOnTargets="_DownloadCopilotCli" Condition="'$(_CopilotPlatform)' != ''">
<PropertyGroup>
<_CopilotCacheDir>$(IntermediateOutputPath)copilot-cli\$(CopilotCliVersion)\$(_CopilotPlatform)</_CopilotCacheDir>
<_CopilotCliBinaryPath>$(_CopilotCacheDir)\$(_CopilotBinary)</_CopilotCliBinaryPath>
<_CopilotOutputDir>$(OutDir)runtimes\$(_CopilotRid)\native</_CopilotOutputDir>
</PropertyGroup>
<MakeDir Directories="$(_CopilotOutputDir)" />
<Copy SourceFiles="$(_CopilotCliBinaryPath)" DestinationFolder="$(_CopilotOutputDir)" SkipUnchangedFiles="true" />
</Target>

<!-- Register CLI binary as content so it flows through project references -->
<Target Name="_RegisterCopilotCliForCopy" BeforeTargets="GetCopyToOutputDirectoryItems" DependsOnTargets="_DownloadCopilotCli" Condition="'$(_CopilotPlatform)' != ''">
<PropertyGroup>
<_CopilotCacheDir>$(IntermediateOutputPath)copilot-cli\$(CopilotCliVersion)\$(_CopilotPlatform)</_CopilotCacheDir>
<_CopilotCliBinaryPath>$(_CopilotCacheDir)\$(_CopilotBinary)</_CopilotCliBinaryPath>
</PropertyGroup>
<ItemGroup>
<ContentWithTargetPath Include="$(_CopilotCliBinaryPath)"
TargetPath="runtimes\$(_CopilotRid)\native\$(_CopilotBinary)"
CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Target>
</Project>
Loading
Loading