Python: Improve DevUI, add Context Inspector view as new tab under traces (#2742)

* Improve DevUI, add Context Inspector view as new tab under traces

* fix mypy errors

* fix: Handle stale MCP connections in DevUI executor

MCP tools can become stale when HTTP streaming responses end - the underlying
stdio streams close but `is_connected` remains True. This causes subsequent
requests to fail with `ClosedResourceError`.

Add `_ensure_mcp_connections()` to detect and reconnect stale MCP tools before
agent execution. This is a workaround for an upstream Agent Framework issue
where connection state isn't properly tracked.

Fixes MCP tools failing on second HTTP request in DevUI.

fixes  #1476 #1515 #2865

* fix #1572 report import dependency errors more clearly

* Ensure there is streaming toggle where users can select streaming vs non streaming mode in devui . Fixes .NET: [Python] DevUI tool call rendering in non-streaming mode?

* remove unused dead code

* improve ux - workflows with agents show a chat component in execution timelien, also ensure magentic final output shows correctly

* update ui build

* update devui to use instrumentation instead of tracing, other instrumentation and type/instance check fixes
This commit is contained in:
Victor Dibia
2026-01-07 00:26:08 -08:00
committed by GitHub
Unverified
parent db283cd396
commit 2e1189ca65
36 changed files with 7430 additions and 1662 deletions
@@ -398,7 +398,11 @@ class ApiClient {
async listConversationItems(
conversationId: string,
options?: { limit?: number; after?: string; order?: "asc" | "desc" }
): Promise<{ data: unknown[]; has_more: boolean }> {
): Promise<{
data: unknown[];
has_more: boolean;
metadata?: { traces?: unknown[] };
}> {
const params = new URLSearchParams();
if (options?.limit) params.set("limit", options.limit.toString());
if (options?.after) params.set("after", options.after);
@@ -409,7 +413,11 @@ class ApiClient {
queryString ? `?${queryString}` : ""
}`;
return this.request<{ data: unknown[]; has_more: boolean }>(url);
return this.request<{
data: unknown[];
has_more: boolean;
metadata?: { traces?: unknown[] };
}>(url);
}
async getConversationItem(
@@ -800,34 +808,68 @@ class ApiClient {
yield* this.streamOpenAIResponse(openAIRequest, request.conversation_id, signal);
}
// REMOVED: Legacy streaming methods - use streamAgentExecutionOpenAI and streamWorkflowExecutionOpenAI instead
// ========================================
// Non-Streaming Execution Methods
// ========================================
// Non-streaming execution (for testing)
async runAgent(
// Non-streaming agent execution using /v1/responses with stream=false
async runAgentSync(
agentId: string,
request: RunAgentRequest
): Promise<{
conversation_id: string;
result: unknown[];
message_count: number;
}> {
return this.request(`/agents/${agentId}/run`, {
): Promise<import("@/types/openai").OpenAIResponse> {
// Check if OAI proxy mode is enabled
const { oaiMode } = await import("@/stores").then((m) => ({
oaiMode: m.useDevUIStore.getState().oaiMode,
}));
const openAIRequest: AgentFrameworkRequest = {
metadata: { entity_id: agentId },
input: request.input,
stream: false,
conversation: request.conversation_id,
};
// Apply OAI mode settings if enabled
if (oaiMode.enabled) {
openAIRequest.model = oaiMode.model;
if (oaiMode.temperature !== undefined) {
openAIRequest.temperature = oaiMode.temperature;
}
if (oaiMode.max_output_tokens !== undefined) {
openAIRequest.max_output_tokens = oaiMode.max_output_tokens;
}
}
const headers: Record<string, string> = {};
if (oaiMode.enabled) {
headers["X-Proxy-Backend"] = "openai";
}
return this.request<import("@/types/openai").OpenAIResponse>("/v1/responses", {
method: "POST",
body: JSON.stringify(request),
headers,
body: JSON.stringify(openAIRequest),
});
}
async runWorkflow(
// Non-streaming workflow execution using /v1/responses with stream=false
async runWorkflowSync(
workflowId: string,
request: RunWorkflowRequest
): Promise<{
result: string;
events: number;
message_count: number;
}> {
return this.request(`/workflows/${workflowId}/run`, {
): Promise<import("@/types/openai").OpenAIResponse> {
const openAIRequest: AgentFrameworkRequest = {
metadata: { entity_id: workflowId },
input: JSON.stringify(request.input_data || {}),
stream: false,
conversation: request.conversation_id,
extra_body: request.checkpoint_id
? { entity_id: workflowId, checkpoint_id: request.checkpoint_id }
: undefined,
};
return this.request<import("@/types/openai").OpenAIResponse>("/v1/responses", {
method: "POST",
body: JSON.stringify(request),
body: JSON.stringify(openAIRequest),
});
}