Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
cc70906
DevUI: Add OpenAI Responses API proxy support with enhanced UI features
victordibia Oct 27, 2025
bae374b
Merge remote-tracking branch 'origin/main' into devui_oai_responses
victordibia Oct 27, 2025
6b4d188
update ui, settings modal and workflow input form, add register clean…
victordibia Oct 29, 2025
c6434b0
add workflow HIL support, user mode, other fixes
victordibia Oct 30, 2025
a4cc42a
Merge branch 'main' into devui_oai_responses
markwallace-microsoft Oct 31, 2025
8cfbc1a
Merge branch 'main' into devui_oai_responses
markwallace-microsoft Oct 31, 2025
ea5a239
Merge branch 'main' into devui_oai_responses
victordibia Nov 3, 2025
c0efc00
Merge branch 'main' into devui_oai_responses
victordibia Nov 4, 2025
55389da
feat(devui): add human-in-the-loop (HIL) support with dynamic respons…
victordibia Nov 4, 2025
246af75
improve HIL support, improve workflow execution view
victordibia Nov 5, 2025
ee1f10e
Merge branch 'main' into devui_oai_responses
victordibia Nov 5, 2025
512967b
ui updates
victordibia Nov 5, 2025
53868a5
ui updates
victordibia Nov 5, 2025
037ab69
Merge branch 'devui_oai_responses' of github.com:victordibia/agent-fr…
victordibia Nov 5, 2025
695a99f
improve HIL for workflows, add auth and view modes
victordibia Nov 6, 2025
41b7465
update workflow
victordibia Nov 6, 2025
71e8699
Merge remote-tracking branch 'origin/main' into devui_oai_responses
victordibia Nov 7, 2025
146cbad
Merge branch 'main' into devui_oai_responses
victordibia Nov 7, 2025
b2cc3c6
Merge remote-tracking branch 'origin/main' into devui_oai_responses
victordibia Nov 7, 2025
b86c8fe
security improvements , ui fixes
victordibia Nov 7, 2025
39715db
Merge remote-tracking branch 'origin/main' into devui_oai_responses
victordibia Nov 7, 2025
130a669
Merge branch 'devui_oai_responses' of github.com:victordibia/agent-fr…
victordibia Nov 7, 2025
5cf185f
Merge branch 'main' into devui_oai_responses
victordibia Nov 7, 2025
bf5738c
Merge remote-tracking branch 'origin/main' into devui_oai_responses a…
victordibia Nov 7, 2025
a4bdd6b
fix mypy error
victordibia Nov 7, 2025
aecf480
update loading spinner in ui
victordibia Nov 7, 2025
44a4488
Merge branch 'main' into devui_oai_responses
victordibia Nov 7, 2025
05ccbce
DevUI: Serialize workflow input as string to maintain conformance wit…
ReubenBond Nov 7, 2025
7dabc45
Phase 1: Add /meta endpoint and fix workflow event naming for .NET De…
victordibia Nov 8, 2025
1c6aa07
Merge remote-tracking branch 'origin/main' into dotnet-devui-compatib…
victordibia Nov 8, 2025
bcc685c
Merge branch 'main' into fix/devui-wf-serialization
victordibia Nov 8, 2025
f0f5488
Merge branch 'pr-2021-workflow-fix' into dotnet-devui-compatibility-f…
victordibia Nov 8, 2025
1514869
Merge remote-tracking branch 'origin/main' into dotnet-devui-compatib…
victordibia Nov 8, 2025
9d5d015
additional fixes for .NET DevUI workflow visualization item ID tracking
victordibia Nov 8, 2025
dfec3f9
format fixes, remove cors tests
victordibia Nov 8, 2025
12c65f6
Merge remote-tracking branch 'origin' into dotnet-devui-compatibility…
victordibia Nov 8, 2025
451a9f2
remove unecessary attributes
victordibia Nov 8, 2025
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
1 change: 1 addition & 0 deletions dotnet/src/Microsoft.Agents.AI.DevUI/DevUIExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public static IEndpointConventionBuilder MapDevUI(
{
var group = endpoints.MapGroup("");
group.MapDevUI(pattern: "/devui");
group.MapMeta();
group.MapEntities();
return group;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ namespace Microsoft.Agents.AI.DevUI.Entities;
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(EntityInfo))]
[JsonSerializable(typeof(DiscoveryResponse))]
[JsonSerializable(typeof(MetaResponse))]
[JsonSerializable(typeof(EnvVarRequirement))]
[JsonSerializable(typeof(List<EntityInfo>))]
[JsonSerializable(typeof(List<JsonElement>))]
[JsonSerializable(typeof(Dictionary<string, JsonElement>))]
[JsonSerializable(typeof(Dictionary<string, bool>))]
[JsonSerializable(typeof(JsonElement))]
[ExcludeFromCodeCoverage]
internal sealed partial class EntitiesJsonContext : JsonSerializerContext;
66 changes: 66 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.DevUI/Entities/MetaResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.Json.Serialization;

