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
12 changes: 6 additions & 6 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
<ModelContextProtocolVersion>2.0.0</ModelContextProtocolVersion>
<ModelContextProtocolVersion>2.2.0</ModelContextProtocolVersion>
<GitHubCopilotSdkVersion>1.0.8</GitHubCopilotSdkVersion>
</PropertyGroup>
<ItemGroup>
<!-- Miscellaneous Packages -->
<PackageVersion Include="A2A" Version="0.3.4-preview" />
<PackageVersion Include="A2A.AspNetCore" Version="0.3.4-preview" />
<PackageVersion Include="A2A" Version="1.0.0-preview2" />
<PackageVersion Include="A2A.AspNetCore" Version="1.0.0-preview2" />
<PackageVersion Include="Anthropic" Version="12.39.0" />
<PackageVersion Include="DocumentFormat.OpenXml" Version="3.5.1" />
<PackageVersion Include="Elastic.Clients.Elasticsearch" Version="[8.19.23,9.0.0)" />
Expand All @@ -28,9 +28,9 @@
<PackageVersion Include="PdfPig" Version="0.1.15" />
<PackageVersion Include="PDFsharp-MigraDoc" Version="6.2.4" />
<PackageVersion Include="SSH.NET" Version="2026.0.0" />
<PackageVersion Include="YesSql.Abstractions" Version="5.4.7" />
<PackageVersion Include="YesSql.Core" Version="5.4.7" />
<PackageVersion Include="YesSql.Provider.Sqlite" Version="5.4.7" />
<PackageVersion Include="YesSql.Abstractions" Version="6.0.0" />
<PackageVersion Include="YesSql.Core" Version="6.0.0" />
<PackageVersion Include="YesSql.Provider.Sqlite" Version="6.0.0" />
<PackageVersion Include="ZString" Version="2.6.0" />
</ItemGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,13 @@ public void Setup()
{
Name = $"Agent {connectionIndex}",
Description = $"Benchmark agent {connectionIndex}.",
Url = $"https://agent-{connectionIndex}.example",
SupportedInterfaces =
[
new AgentInterface
{
Url = $"https://agent-{connectionIndex}.example",
},
],
Version = "1.0",
Skills = skills,
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<NoWarn>$(NoWarn);CA1001;CA1305;CA1848</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand Down
5 changes: 5 additions & 0 deletions src/CrestApps.Core.Docs/docs/changelog/1.2.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ description: Release notes for the CrestApps.Core 1.2.0 release.

## Change Logs

- Updated the A2A client, proxy tools, MVC sample host, and Blazor sample host to use the A2A 1.0 preview protocol models and ASP.NET Core endpoint APIs.
- Updated A2A tests and benchmark build settings for the A2A 1.0 preview API and stricter analyzer defaults.
- Fixed the A2A client sample error handling so authentication redirects and non-JSON host responses produce actionable messages instead of JSON parser errors.
- Corrected the A2A client sample's standalone MVC and Blazor host endpoints to match each project's HTTPS launch URL and improved local TLS/connection error messages.
- Updated the Aspire sample host to set the effective A2A host options for local unauthenticated A2A sample-client calls.
- Downgraded YesSql to 5.4.7 to unblock the release of CrestApps.OrchardCore 2.1.
- upgrades SSH.NET to 2026.0.0 to address the high-severity
[GHSA-q939-rpr3-3284](https://github.com/advisories/GHSA-q939-rpr3-3284)
Expand Down
105 changes: 105 additions & 0 deletions src/Primitives/CrestApps.Core.AI.A2A/A2ALog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using Microsoft.Extensions.Logging;

namespace CrestApps.Core.AI.A2A;

internal static class A2ALog
{
private static readonly Action<ILogger, string, Exception> _failedToLoadAgentCardForConnection =
LoggerMessage.Define<string>(
LogLevel.Warning,
new EventId(1001, nameof(FailedToLoadAgentCardForConnection)),
"Failed to load agent card for A2A connection '{ConnectionId}'.");

private static readonly Action<ILogger, string, string, Exception> _failedToFetchAgentCardFromHost =
LoggerMessage.Define<string, string>(
LogLevel.Warning,
new EventId(1002, nameof(FailedToFetchAgentCardFromHost)),
"Failed to fetch agent card from A2A host '{Endpoint}' for connection '{ConnectionId}'.");

private static readonly Action<ILogger, string, string, Exception> _failedToCommunicateWithRemoteAgent =
LoggerMessage.Define<string, string>(
LogLevel.Error,
new EventId(1003, nameof(FailedToCommunicateWithRemoteAgent)),
"Failed to communicate with remote A2A agent '{AgentName}' at '{Endpoint}'.");

private static readonly Action<ILogger, Exception> _failedToListLocalAgentProfiles =
LoggerMessage.Define(
LogLevel.Warning,
new EventId(1004, nameof(FailedToListLocalAgentProfiles)),
"Failed to list local agent profiles.");

private static readonly Action<ILogger, string, Exception> _failedToFetchAgentCardFromConnection =
LoggerMessage.Define<string>(
LogLevel.Warning,
new EventId(1005, nameof(FailedToFetchAgentCardFromConnection)),
"Failed to fetch agent card from A2A connection '{ConnectionId}'.");

private static readonly Action<ILogger, Exception> _failedToListRemoteA2AAgents =
LoggerMessage.Define(
LogLevel.Warning,
new EventId(1006, nameof(FailedToListRemoteA2AAgents)),
"Failed to list remote A2A agents.");

private static readonly Action<ILogger, Exception> _failedToSearchForTools =
LoggerMessage.Define(
LogLevel.Warning,
new EventId(1007, nameof(FailedToSearchForTools)),
"Failed to search for tools.");

private static readonly Action<ILogger, Exception> _failedToSearchLocalAgentProfiles =
LoggerMessage.Define(
LogLevel.Warning,
new EventId(1008, nameof(FailedToSearchLocalAgentProfiles)),
"Failed to search local agent profiles.");

private static readonly Action<ILogger, Exception> _failedToSearchRemoteA2AAgents =
LoggerMessage.Define(
LogLevel.Warning,
new EventId(1009, nameof(FailedToSearchRemoteA2AAgents)),
"Failed to search remote A2A agents.");

public static void FailedToLoadAgentCardForConnection(ILogger logger, string connectionId, Exception exception)
{
_failedToLoadAgentCardForConnection(logger, connectionId, exception);
}

public static void FailedToFetchAgentCardFromHost(ILogger logger, string endpoint, string connectionId, Exception exception)
{
_failedToFetchAgentCardFromHost(logger, endpoint, connectionId, exception);
}

public static void FailedToCommunicateWithRemoteAgent(ILogger logger, string agentName, string endpoint, Exception exception)
{
_failedToCommunicateWithRemoteAgent(logger, agentName, endpoint, exception);
}

public static void FailedToListLocalAgentProfiles(ILogger logger, Exception exception)
{
_failedToListLocalAgentProfiles(logger, exception);
}

public static void FailedToFetchAgentCardFromConnection(ILogger logger, string connectionId, Exception exception)
{
_failedToFetchAgentCardFromConnection(logger, connectionId, exception);
}

public static void FailedToListRemoteA2AAgents(ILogger logger, Exception exception)
{
_failedToListRemoteA2AAgents(logger, exception);
}

public static void FailedToSearchForTools(ILogger logger, Exception exception)
{
_failedToSearchForTools(logger, exception);
}

public static void FailedToSearchLocalAgentProfiles(ILogger logger, Exception exception)
{
_failedToSearchLocalAgentProfiles(logger, exception);
}

public static void FailedToSearchRemoteA2AAgents(ILogger logger, Exception exception)
{
_failedToSearchRemoteA2AAgents(logger, exception);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using CrestApps.Core.AI.A2A;
using CrestApps.Core.AI.A2A.Models;
using CrestApps.Core.AI.A2A.Services;
using CrestApps.Core.AI.Extensions;
Expand Down Expand Up @@ -106,7 +107,7 @@ protected override async ValueTask<object> InvokeCoreAsync(AIFunctionArguments a
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to search local agent profiles.");
A2ALog.FailedToSearchLocalAgentProfiles(logger, ex);
}

try
Expand Down Expand Up @@ -143,13 +144,13 @@ protected override async ValueTask<object> InvokeCoreAsync(AIFunctionArguments a
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to fetch agent card from A2A connection '{ConnectionId}'.", connection.ItemId);
A2ALog.FailedToFetchAgentCardFromConnection(logger, connection.ItemId, ex);
}
}
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to search remote A2A agents.");
A2ALog.FailedToSearchRemoteA2AAgents(logger, ex);
}

var results = scoredAgents.Where(s => s.Score > 0).OrderByDescending(s => s.Score).Take(maxResults).Select(s => s.Agent).ToList();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using CrestApps.Core.AI.A2A;
using CrestApps.Core.AI.A2A.Models;
using CrestApps.Core.AI.Extensions;
using CrestApps.Core.AI.Models;
Expand Down Expand Up @@ -111,7 +112,7 @@ protected override async ValueTask<object> InvokeCoreAsync(AIFunctionArguments a
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to search for tools.");
A2ALog.FailedToSearchForTools(logger, ex);

return "An error occurred while searching for tools.";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using CrestApps.Core.AI.A2A;
using CrestApps.Core.AI.A2A.Models;
using CrestApps.Core.AI.A2A.Services;
using CrestApps.Core.AI.Models;
Expand Down Expand Up @@ -66,7 +67,7 @@ protected override async ValueTask<object> InvokeCoreAsync(AIFunctionArguments a
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to list local agent profiles.");
A2ALog.FailedToListLocalAgentProfiles(logger, ex);
}

try
Expand Down Expand Up @@ -96,13 +97,13 @@ protected override async ValueTask<object> InvokeCoreAsync(AIFunctionArguments a
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to fetch agent card from A2A connection '{ConnectionId}'.", connection.ItemId);
A2ALog.FailedToFetchAgentCardFromConnection(logger, connection.ItemId, ex);
}
}
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to list remote A2A agents.");
A2ALog.FailedToListRemoteA2AAgents(logger, ex);
}

return agents.Count > 0 ? JsonSerializer.Serialize(agents) : "No agents are currently available.";
Expand Down
32 changes: 18 additions & 14 deletions src/Primitives/CrestApps.Core.AI.A2A/Services/A2AAgentProxyTool.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.Json;
using A2A;
using CrestApps.Core.AI.A2A;
using CrestApps.Core.AI.A2A.Models;
using CrestApps.Core.Services;
using Microsoft.Extensions.AI;
Expand Down Expand Up @@ -111,24 +112,24 @@ protected override async ValueTask<object> InvokeCoreAsync(

var client = new A2AClient(new Uri(_endpoint), httpClient);

var agentMessage = new AgentMessage
var agentMessage = new Message
{
Role = MessageRole.User,
Role = Role.User,
MessageId = Guid.NewGuid().ToString(),
ContextId = contextId ?? Guid.NewGuid().ToString(),
Parts = [new TextPart { Text = message }],
Parts = [Part.FromText(message)],
Metadata = new Dictionary<string, JsonElement>
{
["agentName"] = JsonSerializer.SerializeToElement(_agentName),
},
};

var sendParams = new MessageSendParams
var sendRequest = new SendMessageRequest
{
Message = agentMessage,
};

var response = await client.SendMessageAsync(sendParams, cancellationToken);
var response = await client.SendMessageAsync(sendRequest, cancellationToken);

var responseText = ExtractTextFromResponse(response);

Expand All @@ -140,7 +141,7 @@ protected override async ValueTask<object> InvokeCoreAsync(
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to communicate with remote A2A agent '{AgentName}' at '{Endpoint}'.", _agentName, _endpoint);
A2ALog.FailedToCommunicateWithRemoteAgent(logger, _agentName, _endpoint, ex);

return $"An error occurred while communicating with remote agent '{_agentName}'.";
}
Expand Down Expand Up @@ -185,24 +186,25 @@ private static bool TryGetString(
/// </summary>
/// <param name="response">The A2A response.</param>
/// <returns>The response text, or <see langword="null"/> when no usable text exists.</returns>
internal static string ExtractTextFromResponse(A2AResponse response)
internal static string ExtractTextFromResponse(SendMessageResponse response)
{
if (response is AgentMessage message)
if (response.Message is { } message)
{
var texts = message.Parts?.OfType<TextPart>().Select(p => p.Text);
var texts = message.Parts.Select(p => p.Text).OfType<string>();

if (texts?.Any() == true)
if (texts.Any())
{
return string.Join(string.Empty, texts);
}
}
else if (response is AgentTask task)
else if (response.Task is { } task)
{
if (task.Artifacts?.Count > 0)
{
var artifactTexts = task.Artifacts
.SelectMany(a => a.Parts?.OfType<TextPart>() ?? [])
.Select(p => p.Text);
.SelectMany(a => a.Parts ?? [])
.Select(p => p.Text)
.OfType<string>();

var combined = string.Join(string.Empty, artifactTexts);

Expand All @@ -214,7 +216,9 @@ internal static string ExtractTextFromResponse(A2AResponse response)

if (task.Status.Message?.Parts is not null)
{
var statusTexts = task.Status.Message.Parts.OfType<TextPart>().Select(p => p.Text);
var statusTexts = task.Status.Message.Parts
.Select(p => p.Text)
.OfType<string>();

var combined = string.Join(string.Empty, statusTexts);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using CrestApps.Core.AI.A2A;
using CrestApps.Core.AI.A2A.Models;
using CrestApps.Core.AI.Models;
using CrestApps.Core.AI.Tooling;
Expand Down Expand Up @@ -89,7 +90,7 @@ public async Task<IReadOnlyList<ToolRegistryEntry>> GetToolsAsync(
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to load agent card for A2A connection '{ConnectionId}'.", connectionId);
A2ALog.FailedToLoadAgentCardForConnection(_logger, connectionId, ex);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using A2A;
using CrestApps.Core.AI.A2A;
using CrestApps.Core.AI.A2A.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
Expand Down Expand Up @@ -75,7 +76,7 @@ public async Task<AgentCard> GetAgentCardAsync(string connectionId, A2AConnectio
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to fetch agent card from A2A host '{Endpoint}' for connection '{ConnectionId}'.", connection.Endpoint, connectionId);
A2ALog.FailedToFetchAgentCardFromHost(_logger, connection.Endpoint, connectionId, ex);

return null;
}
Expand Down
8 changes: 4 additions & 4 deletions src/Startup/CrestApps.Core.Aspire.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ void WriteCrashEntry(string label, object data)
options.EnvironmentVariables.Add("CrestApps__AI__Providers__Ollama__Connections__Default__Endpoint", "http://localhost:11434");
options.EnvironmentVariables.Add("CrestApps__AI__Providers__Ollama__Connections__Default__ChatDeploymentName", ollamaModelName);
options.EnvironmentVariables.Add("CrestApps__MvcApp__MCP__Server__AuthenticationType", "None");
options.EnvironmentVariables.Add("CrestApps__MvcApp__A2A__Host__AuthenticationType", "None");
options.EnvironmentVariables.Add("CrestApps__MvcApp__A2A__Host__ExposeAgentsAsSkill", "true");
options.EnvironmentVariables.Add("A2AHostOptions__AuthenticationType", "None");
options.EnvironmentVariables.Add("A2AHostOptions__ExposeAgentsAsSkill", "true");
options.EnvironmentVariables.Add("CrestApps__PostgreSQL__ConnectionString", postgres.Resource.ConnectionStringExpression);

// Prevent VS-injected startup hooks (BrowserRefresh, DeltaApplier, BrowserLink)
Expand All @@ -102,8 +102,8 @@ void WriteCrashEntry(string label, object data)
options.EnvironmentVariables.Add("CrestApps__AI__Providers__Ollama__Connections__Default__Endpoint", "http://localhost:11434");
options.EnvironmentVariables.Add("CrestApps__AI__Providers__Ollama__Connections__Default__ChatDeploymentName", ollamaModelName);
options.EnvironmentVariables.Add("CrestApps__BlazorApp__MCP__Server__AuthenticationType", "None");
options.EnvironmentVariables.Add("CrestApps__BlazorApp__A2A__Host__AuthenticationType", "None");
options.EnvironmentVariables.Add("CrestApps__BlazorApp__A2A__Host__ExposeAgentsAsSkill", "true");
options.EnvironmentVariables.Add("A2AHostOptions__AuthenticationType", "None");
options.EnvironmentVariables.Add("A2AHostOptions__ExposeAgentsAsSkill", "true");
options.EnvironmentVariables.Add("CrestApps__PostgreSQL__ConnectionString", postgres.Resource.ConnectionStringExpression);

// Prevent VS-injected startup hooks (BrowserRefresh, DeltaApplier, BrowserLink)
Expand Down
Loading
Loading