test
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
Python - Merge - Tests / paths-filter (push) Has been cancelled
Python - Merge - Tests / Python Tests - Core (integration, ubuntu-latest, 3.10) (push) Has been cancelled
Python - Merge - Tests / Python Tests - Azure AI (integration, ubuntu-latest, 3.10) (push) Has been cancelled
Python - Merge - Tests / python-integration-tests-check (push) Has been cancelled
Python - Lab Tests / paths-filter (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.10) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.11) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.12) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.13) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.14) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.10) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.11) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.12) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.13) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.14) (push) Has been cancelled
Check .md links / markdown-link-check (push) Has been cancelled
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
Python - Merge - Tests / paths-filter (push) Has been cancelled
Python - Merge - Tests / Python Tests - Core (integration, ubuntu-latest, 3.10) (push) Has been cancelled
Python - Merge - Tests / Python Tests - Azure AI (integration, ubuntu-latest, 3.10) (push) Has been cancelled
Python - Merge - Tests / python-integration-tests-check (push) Has been cancelled
Python - Lab Tests / paths-filter (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.10) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.11) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.12) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.13) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.14) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.10) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.11) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.12) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.13) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.14) (push) Has been cancelled
Check .md links / markdown-link-check (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<InjectSharedIntegrationTestCode>True</InjectSharedIntegrationTestCode>
|
||||
<InjectSharedThrow>true</InjectSharedThrow>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.CopilotStudio\Microsoft.Agents.AI.CopilotStudio.csproj" />
|
||||
<ProjectReference Include="..\AgentConformance.IntegrationTests\AgentConformance.IntegrationTests.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests;
|
||||
using AgentConformance.IntegrationTests.Support;
|
||||
using CopilotStudio.IntegrationTests.Support;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.CopilotStudio;
|
||||
using Microsoft.Agents.CopilotStudio.Client;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace CopilotStudio.IntegrationTests;
|
||||
|
||||
public class CopilotStudioFixture : IAgentFixture
|
||||
{
|
||||
public AIAgent Agent { get; private set; } = null!;
|
||||
|
||||
public Task<List<ChatMessage>> GetChatHistoryAsync(AgentThread thread) =>
|
||||
throw new NotSupportedException("CopilotStudio doesn't allow retrieval of chat history.");
|
||||
|
||||
public Task DeleteThreadAsync(AgentThread thread) =>
|
||||
// Chat Completion does not require/support deleting threads, so this is a no-op.
|
||||
Task.CompletedTask;
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
const string CopilotStudioHttpClientName = nameof(CopilotStudioAgent);
|
||||
|
||||
var config = TestConfiguration.LoadSection<CopilotStudioAgentConfiguration>();
|
||||
var settings = new CopilotStudioConnectionSettings(config.TenantId, config.AppClientId)
|
||||
{
|
||||
DirectConnectUrl = config.DirectConnectUrl,
|
||||
};
|
||||
|
||||
ServiceCollection services = new();
|
||||
|
||||
services
|
||||
.AddSingleton(settings)
|
||||
.AddSingleton<CopilotStudioTokenHandler>()
|
||||
.AddHttpClient(CopilotStudioHttpClientName)
|
||||
.ConfigurePrimaryHttpMessageHandler<CopilotStudioTokenHandler>();
|
||||
|
||||
IHttpClientFactory httpClientFactory =
|
||||
services
|
||||
.BuildServiceProvider()
|
||||
.GetRequiredService<IHttpClientFactory>();
|
||||
|
||||
CopilotClient client = new(settings, httpClientFactory, NullLogger.Instance, CopilotStudioHttpClientName);
|
||||
|
||||
this.Agent = new CopilotStudioAgent(client);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests;
|
||||
|
||||
namespace CopilotStudio.IntegrationTests;
|
||||
|
||||
public class CopilotStudioRunStreamingTests() : RunStreamingTests<CopilotStudioFixture>(() => new())
|
||||
{
|
||||
// Set to null to run the tests.
|
||||
private const string ManualVerification = "For manual verification";
|
||||
|
||||
[Fact(Skip = "Copilot Studio does not support thread history retrieval, so this test is not applicable.")]
|
||||
public override Task ThreadMaintainsHistoryAsync() =>
|
||||
Task.CompletedTask;
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithChatMessageReturnsExpectedResultAsync() =>
|
||||
base.RunWithChatMessageReturnsExpectedResultAsync();
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithChatMessagesReturnsExpectedResultAsync() =>
|
||||
base.RunWithChatMessagesReturnsExpectedResultAsync();
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync() =>
|
||||
base.RunWithNoMessageDoesNotFailAsync();
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithStringReturnsExpectedResultAsync() =>
|
||||
base.RunWithStringReturnsExpectedResultAsync();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using AgentConformance.IntegrationTests;
|
||||
|
||||
namespace CopilotStudio.IntegrationTests;
|
||||
|
||||
public class CopilotStudioRunTests() : RunTests<CopilotStudioFixture>(() => new())
|
||||
{
|
||||
// Set to null to run the tests.
|
||||
private const string ManualVerification = "For manual verification";
|
||||
|
||||
[Fact(Skip = "Copilot Studio does not support thread history retrieval, so this test is not applicable.")]
|
||||
public override Task ThreadMaintainsHistoryAsync() =>
|
||||
Task.CompletedTask;
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithChatMessageReturnsExpectedResultAsync() => base.RunWithChatMessageReturnsExpectedResultAsync();
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithChatMessagesReturnsExpectedResultAsync() =>
|
||||
|
||||
base.RunWithChatMessagesReturnsExpectedResultAsync();
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync() =>
|
||||
base.RunWithNoMessageDoesNotFailAsync();
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithStringReturnsExpectedResultAsync() =>
|
||||
base.RunWithStringReturnsExpectedResultAsync();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace CopilotStudio.IntegrationTests.Support;
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
#pragma warning disable CA1812 // Internal class that is apparently never instantiated.
|
||||
|
||||
internal sealed class CopilotStudioAgentConfiguration
|
||||
{
|
||||
public string DirectConnectUrl { get; set; }
|
||||
|
||||
public string TenantId { get; set; }
|
||||
|
||||
public string AppClientId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using Microsoft.Agents.CopilotStudio.Client;
|
||||
using Microsoft.Agents.CopilotStudio.Client.Discovery;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace CopilotStudio.IntegrationTests.Support;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ConnectionSettings"/> with additional properties to specify Application (Client) Id,
|
||||
/// Tenant Id, and optionally the Application Client secret.
|
||||
/// </summary>
|
||||
internal sealed class CopilotStudioConnectionSettings : ConnectionSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Application ID for creating the authentication for the connection
|
||||
/// </summary>
|
||||
public string AppClientId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Application secret for creating the authentication for the connection
|
||||
/// </summary>
|
||||
public string? AppClientSecret { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tenant ID for creating the authentication for the connection
|
||||
/// </summary>
|
||||
public string TenantId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Use interactive or service connection for authentication.
|
||||
/// Defaults to true, meaning interactive authentication will be used.
|
||||
/// </summary>
|
||||
public bool UseInteractiveAuthentication { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiate a new instance of the <see cref="CopilotStudioConnectionSettings"/> from provided settings.
|
||||
/// </summary>
|
||||
public CopilotStudioConnectionSettings(string tenantId, string appClientId, string? appClientSecret = null)
|
||||
{
|
||||
this.TenantId = tenantId;
|
||||
this.AppClientId = appClientId;
|
||||
this.AppClientSecret = appClientSecret;
|
||||
this.Cloud = PowerPlatformCloud.Prod;
|
||||
this.CopilotAgentType = AgentType.Published;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiate a new instance of the <see cref="CopilotStudioConnectionSettings"/> from a configuration section.
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public CopilotStudioConnectionSettings(IConfigurationSection config)
|
||||
: base(config)
|
||||
{
|
||||
this.AppClientId = config[nameof(this.AppClientId)] ?? throw new ArgumentException($"{nameof(this.AppClientId)} not found in config");
|
||||
this.TenantId = config[nameof(this.TenantId)] ?? throw new ArgumentException($"{nameof(this.TenantId)} not found in config");
|
||||
this.AppClientSecret = config[nameof(this.AppClientSecret)];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.CopilotStudio.Client;
|
||||
using Microsoft.Identity.Client;
|
||||
using Microsoft.Identity.Client.Extensions.Msal;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace CopilotStudio.IntegrationTests.Support;
|
||||
|
||||
#pragma warning disable CA1812 // Internal class that is apparently never instantiated.
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="HttpClientHandler"/> that adds an authentication token to the request headers for Copilot Studio API calls.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For more information on how to setup various authentication flows, see the Microsoft Identity documentation at https://aka.ms/msal.
|
||||
/// </remarks>
|
||||
internal sealed class CopilotStudioTokenHandler : HttpClientHandler
|
||||
{
|
||||
private const string AuthenticationHeader = "Bearer";
|
||||
private const string CacheFolderName = "mcs_client_console";
|
||||
private const string KeyChainServiceName = "copilot_studio_client_app";
|
||||
private const string KeyChainAccountName = "copilot_studio_client";
|
||||
|
||||
private readonly CopilotStudioConnectionSettings _settings;
|
||||
private readonly string[] _scopes;
|
||||
|
||||
private IConfidentialClientApplication? _clientApplication;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CopilotStudioTokenHandler"/> class with the specified connection settings.
|
||||
/// </summary>
|
||||
/// <param name="settings">The connection settings for Copilot Studio.</param>
|
||||
public CopilotStudioTokenHandler(CopilotStudioConnectionSettings settings)
|
||||
{
|
||||
Throw.IfNull(settings);
|
||||
|
||||
this._settings = settings;
|
||||
this._scopes = [CopilotClient.ScopeFromSettings(this._settings)];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (request.Headers.Authorization is null)
|
||||
{
|
||||
AuthenticationResult authResponse = await this.AuthenticateAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeader, authResponse.AccessToken);
|
||||
}
|
||||
|
||||
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Task<AuthenticationResult> AuthenticateAsync(CancellationToken cancellationToken) =>
|
||||
this._settings.UseInteractiveAuthentication ?
|
||||
this.AuthenticateInteractiveAsync(cancellationToken) :
|
||||
this.AuthenticateServiceAsync(cancellationToken);
|
||||
|
||||
private async Task<AuthenticationResult> AuthenticateServiceAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (this._clientApplication is null)
|
||||
{
|
||||
this._clientApplication = ConfidentialClientApplicationBuilder.Create(this._settings.AppClientId)
|
||||
.WithAuthority(AzureCloudInstance.AzurePublic, this._settings.TenantId)
|
||||
.WithClientSecret(this._settings.AppClientSecret)
|
||||
.Build();
|
||||
|
||||
MsalCacheHelper tokenCacheHelper = await CreateCacheHelperAsync("AppTokenCache").ConfigureAwait(false);
|
||||
tokenCacheHelper.RegisterCache(this._clientApplication.AppTokenCache);
|
||||
}
|
||||
|
||||
AuthenticationResult authResponse;
|
||||
|
||||
authResponse = await this._clientApplication.AcquireTokenForClient(this._scopes).ExecuteAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return authResponse;
|
||||
}
|
||||
|
||||
private async Task<AuthenticationResult> AuthenticateInteractiveAsync(CancellationToken cancellationToken = default!)
|
||||
{
|
||||
IPublicClientApplication app =
|
||||
PublicClientApplicationBuilder.Create(this._settings.AppClientId)
|
||||
.WithAuthority(AadAuthorityAudience.AzureAdMyOrg)
|
||||
.WithTenantId(this._settings.TenantId)
|
||||
.WithRedirectUri("http://localhost")
|
||||
.Build();
|
||||
|
||||
MsalCacheHelper tokenCacheHelper = await CreateCacheHelperAsync("TokenCache").ConfigureAwait(false);
|
||||
tokenCacheHelper.RegisterCache(app.UserTokenCache);
|
||||
|
||||
IEnumerable<IAccount> accounts = await app.GetAccountsAsync().ConfigureAwait(false);
|
||||
IAccount? account = accounts.FirstOrDefault();
|
||||
|
||||
AuthenticationResult authResponse;
|
||||
|
||||
try
|
||||
{
|
||||
authResponse = await app.AcquireTokenSilent(this._scopes, account).ExecuteAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (MsalUiRequiredException)
|
||||
{
|
||||
authResponse = await app.AcquireTokenInteractive(this._scopes).ExecuteAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return authResponse;
|
||||
}
|
||||
|
||||
private static async Task<MsalCacheHelper> CreateCacheHelperAsync(string cacheFileName)
|
||||
{
|
||||
string currentDir = Path.Combine(AppContext.BaseDirectory, CacheFolderName);
|
||||
|
||||
if (!Directory.Exists(currentDir))
|
||||
{
|
||||
Directory.CreateDirectory(currentDir);
|
||||
}
|
||||
|
||||
StorageCreationPropertiesBuilder storageProperties = new(cacheFileName, currentDir);
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
storageProperties.WithLinuxUnprotectedFile();
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
storageProperties.WithMacKeyChain(KeyChainServiceName, KeyChainAccountName);
|
||||
}
|
||||
|
||||
return await MsalCacheHelper.CreateAsync(storageProperties.Build()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user