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
276 changes: 135 additions & 141 deletions dotnet/src/Microsoft.Agents.AI.DevUI/EntitiesApiExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Runtime.CompilerServices;
using System.Text.Json;

using Microsoft.Agents.AI.DevUI.Entities;
using Microsoft.Agents.AI.Hosting;
using Microsoft.Agents.AI.Workflows;

namespace Microsoft.Agents.AI.DevUI;

Expand Down Expand Up @@ -56,79 +58,19 @@ private static async Task<IResult> ListEntitiesAsync(
{
var entities = new List<EntityInfo>();

// Discover agents from the agent catalog
if (agentCatalog is not null)
// Discover agents
await foreach (var agentInfo in DiscoverAgentsAsync(agentCatalog, entityIdFilter: null, cancellationToken).ConfigureAwait(false))
{
await foreach (var agent in agentCatalog.GetAgentsAsync(cancellationToken).ConfigureAwait(false))
{
if (agent.GetType().Name == "WorkflowHostAgent")
{
// HACK: ignore WorkflowHostAgent instances as they are just wrappers around workflows,
// and workflows are handled below.
continue;
}

entities.Add(new EntityInfo(
Id: agent.Name ?? agent.Id,
Type: "agent",
Name: agent.Name ?? agent.Id,
Description: agent.Description,
Framework: "agent-framework",
Tools: null,
Metadata: []
)
{
Source = "in_memory"
});
}
entities.Add(agentInfo);
}

// Discover workflows from the workflow catalog
if (workflowCatalog is not null)
// Discover workflows
await foreach (var workflowInfo in DiscoverWorkflowsAsync(workflowCatalog, entityIdFilter: null, cancellationToken).ConfigureAwait(false))
{
await foreach (var workflow in workflowCatalog.GetWorkflowsAsync(cancellationToken).ConfigureAwait(false))
{
// Extract executor IDs from the workflow structure
var executorIds = new HashSet<string> { workflow.StartExecutorId };
var reflectedEdges = workflow.ReflectEdges();
foreach (var (sourceId, edgeSet) in reflectedEdges)
{
executorIds.Add(sourceId);
foreach (var edge in edgeSet)
{
foreach (var sinkId in edge.Connection.SinkIds)
{
executorIds.Add(sinkId);
}
}
}

// Create a default input schema (string type)
var defaultInputSchema = new Dictionary<string, object>
{
["type"] = "string"
};

entities.Add(new EntityInfo(
Id: workflow.Name ?? workflow.StartExecutorId,
Type: "workflow",
Name: workflow.Name ?? workflow.StartExecutorId,
Description: workflow.Description,
Framework: "agent-framework",
Tools: [.. executorIds],
Metadata: []
)
{
Source = "in_memory",
WorkflowDump = JsonSerializer.SerializeToElement(workflow.ToDevUIDict()),
InputSchema = JsonSerializer.SerializeToElement(defaultInputSchema),
InputTypeName = "string",
StartExecutorId = workflow.StartExecutorId
});
}
entities.Add(workflowInfo);
}

return Results.Json(new DiscoveryResponse(entities), EntitiesJsonContext.Default.DiscoveryResponse);
return Results.Json(new DiscoveryResponse([.. entities]), EntitiesJsonContext.Default.DiscoveryResponse);
}
catch (Exception ex)
{
Expand All @@ -141,93 +83,26 @@ private static async Task<IResult> ListEntitiesAsync(

private static async Task<IResult> GetEntityInfoAsync(
string entityId,
string? type,
AgentCatalog? agentCatalog,
WorkflowCatalog? workflowCatalog,
CancellationToken cancellationToken)
{
try
{
// Try to find the entity among discovered agents
if (agentCatalog is not null)
if (type is null || string.Equals(type, "agent", StringComparison.OrdinalIgnoreCase))
{
await foreach (var agent in agentCatalog.GetAgentsAsync(cancellationToken).ConfigureAwait(false))
await foreach (var agentInfo in DiscoverAgentsAsync(agentCatalog, entityId, cancellationToken).ConfigureAwait(false))
{
if (agent.GetType().Name == "WorkflowHostAgent")
{
// HACK: ignore WorkflowHostAgent instances as they are just wrappers around workflows,
// and workflows are handled below.
continue;
}

if (string.Equals(agent.Name, entityId, StringComparison.OrdinalIgnoreCase) ||
string.Equals(agent.Id, entityId, StringComparison.OrdinalIgnoreCase))
{
var entityInfo = new EntityInfo(
Id: agent.Name ?? agent.Id,
Type: "agent",
Name: agent.Name ?? agent.Id,
Description: agent.Description,
Framework: "agent-framework",
Tools: null,
Metadata: []
)
{
Source = "in_memory"
};

return Results.Json(entityInfo, EntitiesJsonContext.Default.EntityInfo);
}
return Results.Json(agentInfo, EntitiesJsonContext.Default.EntityInfo);
}
}

// Try to find the entity among discovered workflows
if (workflowCatalog is not null)
if (type is null || string.Equals(type, "workflow", StringComparison.OrdinalIgnoreCase))
{
await foreach (var workflow in workflowCatalog.GetWorkflowsAsync(cancellationToken).ConfigureAwait(false))
await foreach (var workflowInfo in DiscoverWorkflowsAsync(workflowCatalog, entityId, cancellationToken).ConfigureAwait(false))
{
var workflowId = workflow.Name ?? workflow.StartExecutorId;
if (string.Equals(workflowId, entityId, StringComparison.OrdinalIgnoreCase))
{
// Extract executor IDs from the workflow structure
var executorIds = new HashSet<string> { workflow.StartExecutorId };
var reflectedEdges = workflow.ReflectEdges();
foreach (var (sourceId, edgeSet) in reflectedEdges)
{
executorIds.Add(sourceId);
foreach (var edge in edgeSet)
{
foreach (var sinkId in edge.Connection.SinkIds)
{
executorIds.Add(sinkId);
}
}
}

// Create a default input schema (string type)
var defaultInputSchema = new Dictionary<string, object>
{
["type"] = "string"
};

var entityInfo = new EntityInfo(
Id: workflowId,
Type: "workflow",
Name: workflow.Name ?? workflow.StartExecutorId,
Description: workflow.Description,
Framework: "agent-framework",
Tools: [.. executorIds],
Metadata: []
)
{
Source = "in_memory",
WorkflowDump = JsonSerializer.SerializeToElement(workflow.ToDevUIDict()),
InputSchema = JsonSerializer.SerializeToElement(defaultInputSchema),
InputTypeName = "Input",
StartExecutorId = workflow.StartExecutorId
};

return Results.Json(entityInfo, EntitiesJsonContext.Default.EntityInfo);
}
return Results.Json(workflowInfo, EntitiesJsonContext.Default.EntityInfo);
}
}

Expand All @@ -241,4 +116,123 @@ private static async Task<IResult> GetEntityInfoAsync(
title: "Error getting entity info");
}
}

private static async IAsyncEnumerable<EntityInfo> DiscoverAgentsAsync(
AgentCatalog? agentCatalog,
string? entityIdFilter,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
if (agentCatalog is null)
{
yield break;
}

await foreach (var agent in agentCatalog.GetAgentsAsync(cancellationToken).ConfigureAwait(false))
{
// If filtering by entity ID, skip non-matching agents
if (entityIdFilter is not null &&
!string.Equals(agent.Name, entityIdFilter, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(agent.Id, entityIdFilter, StringComparison.OrdinalIgnoreCase))
{
continue;
}

yield return CreateAgentEntityInfo(agent);

// If we found the entity we're looking for, we're done
if (entityIdFilter is not null)
{
yield break;
}
}
}

private static async IAsyncEnumerable<EntityInfo> DiscoverWorkflowsAsync(
WorkflowCatalog? workflowCatalog,
string? entityIdFilter,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
if (workflowCatalog is null)
{
yield break;
}

await foreach (var workflow in workflowCatalog.GetWorkflowsAsync(cancellationToken).ConfigureAwait(false))
{
var workflowId = workflow.Name ?? workflow.StartExecutorId;

// If filtering by entity ID, skip non-matching workflows
if (entityIdFilter is not null && !string.Equals(workflowId, entityIdFilter, StringComparison.OrdinalIgnoreCase))
{
continue;
}

yield return CreateWorkflowEntityInfo(workflow);

// If we found the entity we're looking for, we're done
if (entityIdFilter is not null)
{
yield break;
}
}
}

private static EntityInfo CreateAgentEntityInfo(AIAgent agent)
{
var entityId = agent.Name ?? agent.Id;
return new EntityInfo(
Id: entityId,
Type: "agent",
Name: entityId,
Description: agent.Description,
Framework: "agent-framework",
Tools: null,
Metadata: []
)
{
Source = "in_memory"
};
}

private static EntityInfo CreateWorkflowEntityInfo(Workflow workflow)
{
// Extract executor IDs from the workflow structure
var executorIds = new HashSet<string> { workflow.StartExecutorId };
var reflectedEdges = workflow.ReflectEdges();
foreach (var (sourceId, edgeSet) in reflectedEdges)
{
executorIds.Add(sourceId);
foreach (var edge in edgeSet)
{
foreach (var sinkId in edge.Connection.SinkIds)
{
executorIds.Add(sinkId);
}
}
}

// Create a default input schema (string type)
var defaultInputSchema = new Dictionary<string, object>
{
["type"] = "string"
};

var workflowId = workflow.Name ?? workflow.StartExecutorId;
return new EntityInfo(
Id: workflowId,
Type: "workflow",
Name: workflowId,
Description: workflow.Description,
Framework: "agent-framework",
Tools: [.. executorIds],
Metadata: []
)
{
Source = "in_memory",
WorkflowDump = JsonSerializer.SerializeToElement(workflow.ToDevUIDict()),
InputSchema = JsonSerializer.SerializeToElement(defaultInputSchema),
InputTypeName = "string",
StartExecutorId = workflow.StartExecutorId
};
}
}
Loading
Loading