namespace Microsoft.Agents.AI.DevUI.Entities;

/// <summary>
/// Server metadata response for the /meta endpoint.
/// Provides information about the DevUI server configuration, capabilities, and requirements.
/// </summary>
/// <remarks>
/// This response is used by the frontend to:
/// - Determine the UI mode (developer vs user interface)
/// - Check server capabilities (tracing, OpenAI proxy support)
/// - Verify authentication requirements
/// - Display framework and version information
/// </remarks>
internal sealed record MetaResponse
{
/// <summary>
/// Gets the UI interface mode.
/// "developer" shows debug tools and advanced features, "user" shows a simplified interface.
/// </summary>
[JsonPropertyName("ui_mode")]
public string UiMode { get; init; } = "developer";

/// <summary>
/// Gets the DevUI version string.
/// </summary>
[JsonPropertyName("version")]
public string Version { get; init; } = "0.1.0";

/// <summary>
/// Gets the backend framework identifier.
/// Always "agent_framework" for Agent Framework implementations.
/// </summary>
[JsonPropertyName("framework")]
public string Framework { get; init; } = "agent_framework";

/// <summary>
/// Gets the backend runtime/language.
/// "dotnet" for .NET implementations, "python" for Python implementations.
/// Used by frontend for deployment guides and feature availability.
/// </summary>
[JsonPropertyName("runtime")]
public string Runtime { get; init; } = "dotnet";

/// <summary>
/// Gets the server capabilities dictionary.
/// Key-value pairs indicating which optional features are enabled.
/// </summary>
/// <remarks>
/// Standard capability keys:
/// - "tracing": Whether trace events are emitted for debugging
/// - "openai_proxy": Whether the server can proxy requests to OpenAI
/// </remarks>
[JsonPropertyName("capabilities")]
public Dictionary<string, bool> Capabilities { get; init; } = new();

/// <summary>
/// Gets a value indicating whether Bearer token authentication is required for API access.
/// When true, clients must include "Authorization: Bearer {token}" header in requests.
/// </summary>
[JsonPropertyName("auth_required")]
public bool AuthRequired { get; init; }
}
61 changes: 61 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.DevUI/MetaApiExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.Agents.AI.DevUI.Entities;

namespace Microsoft.Agents.AI.DevUI;

