chore: import upstream snapshot with attribution
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:25 +08:00
commit db620d33df
5151 changed files with 925932 additions and 0 deletions
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.Projects" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="System.Linq.AsyncEnumerable" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Hosting.A2A.AspNetCore\Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Hosting.A2A\Microsoft.Agents.AI.Hosting.A2A.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,85 @@
### Each A2A agent is available at a different host address
@hostInvoice = http://localhost:5000
@hostPolicy = http://localhost:5001
@hostLogistics = http://localhost:5002
### Query agent card for the invoice agent
GET {{hostInvoice}}/.well-known/agent-card.json
### Send a message to the invoice agent
POST {{hostInvoice}}
Content-Type: application/json
{
"id": "1",
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"id": "12345",
"message": {
"kind": "message",
"role": "user",
"messageId": "msg_1",
"parts": [
{
"kind": "text",
"text": "Show me all invoices for Contoso?"
}
]
}
}
}
### Query agent card for the policy agent
GET {{hostPolicy}}/.well-known/agent-card.json
### Send a message to the policy agent
POST {{hostPolicy}}
Content-Type: application/json
{
"id": "1",
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"id": "12345",
"message": {
"kind": "message",
"role": "user",
"messageId": "msg_1",
"parts": [
{
"kind": "text",
"text": "What is the policy for short shipments?"
}
]
}
}
}
### Query agent card for the logistics agent
GET {{hostLogistics}}/.well-known/agent-card.json
### Send a message to the logistics agent
POST {{hostLogistics}}
Content-Type: application/json
{
"id": "1",
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"id": "12345",
"message": {
"kind": "message",
"role": "user",
"messageId": "msg_1",
"parts": [
{
"kind": "text",
"text": "What is the status for SHPMT-SAP-001?"
}
]
}
}
}
@@ -0,0 +1,176 @@
// Copyright (c) Microsoft. All rights reserved.
using A2A;
using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Chat;
using AgentCard = A2A.AgentCard;
namespace A2AServer;
internal static class HostAgentFactory
{
internal static async Task<(AIAgent, AgentCard)> CreateFoundryHostAgentAsync(string agentType, string model, string endpoint, string agentName, string[] agentUrls, IList<AITool>? tools = null)
{
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
var aiProjectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());
ProjectsAgentRecord agentRecord = await aiProjectClient.AgentAdministrationClient.GetAgentAsync(agentName);
AIAgent agent = aiProjectClient.AsAIAgent(agentRecord, tools: tools);
AgentCard agentCard = agentType.ToUpperInvariant() switch
{
"INVOICE" => GetInvoiceAgentCard(agentUrls),
"POLICY" => GetPolicyAgentCard(agentUrls),
"LOGISTICS" => GetLogisticsAgentCard(agentUrls),
_ => throw new ArgumentException($"Unsupported agent type: {agentType}"),
};
return new(agent, agentCard);
}
internal static async Task<(AIAgent, AgentCard)> CreateChatCompletionHostAgentAsync(string agentType, string model, string apiKey, string name, string instructions, string[] agentUrls, IList<AITool>? tools = null)
{
AIAgent agent = new OpenAIClient(apiKey)
.GetChatClient(model)
.AsAIAgent(instructions, name, tools: tools);
AgentCard agentCard = agentType.ToUpperInvariant() switch
{
"INVOICE" => GetInvoiceAgentCard(agentUrls),
"POLICY" => GetPolicyAgentCard(agentUrls),
"LOGISTICS" => GetLogisticsAgentCard(agentUrls),
_ => throw new ArgumentException($"Unsupported agent type: {agentType}"),
};
return new(agent, agentCard);
}
#region private
private static AgentCard GetInvoiceAgentCard(string[] agentUrls)
{
var capabilities = new AgentCapabilities()
{
Streaming = false,
PushNotifications = false,
};
var invoiceQuery = new A2A.AgentSkill()
{
Id = "id_invoice_agent",
Name = "InvoiceQuery",
Description = "Handles requests relating to invoices.",
Tags = ["invoice", "semantic-kernel"],
Examples =
[
"List the latest invoices for Contoso.",
],
};
return new()
{
Name = "InvoiceAgent",
Description = "Handles requests relating to invoices.",
Version = "1.0.0",
DefaultInputModes = ["text"],
DefaultOutputModes = ["text"],
Capabilities = capabilities,
Skills = [invoiceQuery],
SupportedInterfaces = CreateAgentInterfaces(agentUrls)
};
}
private static AgentCard GetPolicyAgentCard(string[] agentUrls)
{
var capabilities = new AgentCapabilities()
{
Streaming = false,
PushNotifications = false,
};
var policyQuery = new A2A.AgentSkill()
{
Id = "id_policy_agent",
Name = "PolicyAgent",
Description = "Handles requests relating to policies and customer communications.",
Tags = ["policy", "semantic-kernel"],
Examples =
[
"What is the policy for short shipments?",
],
};
return new AgentCard()
{
Name = "PolicyAgent",
Description = "Handles requests relating to policies and customer communications.",
Version = "1.0.0",
DefaultInputModes = ["text"],
DefaultOutputModes = ["text"],
Capabilities = capabilities,
Skills = [policyQuery],
SupportedInterfaces = CreateAgentInterfaces(agentUrls)
};
}
private static AgentCard GetLogisticsAgentCard(string[] agentUrls)
{
var capabilities = new AgentCapabilities()
{
Streaming = false,
PushNotifications = false,
};
var logisticsQuery = new A2A.AgentSkill()
{
Id = "id_logistics_agent",
Name = "LogisticsQuery",
Description = "Handles requests relating to logistics.",
Tags = ["logistics", "semantic-kernel"],
Examples =
[
"What is the status for SHPMT-SAP-001",
],
};
return new AgentCard()
{
Name = "LogisticsAgent",
Description = "Handles requests relating to logistics.",
Version = "1.0.0",
DefaultInputModes = ["text"],
DefaultOutputModes = ["text"],
Capabilities = capabilities,
Skills = [logisticsQuery],
SupportedInterfaces = CreateAgentInterfaces(agentUrls)
};
}
private static List<AgentInterface> CreateAgentInterfaces(string[] agentUrls)
{
List<AgentInterface> agentInterfaces = [];
agentInterfaces.AddRange(agentUrls.Select(url => new AgentInterface
{
Url = url,
ProtocolBinding = ProtocolBindingNames.JsonRpc,
ProtocolVersion = "1.0",
}));
agentInterfaces.AddRange(agentUrls.Select(url => new AgentInterface
{
Url = url,
ProtocolBinding = ProtocolBindingNames.HttpJson,
ProtocolVersion = "1.0",
}));
return agentInterfaces;
}
#endregion
}
@@ -0,0 +1,167 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
namespace A2A;
/// <summary>
/// A simple invoice plugin that returns mock data.
/// </summary>
public class Product
{
public string Name { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; } // Price per unit
public Product(string name, int quantity, decimal price)
{
this.Name = name;
this.Quantity = quantity;
this.Price = price;
}
public decimal TotalPrice() => this.Quantity * this.Price; // Total price for this product
}
public class Invoice
{
public string TransactionId { get; set; }
public string InvoiceId { get; set; }
public string CompanyName { get; set; }
public DateTime InvoiceDate { get; set; }
public List<Product> Products { get; set; } // List of products
public Invoice(string transactionId, string invoiceId, string companyName, DateTime invoiceDate, List<Product> products)
{
this.TransactionId = transactionId;
this.InvoiceId = invoiceId;
this.CompanyName = companyName;
this.InvoiceDate = invoiceDate;
this.Products = products;
}
public decimal TotalInvoicePrice() => this.Products.Sum(product => product.TotalPrice()); // Total price of all products in the invoice
}
public class InvoiceQuery
{
private readonly List<Invoice> _invoices;
public InvoiceQuery()
{
// Extended mock data with quantities and prices
this._invoices =
[
new("TICKET-XYZ987", "INV789", "Contoso", GetRandomDateWithinLastTwoMonths(),
[
new("T-Shirts", 150, 10.00m),
new("Hats", 200, 15.00m),
new("Glasses", 300, 5.00m)
]),
new("TICKET-XYZ111", "INV111", "XStore", GetRandomDateWithinLastTwoMonths(),
[
new("T-Shirts", 2500, 12.00m),
new("Hats", 1500, 8.00m),
new("Glasses", 200, 20.00m)
]),
new("TICKET-XYZ222", "INV222", "Cymbal Direct", GetRandomDateWithinLastTwoMonths(),
[
new("T-Shirts", 1200, 14.00m),
new("Hats", 800, 7.00m),
new("Glasses", 500, 25.00m)
]),
new("TICKET-XYZ333", "INV333", "Contoso", GetRandomDateWithinLastTwoMonths(),
[
new("T-Shirts", 400, 11.00m),
new("Hats", 600, 15.00m),
new("Glasses", 700, 5.00m)
]),
new("TICKET-XYZ444", "INV444", "XStore", GetRandomDateWithinLastTwoMonths(),
[
new("T-Shirts", 800, 10.00m),
new("Hats", 500, 18.00m),
new("Glasses", 300, 22.00m)
]),
new("TICKET-XYZ555", "INV555", "Cymbal Direct", GetRandomDateWithinLastTwoMonths(),
[
new("T-Shirts", 1100, 9.00m),
new("Hats", 900, 12.00m),
new("Glasses", 1200, 15.00m)
]),
new("TICKET-XYZ666", "INV666", "Contoso", GetRandomDateWithinLastTwoMonths(),
[
new("T-Shirts", 2500, 8.00m),
new("Hats", 1200, 10.00m),
new("Glasses", 1000, 6.00m)
]),
new("TICKET-XYZ777", "INV777", "XStore", GetRandomDateWithinLastTwoMonths(),
[
new("T-Shirts", 1900, 13.00m),
new("Hats", 1300, 16.00m),
new("Glasses", 800, 19.00m)
]),
new("TICKET-XYZ888", "INV888", "Cymbal Direct", GetRandomDateWithinLastTwoMonths(),
[
new("T-Shirts", 2200, 11.00m),
new("Hats", 1700, 8.50m),
new("Glasses", 600, 21.00m)
]),
new("TICKET-XYZ999", "INV999", "Contoso", GetRandomDateWithinLastTwoMonths(),
[
new("T-Shirts", 1400, 10.50m),
new("Hats", 1100, 9.00m),
new("Glasses", 950, 12.00m)
])
];
}
public static DateTime GetRandomDateWithinLastTwoMonths()
{
// Get the current date and time
DateTime endDate = DateTime.UtcNow;
// Calculate the start date, which is two months before the current date
DateTime startDate = endDate.AddMonths(-2);
// Generate a random number of days between 0 and the total number of days in the range
int totalDays = (endDate - startDate).Days;
int randomDays = Random.Shared.Next(0, totalDays + 1); // +1 to include the end date
// Return the random date
return startDate.AddDays(randomDays);
}
[Description("Retrieves invoices for the specified company and optionally within the specified time range")]
public IEnumerable<Invoice> QueryInvoices(string companyName, DateTime? startDate = null, DateTime? endDate = null)
{
var query = this._invoices.Where(i => i.CompanyName.Equals(companyName, StringComparison.OrdinalIgnoreCase));
if (startDate.HasValue)
{
query = query.Where(i => i.InvoiceDate >= startDate.Value);
}
if (endDate.HasValue)
{
query = query.Where(i => i.InvoiceDate <= endDate.Value);
}
return query.ToList();
}
[Description("Retrieves invoice using the transaction id")]
public IEnumerable<Invoice> QueryByTransactionId(string transactionId)
{
var query = this._invoices.Where(i => i.TransactionId.Equals(transactionId, StringComparison.OrdinalIgnoreCase));
return query.ToList();
}
[Description("Retrieves invoice using the invoice id")]
public IEnumerable<Invoice> QueryByInvoiceId(string invoiceId)
{
var query = this._invoices.Where(i => i.InvoiceId.Equals(invoiceId, StringComparison.OrdinalIgnoreCase));
return query.ToList();
}
}
@@ -0,0 +1,121 @@
// Copyright (c) Microsoft. All rights reserved.
using A2A;
using A2A.AspNetCore;
using A2AServer;
using Microsoft.Agents.AI;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
string agentName = string.Empty;
string agentType = string.Empty;
for (var i = 0; i < args.Length; i++)
{
if (args[i].Equals("--agentName", StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length)
{
agentName = args[++i];
}
else if (args[i].Equals("--agentType", StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length)
{
agentType = args[++i];
}
}
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient().AddLogging();
IConfigurationRoot configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddUserSecrets<Program>()
.Build();
string? apiKey = configuration["OPENAI_API_KEY"];
string model = configuration["OPENAI_CHAT_MODEL_NAME"] ?? "gpt-5.4-mini";
string? endpoint = configuration["FOUNDRY_PROJECT_ENDPOINT"];
string[] agentUrls = (builder.Configuration["urls"] ?? "http://localhost:5000").Split(';');
var invoiceQueryPlugin = new InvoiceQuery();
IList<AITool> tools =
[
AIFunctionFactory.Create(invoiceQueryPlugin.QueryInvoices),
AIFunctionFactory.Create(invoiceQueryPlugin.QueryByTransactionId),
AIFunctionFactory.Create(invoiceQueryPlugin.QueryByInvoiceId)
];
AIAgent hostA2AAgent;
AgentCard hostA2AAgentCard;
if (!string.IsNullOrEmpty(endpoint) && !string.IsNullOrEmpty(agentName))
{
(hostA2AAgent, hostA2AAgentCard) = agentType.ToUpperInvariant() switch
{
"INVOICE" => await HostAgentFactory.CreateFoundryHostAgentAsync(agentType, model, endpoint, agentName, agentUrls, tools),
"POLICY" => await HostAgentFactory.CreateFoundryHostAgentAsync(agentType, model, endpoint, agentName, agentUrls),
"LOGISTICS" => await HostAgentFactory.CreateFoundryHostAgentAsync(agentType, model, endpoint, agentName, agentUrls),
_ => throw new ArgumentException($"Unsupported agent type: {agentType}"),
};
}
else if (!string.IsNullOrEmpty(apiKey))
{
(hostA2AAgent, hostA2AAgentCard) = agentType.ToUpperInvariant() switch
{
"INVOICE" => await HostAgentFactory.CreateChatCompletionHostAgentAsync(
agentType, model, apiKey, "InvoiceAgent",
"""
You specialize in handling queries related to invoices.
""", agentUrls, tools),
"POLICY" => await HostAgentFactory.CreateChatCompletionHostAgentAsync(
agentType, model, apiKey, "PolicyAgent",
"""
You specialize in handling queries related to policies and customer communications.
Always reply with exactly this text:
Policy: Short Shipment Dispute Handling Policy V2.1
Summary: "For short shipments reported by customers, first verify internal shipment records
(SAP) and physical logistics scan data (BigQuery). If discrepancy is confirmed and logistics data
shows fewer items packed than invoiced, issue a credit for the missing items. Document the
resolution in SAP CRM and notify the customer via email within 2 business days, referencing the
original invoice and the credit memo number. Use the 'Formal Credit Notification' email
template."
""", agentUrls),
"LOGISTICS" => await HostAgentFactory.CreateChatCompletionHostAgentAsync(
agentType, model, apiKey, "LogisticsAgent",
"""
You specialize in handling queries related to logistics.
Always reply with exactly:
Shipment number: SHPMT-SAP-001
Item: TSHIRT-RED-L
Quantity: 900
""", agentUrls),
_ => throw new ArgumentException($"Unsupported agent type: {agentType}"),
};
}
else
{
throw new ArgumentException("Either A2AServer:ApiKey or A2AServer:ConnectionString & agentName must be provided");
}
// IMPORTANT: In production, register a SessionIsolationKeyProvider to isolate sessions by authenticated caller.
// Without this, contextId alone is the session key — any caller who knows a contextId can access that session.
// Example using claims-based identity:
// builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier });
// By default, NoopAgentSessionStore is used — sessions are not persisted across requests.
// To enable multi-turn conversations, register a session store explicitly, e.g.:
// builder.Services.AddKeyedSingleton<AgentSessionStore>(hostA2AAgent.Name, new InMemoryAgentSessionStore());
builder.AddA2AServer(hostA2AAgent);
var app = builder.Build();
app.MapA2AHttpJson(hostA2AAgent, "/");
app.MapA2AJsonRpc(hostA2AAgent, "/");
app.MapWellKnownAgentCard(hostA2AAgentCard);
await app.RunAsync();