agent-framework-devui 1.0.0b251016__py3-none-any.whl → 1.0.0b251028__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of agent-framework-devui might be problematic. Click here for more details.
- agent_framework_devui/_discovery.py +2 -2
- agent_framework_devui/_executor.py +19 -8
- agent_framework_devui/_mapper.py +469 -13
- agent_framework_devui/_server.py +43 -1
- agent_framework_devui/_utils.py +24 -2
- agent_framework_devui/models/__init__.py +6 -0
- agent_framework_devui/models/_openai_custom.py +67 -2
- agent_framework_devui/ui/assets/index-D_Y1oSGu.js +577 -0
- agent_framework_devui/ui/index.html +1 -1
- {agent_framework_devui-1.0.0b251016.dist-info → agent_framework_devui-1.0.0b251028.dist-info}/METADATA +57 -22
- agent_framework_devui-1.0.0b251028.dist-info/RECORD +23 -0
- agent_framework_devui/ui/assets/index-DmL7WSFa.js +0 -577
- agent_framework_devui-1.0.0b251016.dist-info/RECORD +0 -23
- {agent_framework_devui-1.0.0b251016.dist-info → agent_framework_devui-1.0.0b251028.dist-info}/WHEEL +0 -0
- {agent_framework_devui-1.0.0b251016.dist-info → agent_framework_devui-1.0.0b251028.dist-info}/entry_points.txt +0 -0
- {agent_framework_devui-1.0.0b251016.dist-info → agent_framework_devui-1.0.0b251028.dist-info}/licenses/LICENSE +0 -0
agent_framework_devui/_server.py
CHANGED
|
@@ -85,19 +85,25 @@ class DevServer:
|
|
|
85
85
|
return self.executor
|
|
86
86
|
|
|
87
87
|
async def _cleanup_entities(self) -> None:
|
|
88
|
-
"""Cleanup entity resources (close clients, credentials, etc.)."""
|
|
88
|
+
"""Cleanup entity resources (close clients, MCP tools, credentials, etc.)."""
|
|
89
89
|
if not self.executor:
|
|
90
90
|
return
|
|
91
91
|
|
|
92
92
|
logger.info("Cleaning up entity resources...")
|
|
93
93
|
entities = self.executor.entity_discovery.list_entities()
|
|
94
94
|
closed_count = 0
|
|
95
|
+
mcp_tools_closed = 0
|
|
96
|
+
credentials_closed = 0
|
|
95
97
|
|
|
96
98
|
for entity_info in entities:
|
|
97
99
|
try:
|
|
98
100
|
entity_obj = self.executor.entity_discovery.get_entity_object(entity_info.id)
|
|
101
|
+
|
|
102
|
+
# Close chat clients and their credentials
|
|
99
103
|
if entity_obj and hasattr(entity_obj, "chat_client"):
|
|
100
104
|
client = entity_obj.chat_client
|
|
105
|
+
|
|
106
|
+
# Close the chat client itself
|
|
101
107
|
if hasattr(client, "close") and callable(client.close):
|
|
102
108
|
if inspect.iscoroutinefunction(client.close):
|
|
103
109
|
await client.close()
|
|
@@ -105,11 +111,47 @@ class DevServer:
|
|
|
105
111
|
client.close()
|
|
106
112
|
closed_count += 1
|
|
107
113
|
logger.debug(f"Closed client for entity: {entity_info.id}")
|
|
114
|
+
|
|
115
|
+
# Close credentials attached to chat clients (e.g., AzureCliCredential)
|
|
116
|
+
credential_attrs = ["credential", "async_credential", "_credential", "_async_credential"]
|
|
117
|
+
for attr in credential_attrs:
|
|
118
|
+
if hasattr(client, attr):
|
|
119
|
+
cred = getattr(client, attr)
|
|
120
|
+
if cred and hasattr(cred, "close") and callable(cred.close):
|
|
121
|
+
try:
|
|
122
|
+
if inspect.iscoroutinefunction(cred.close):
|
|
123
|
+
await cred.close()
|
|
124
|
+
else:
|
|
125
|
+
cred.close()
|
|
126
|
+
credentials_closed += 1
|
|
127
|
+
logger.debug(f"Closed credential for entity: {entity_info.id}")
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.warning(f"Error closing credential for {entity_info.id}: {e}")
|
|
130
|
+
|
|
131
|
+
# Close MCP tools (framework tracks them in _local_mcp_tools)
|
|
132
|
+
if entity_obj and hasattr(entity_obj, "_local_mcp_tools"):
|
|
133
|
+
for mcp_tool in entity_obj._local_mcp_tools:
|
|
134
|
+
if hasattr(mcp_tool, "close") and callable(mcp_tool.close):
|
|
135
|
+
try:
|
|
136
|
+
if inspect.iscoroutinefunction(mcp_tool.close):
|
|
137
|
+
await mcp_tool.close()
|
|
138
|
+
else:
|
|
139
|
+
mcp_tool.close()
|
|
140
|
+
mcp_tools_closed += 1
|
|
141
|
+
tool_name = getattr(mcp_tool, "name", "unknown")
|
|
142
|
+
logger.debug(f"Closed MCP tool '{tool_name}' for entity: {entity_info.id}")
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.warning(f"Error closing MCP tool for {entity_info.id}: {e}")
|
|
145
|
+
|
|
108
146
|
except Exception as e:
|
|
109
147
|
logger.warning(f"Error closing entity {entity_info.id}: {e}")
|
|
110
148
|
|
|
111
149
|
if closed_count > 0:
|
|
112
150
|
logger.info(f"Closed {closed_count} entity client(s)")
|
|
151
|
+
if credentials_closed > 0:
|
|
152
|
+
logger.info(f"Closed {credentials_closed} credential(s)")
|
|
153
|
+
if mcp_tools_closed > 0:
|
|
154
|
+
logger.info(f"Closed {mcp_tools_closed} MCP tool(s)")
|
|
113
155
|
|
|
114
156
|
def create_app(self) -> FastAPI:
|
|
115
157
|
"""Create the FastAPI application."""
|
agent_framework_devui/_utils.py
CHANGED
|
@@ -6,7 +6,10 @@ import inspect
|
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
8
|
from dataclasses import fields, is_dataclass
|
|
9
|
-
from
|
|
9
|
+
from types import UnionType
|
|
10
|
+
from typing import Any, Union, get_args, get_origin
|
|
11
|
+
|
|
12
|
+
from agent_framework import ChatMessage
|
|
10
13
|
|
|
11
14
|
logger = logging.getLogger(__name__)
|
|
12
15
|
|
|
@@ -110,10 +113,25 @@ def extract_executor_message_types(executor: Any) -> list[Any]:
|
|
|
110
113
|
return message_types
|
|
111
114
|
|
|
112
115
|
|
|
116
|
+
def _contains_chat_message(type_hint: Any) -> bool:
|
|
117
|
+
"""Check whether the provided type hint directly or indirectly references ChatMessage."""
|
|
118
|
+
if type_hint is ChatMessage:
|
|
119
|
+
return True
|
|
120
|
+
|
|
121
|
+
origin = get_origin(type_hint)
|
|
122
|
+
if origin in (list, tuple):
|
|
123
|
+
return any(_contains_chat_message(arg) for arg in get_args(type_hint))
|
|
124
|
+
|
|
125
|
+
if origin in (Union, UnionType):
|
|
126
|
+
return any(_contains_chat_message(arg) for arg in get_args(type_hint))
|
|
127
|
+
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
|
|
113
131
|
def select_primary_input_type(message_types: list[Any]) -> Any | None:
|
|
114
132
|
"""Choose the most user-friendly input type for workflow inputs.
|
|
115
133
|
|
|
116
|
-
Prefers
|
|
134
|
+
Prefers ChatMessage (or containers thereof) and then falls back to primitives.
|
|
117
135
|
|
|
118
136
|
Args:
|
|
119
137
|
message_types: List of possible message types
|
|
@@ -124,6 +142,10 @@ def select_primary_input_type(message_types: list[Any]) -> Any | None:
|
|
|
124
142
|
if not message_types:
|
|
125
143
|
return None
|
|
126
144
|
|
|
145
|
+
for message_type in message_types:
|
|
146
|
+
if _contains_chat_message(message_type):
|
|
147
|
+
return ChatMessage
|
|
148
|
+
|
|
127
149
|
preferred = (str, dict)
|
|
128
150
|
|
|
129
151
|
for candidate in preferred:
|
|
@@ -30,6 +30,9 @@ from openai.types.shared import Metadata, ResponsesModel
|
|
|
30
30
|
from ._discovery_models import DiscoveryResponse, EntityInfo
|
|
31
31
|
from ._openai_custom import (
|
|
32
32
|
AgentFrameworkRequest,
|
|
33
|
+
CustomResponseOutputItemAddedEvent,
|
|
34
|
+
CustomResponseOutputItemDoneEvent,
|
|
35
|
+
ExecutorActionItem,
|
|
33
36
|
OpenAIError,
|
|
34
37
|
ResponseFunctionResultComplete,
|
|
35
38
|
ResponseTraceEvent,
|
|
@@ -46,8 +49,11 @@ __all__ = [
|
|
|
46
49
|
"Conversation",
|
|
47
50
|
"ConversationDeletedResource",
|
|
48
51
|
"ConversationItem",
|
|
52
|
+
"CustomResponseOutputItemAddedEvent",
|
|
53
|
+
"CustomResponseOutputItemDoneEvent",
|
|
49
54
|
"DiscoveryResponse",
|
|
50
55
|
"EntityInfo",
|
|
56
|
+
"ExecutorActionItem",
|
|
51
57
|
"InputTokensDetails",
|
|
52
58
|
"Metadata",
|
|
53
59
|
"OpenAIError",
|
|
@@ -8,6 +8,7 @@ to support Agent Framework specific features like workflows and traces.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
+
from dataclasses import dataclass
|
|
11
12
|
from typing import Any, Literal
|
|
12
13
|
|
|
13
14
|
from pydantic import BaseModel, ConfigDict
|
|
@@ -15,6 +16,69 @@ from pydantic import BaseModel, ConfigDict
|
|
|
15
16
|
# Custom Agent Framework OpenAI event types for structured data
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
# Agent lifecycle events - simple and clear
|
|
20
|
+
class AgentStartedEvent:
|
|
21
|
+
"""Event emitted when an agent starts execution."""
|
|
22
|
+
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AgentCompletedEvent:
|
|
27
|
+
"""Event emitted when an agent completes execution successfully."""
|
|
28
|
+
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class AgentFailedEvent:
|
|
34
|
+
"""Event emitted when an agent fails during execution."""
|
|
35
|
+
|
|
36
|
+
error: Exception | None = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ExecutorActionItem(BaseModel):
|
|
40
|
+
"""Custom item type for workflow executor actions.
|
|
41
|
+
|
|
42
|
+
This is a DevUI-specific extension to represent workflow executors as output items.
|
|
43
|
+
Since OpenAI's ResponseOutputItemAddedEvent only accepts specific item types,
|
|
44
|
+
and executor actions are not part of the standard, we need this custom type.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
type: Literal["executor_action"] = "executor_action"
|
|
48
|
+
id: str
|
|
49
|
+
executor_id: str
|
|
50
|
+
status: Literal["in_progress", "completed", "failed", "cancelled"] = "in_progress"
|
|
51
|
+
metadata: dict[str, Any] | None = None
|
|
52
|
+
result: Any | None = None
|
|
53
|
+
error: dict[str, Any] | None = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CustomResponseOutputItemAddedEvent(BaseModel):
|
|
57
|
+
"""Custom version of ResponseOutputItemAddedEvent that accepts any item type.
|
|
58
|
+
|
|
59
|
+
This allows us to emit executor action items while maintaining the same
|
|
60
|
+
event structure as OpenAI's standard.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
type: Literal["response.output_item.added"] = "response.output_item.added"
|
|
64
|
+
output_index: int
|
|
65
|
+
sequence_number: int
|
|
66
|
+
item: dict[str, Any] | ExecutorActionItem | Any # Flexible item type
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class CustomResponseOutputItemDoneEvent(BaseModel):
|
|
70
|
+
"""Custom version of ResponseOutputItemDoneEvent that accepts any item type.
|
|
71
|
+
|
|
72
|
+
This allows us to emit executor action items while maintaining the same
|
|
73
|
+
event structure as OpenAI's standard.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
type: Literal["response.output_item.done"] = "response.output_item.done"
|
|
77
|
+
output_index: int
|
|
78
|
+
sequence_number: int
|
|
79
|
+
item: dict[str, Any] | ExecutorActionItem | Any # Flexible item type
|
|
80
|
+
|
|
81
|
+
|
|
18
82
|
class ResponseWorkflowEventComplete(BaseModel):
|
|
19
83
|
"""Complete workflow event data."""
|
|
20
84
|
|
|
@@ -57,6 +121,7 @@ class ResponseFunctionResultComplete(BaseModel):
|
|
|
57
121
|
item_id: str
|
|
58
122
|
output_index: int = 0
|
|
59
123
|
sequence_number: int
|
|
124
|
+
timestamp: str | None = None # Optional timestamp for UI display
|
|
60
125
|
|
|
61
126
|
|
|
62
127
|
# Agent Framework extension fields
|
|
@@ -64,7 +129,7 @@ class AgentFrameworkExtraBody(BaseModel):
|
|
|
64
129
|
"""Agent Framework specific routing fields for OpenAI requests."""
|
|
65
130
|
|
|
66
131
|
entity_id: str
|
|
67
|
-
input_data
|
|
132
|
+
# input_data removed - now using standard input field for all data
|
|
68
133
|
|
|
69
134
|
model_config = ConfigDict(extra="allow")
|
|
70
135
|
|
|
@@ -80,7 +145,7 @@ class AgentFrameworkRequest(BaseModel):
|
|
|
80
145
|
|
|
81
146
|
# All OpenAI fields from ResponseCreateParams
|
|
82
147
|
model: str # Used as entity_id in DevUI!
|
|
83
|
-
input: str | list[Any] # ResponseInputParam
|
|
148
|
+
input: str | list[Any] | dict[str, Any] # ResponseInputParam + dict for workflow structured input
|
|
84
149
|
stream: bool | None = False
|
|
85
150
|
|
|
86
151
|
# OpenAI conversation parameter (standard!)
|