/// <summary>
/// Provides extension methods for mapping the server metadata endpoint to an <see cref="IEndpointRouteBuilder"/>.
/// </summary>
internal static class MetaApiExtensions
{
/// <summary>
/// Maps the HTTP API endpoint for retrieving server metadata.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <returns>The <see cref="IEndpointConventionBuilder"/> for method chaining.</returns>
/// <remarks>
/// This extension method registers the following endpoint:
/// <list type="bullet">
/// <item><description>GET /meta - Retrieve server metadata including UI mode, version, capabilities, and auth requirements</description></item>
/// </list>
/// The endpoint is compatible with the Python DevUI frontend and provides essential
/// configuration information needed for proper frontend initialization.
/// </remarks>
public static IEndpointConventionBuilder MapMeta(this IEndpointRouteBuilder endpoints)
{
return endpoints.MapGet("/meta", GetMeta)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually, we should put the devui endpoints behind a common path prefix so they wont clash with other endpoints.

.WithName("GetMeta")
.WithSummary("Get server metadata and configuration")
.WithDescription("Returns server metadata including UI mode, version, framework identifier, capabilities, and authentication requirements. Used by the frontend for initialization and feature detection.")
.Produces<MetaResponse>(StatusCodes.Status200OK, contentType: "application/json");
}

private static IResult GetMeta()
{
// TODO: Consider making these configurable via IOptions<DevUIOptions>
// For now, using sensible defaults that match Python DevUI behavior

var meta = new MetaResponse
{
UiMode = "developer", // Could be made configurable to support "user" mode
Version = "0.1.0", // TODO: Extract from assembly version attribute
Framework = "agent_framework",
Runtime = "dotnet", // .NET runtime for deployment guides
Capabilities = new Dictionary<string, bool>
{
// Tracing capability - will be enabled when trace event support is added
["tracing"] = false,

// OpenAI proxy capability - not currently supported in .NET DevUI
["openai_proxy"] = false,

// Deployment capability - not currently supported in .NET DevUI
["deployment"] = false
},
AuthRequired = false // Could be made configurable based on authentication middleware
};

return Results.Json(meta, EntitiesJsonContext.Default.MetaResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ private static JsonSerializerOptions CreateDefaultOptions()
[JsonSerializable(typeof(MCPApprovalRequestItemResource))]
[JsonSerializable(typeof(MCPApprovalResponseItemResource))]
[JsonSerializable(typeof(MCPCallItemResource))]
[JsonSerializable(typeof(ExecutorActionItemResource))]
[JsonSerializable(typeof(List<ItemResource>))]
// ItemParam types
[JsonSerializable(typeof(ItemParam))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public static async IAsyncEnumerable<StreamingResponseEvent> ToStreamingResponse
var updateEnumerator = updates.GetAsyncEnumerator(cancellationToken);
await using var _ = updateEnumerator.ConfigureAwait(false);

// Track active item IDs by executor ID to pair invoked/completed/failed events
Dictionary<string, string> executorItemIds = [];

AgentRunResponseUpdate? previousUpdate = null;
StreamingEventGenerator? generator = null;
while (await updateEnumerator.MoveNextAsync().ConfigureAwait(false))
Expand All @@ -55,7 +58,92 @@ public static async IAsyncEnumerable<StreamingResponseEvent> ToStreamingResponse
// Special-case for agent framework workflow events.
if (update.RawRepresentation is WorkflowEvent workflowEvent)
{
yield return CreateWorkflowEventResponse(workflowEvent, seq.Increment(), outputIndex);
// Convert executor events to standard OpenAI output_item events
if (workflowEvent is ExecutorInvokedEvent invokedEvent)
{
var itemId = IdGenerator.NewId(prefix: "item");
// Store the item ID for this executor so we can reuse it for completion/failure
executorItemIds[invokedEvent.ExecutorId] = itemId;

var item = new ExecutorActionItemResource
{
Id = itemId,
ExecutorId = invokedEvent.ExecutorId,
Status = "in_progress",
CreatedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};

yield return new StreamingOutputItemAdded
{
SequenceNumber = seq.Increment(),
OutputIndex = outputIndex,
Item = item
};
}
else if (workflowEvent is ExecutorCompletedEvent completedEvent)
{
// Reuse the item ID from the invoked event, or generate a new one if not found
var itemId = executorItemIds.TryGetValue(completedEvent.ExecutorId, out var existingId)
? existingId
: IdGenerator.NewId(prefix: "item");

// Remove from tracking as this executor run is now complete
executorItemIds.Remove(completedEvent.ExecutorId);
JsonElement? resultData = null;
if (completedEvent.Data != null && JsonSerializer.IsReflectionEnabledByDefault)
{
resultData = JsonSerializer.SerializeToElement(
completedEvent.Data,
OpenAIHostingJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object)));
}

var item = new ExecutorActionItemResource
{
Id = itemId,
ExecutorId = completedEvent.ExecutorId,
Status = "completed",
Result = resultData,
CreatedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};

yield return new StreamingOutputItemDone
{
SequenceNumber = seq.Increment(),
OutputIndex = outputIndex,
Item = item
};
}
else if (workflowEvent is ExecutorFailedEvent failedEvent)
{
// Reuse the item ID from the invoked event, or generate a new one if not found
var itemId = executorItemIds.TryGetValue(failedEvent.ExecutorId, out var existingId)
? existingId
: IdGenerator.NewId(prefix: "item");

// Remove from tracking as this executor run has now failed
executorItemIds.Remove(failedEvent.ExecutorId);

var item = new ExecutorActionItemResource
{
Id = itemId,
ExecutorId = failedEvent.ExecutorId,
Status = "failed",
Error = failedEvent.Data?.ToString(),
CreatedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};

yield return new StreamingOutputItemDone
{
SequenceNumber = seq.Increment(),
OutputIndex = outputIndex,
Item = item
};
}
else
{
// For other workflow events (not executor-specific), keep the old format as fallback
yield return CreateWorkflowEventResponse(workflowEvent, seq.Increment(), outputIndex);
}
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ internal sealed class ItemResourceConverter : JsonConverter<ItemResource>
MCPApprovalRequestItemResource.ItemType => doc.Deserialize(OpenAIHostingJsonContext.Default.MCPApprovalRequestItemResource),
MCPApprovalResponseItemResource.ItemType => doc.Deserialize(OpenAIHostingJsonContext.Default.MCPApprovalResponseItemResource),
MCPCallItemResource.ItemType => doc.Deserialize(OpenAIHostingJsonContext.Default.MCPCallItemResource),
ExecutorActionItemResource.ItemType => doc.Deserialize(OpenAIHostingJsonContext.Default.ExecutorActionItemResource),
_ => null
};
}
Expand Down Expand Up @@ -106,6 +107,9 @@ public override void Write(Utf8JsonWriter writer, ItemResource value, JsonSerial
case MCPCallItemResource mcpCall:
JsonSerializer.Serialize(writer, mcpCall, OpenAIHostingJsonContext.Default.MCPCallItemResource);
break;
case ExecutorActionItemResource executorAction:
JsonSerializer.Serialize(writer, executorAction, OpenAIHostingJsonContext.Default.ExecutorActionItemResource);
break;
default:
throw new JsonException($"Unknown item type: {value.GetType().Name}");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -888,3 +888,47 @@ internal sealed class MCPCallItemResource : ItemResource
[JsonPropertyName("error")]
public string? Error { get; init; }
}

/// <summary>
/// An executor action item resource for workflow execution visualization.
/// </summary>
internal sealed class ExecutorActionItemResource : ItemResource
{
/// <summary>
/// The constant item type identifier for executor action items.
/// </summary>
public const string ItemType = "executor_action";

/// <inheritdoc/>
public override string Type => ItemType;

/// <summary>
/// The executor identifier.
/// </summary>
[JsonPropertyName("executor_id")]
public required string ExecutorId { get; init; }

/// <summary>
/// The execution status: "in_progress", "completed", "failed", or "cancelled".
/// </summary>
[JsonPropertyName("status")]
public required string Status { get; init; }

/// <summary>
/// The executor result data (for completed status).
/// </summary>
[JsonPropertyName("result")]
public JsonElement? Result { get; init; }

/// <summary>
/// The error message (for failed status).
/// </summary>
[JsonPropertyName("error")]
public string? Error { get; init; }

/// <summary>
/// The creation timestamp.
/// </summary>
[JsonPropertyName("created_at")]
public long CreatedAt { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ internal sealed class StreamingWorkflowEventComplete : StreamingResponseEvent
/// <summary>
/// The constant event type identifier for workflow event events.
/// </summary>
public const string EventType = "response.workflow_event.complete";
public const string EventType = "response.workflow_event.completed";

/// <inheritdoc/>
[JsonIgnore]
Expand Down
1 change: 1 addition & 0 deletions python/packages/devui/agent_framework_devui/_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ async def get_meta() -> MetaResponse:
ui_mode=self.mode, # type: ignore[arg-type]
version=__version__,
framework="agent_framework",
runtime="python", # Python DevUI backend
capabilities={
"tracing": os.getenv("ENABLE_OTEL") == "true",
"openai_proxy": openai_executor.is_configured,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,9 @@ class MetaResponse(BaseModel):
framework: str = "agent_framework"
"""Backend framework identifier."""

runtime: Literal["python", "dotnet"] = "python"
"""Backend runtime/language - 'python' or 'dotnet' for deployment guides and feature availability."""

capabilities: dict[str, bool] = {}
"""Server capabilities (e.g., tracing, openai_proxy)."""

Expand Down

Large diffs are not rendered by default.

Loading
Loading