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.

@@ -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."""
@@ -6,7 +6,10 @@ import inspect
6
6
  import json
7
7
  import logging
8
8
  from dataclasses import fields, is_dataclass
9
- from typing import Any, get_args, get_origin
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 str and dict types for better user experience.
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: dict[str, Any] | None = None
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!)