agent-framework-devui 0.0.1a0__py3-none-any.whl → 1.0.0b251001__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/__init__.py +151 -0
- agent_framework_devui/_cli.py +143 -0
- agent_framework_devui/_discovery.py +724 -0
- agent_framework_devui/_executor.py +770 -0
- agent_framework_devui/_mapper.py +582 -0
- agent_framework_devui/_server.py +530 -0
- agent_framework_devui/_session.py +191 -0
- agent_framework_devui/_tracing.py +168 -0
- agent_framework_devui/models/__init__.py +72 -0
- agent_framework_devui/models/_discovery_models.py +51 -0
- agent_framework_devui/models/_openai_custom.py +209 -0
- agent_framework_devui/ui/assets/index-D1AmQWga.css +1 -0
- agent_framework_devui/ui/assets/index-DPEaaIdK.js +435 -0
- agent_framework_devui/ui/index.html +14 -0
- agent_framework_devui/ui/vite.svg +1 -0
- agent_framework_devui-1.0.0b251001.dist-info/METADATA +172 -0
- agent_framework_devui-1.0.0b251001.dist-info/RECORD +20 -0
- {agent_framework_devui-0.0.1a0.dist-info → agent_framework_devui-1.0.0b251001.dist-info}/WHEEL +1 -2
- agent_framework_devui-1.0.0b251001.dist-info/entry_points.txt +3 -0
- agent_framework_devui-1.0.0b251001.dist-info/licenses/LICENSE +21 -0
- agent_framework_devui-0.0.1a0.dist-info/METADATA +0 -18
- agent_framework_devui-0.0.1a0.dist-info/RECORD +0 -5
- agent_framework_devui-0.0.1a0.dist-info/licenses/LICENSE +0 -9
- agent_framework_devui-0.0.1a0.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Agent Framework DevUI - Debug interface with OpenAI compatible API server."""
|
|
4
|
+
|
|
5
|
+
import importlib.metadata
|
|
6
|
+
import logging
|
|
7
|
+
import webbrowser
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from ._server import DevServer
|
|
11
|
+
from .models import AgentFrameworkRequest, OpenAIError, OpenAIResponse, ResponseStreamEvent
|
|
12
|
+
from .models._discovery_models import DiscoveryResponse, EntityInfo, EnvVarRequirement
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
__version__ = importlib.metadata.version(__name__)
|
|
18
|
+
except importlib.metadata.PackageNotFoundError:
|
|
19
|
+
__version__ = "0.0.0" # Fallback for development mode
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def serve(
|
|
23
|
+
entities: list[Any] | None = None,
|
|
24
|
+
entities_dir: str | None = None,
|
|
25
|
+
port: int = 8080,
|
|
26
|
+
host: str = "127.0.0.1",
|
|
27
|
+
auto_open: bool = False,
|
|
28
|
+
cors_origins: list[str] | None = None,
|
|
29
|
+
ui_enabled: bool = True,
|
|
30
|
+
tracing_enabled: bool = False,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Launch Agent Framework DevUI with simple API.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
entities: List of entities for in-memory registration (IDs auto-generated)
|
|
36
|
+
entities_dir: Directory to scan for entities
|
|
37
|
+
port: Port to run server on
|
|
38
|
+
host: Host to bind server to
|
|
39
|
+
auto_open: Whether to automatically open browser
|
|
40
|
+
cors_origins: List of allowed CORS origins
|
|
41
|
+
ui_enabled: Whether to enable the UI
|
|
42
|
+
tracing_enabled: Whether to enable OpenTelemetry tracing
|
|
43
|
+
"""
|
|
44
|
+
import re
|
|
45
|
+
|
|
46
|
+
import uvicorn
|
|
47
|
+
|
|
48
|
+
# Validate host parameter early for security
|
|
49
|
+
if not re.match(r"^(localhost|127\.0\.0\.1|0\.0\.0\.0|[a-zA-Z0-9.-]+)$", host):
|
|
50
|
+
raise ValueError(f"Invalid host: {host}. Must be localhost, IP address, or valid hostname")
|
|
51
|
+
|
|
52
|
+
# Validate port parameter
|
|
53
|
+
if not isinstance(port, int) or not (1 <= port <= 65535):
|
|
54
|
+
raise ValueError(f"Invalid port: {port}. Must be integer between 1 and 65535")
|
|
55
|
+
|
|
56
|
+
# Configure tracing environment variables if enabled
|
|
57
|
+
if tracing_enabled:
|
|
58
|
+
import os
|
|
59
|
+
|
|
60
|
+
# Only set if not already configured by user
|
|
61
|
+
if not os.environ.get("ENABLE_OTEL"):
|
|
62
|
+
os.environ["ENABLE_OTEL"] = "true"
|
|
63
|
+
logger.info("Set ENABLE_OTEL=true for tracing")
|
|
64
|
+
|
|
65
|
+
if not os.environ.get("ENABLE_SENSITIVE_DATA"):
|
|
66
|
+
os.environ["ENABLE_SENSITIVE_DATA"] = "true"
|
|
67
|
+
logger.info("Set ENABLE_SENSITIVE_DATA=true for tracing")
|
|
68
|
+
|
|
69
|
+
if not os.environ.get("OTLP_ENDPOINT"):
|
|
70
|
+
os.environ["OTLP_ENDPOINT"] = "http://localhost:4317"
|
|
71
|
+
logger.info("Set OTLP_ENDPOINT=http://localhost:4317 for tracing")
|
|
72
|
+
|
|
73
|
+
# Create server with direct parameters
|
|
74
|
+
server = DevServer(
|
|
75
|
+
entities_dir=entities_dir, port=port, host=host, cors_origins=cors_origins, ui_enabled=ui_enabled
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Register in-memory entities if provided
|
|
79
|
+
if entities:
|
|
80
|
+
logger.info(f"Registering {len(entities)} in-memory entities")
|
|
81
|
+
# Store entities for later registration during server startup
|
|
82
|
+
server._pending_entities = entities
|
|
83
|
+
|
|
84
|
+
app = server.get_app()
|
|
85
|
+
|
|
86
|
+
if auto_open:
|
|
87
|
+
|
|
88
|
+
def open_browser() -> None:
|
|
89
|
+
import http.client
|
|
90
|
+
import re
|
|
91
|
+
import time
|
|
92
|
+
|
|
93
|
+
# Validate host and port for security
|
|
94
|
+
if not re.match(r"^(localhost|127\.0\.0\.1|0\.0\.0\.0|[a-zA-Z0-9.-]+)$", host):
|
|
95
|
+
logger.warning(f"Invalid host for auto-open: {host}")
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
if not isinstance(port, int) or not (1 <= port <= 65535):
|
|
99
|
+
logger.warning(f"Invalid port for auto-open: {port}")
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
# Wait for server to be ready by checking health endpoint
|
|
103
|
+
browser_url = f"http://{host}:{port}"
|
|
104
|
+
|
|
105
|
+
for _ in range(30): # 15 second timeout (30 * 0.5s)
|
|
106
|
+
try:
|
|
107
|
+
# Use http.client for safe connection handling (standard library)
|
|
108
|
+
conn = http.client.HTTPConnection(host, port, timeout=1)
|
|
109
|
+
try:
|
|
110
|
+
conn.request("GET", "/health")
|
|
111
|
+
response = conn.getresponse()
|
|
112
|
+
if response.status == 200:
|
|
113
|
+
webbrowser.open(browser_url)
|
|
114
|
+
return
|
|
115
|
+
finally:
|
|
116
|
+
conn.close()
|
|
117
|
+
except (http.client.HTTPException, OSError, TimeoutError):
|
|
118
|
+
pass
|
|
119
|
+
time.sleep(0.5)
|
|
120
|
+
|
|
121
|
+
# Fallback: open browser anyway after timeout
|
|
122
|
+
webbrowser.open(browser_url)
|
|
123
|
+
|
|
124
|
+
import threading
|
|
125
|
+
|
|
126
|
+
threading.Thread(target=open_browser, daemon=True).start()
|
|
127
|
+
|
|
128
|
+
logger.info(f"Starting Agent Framework DevUI on {host}:{port}")
|
|
129
|
+
uvicorn.run(app, host=host, port=port, log_level="info")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main() -> None:
|
|
133
|
+
"""CLI entry point for devui command."""
|
|
134
|
+
from ._cli import main as cli_main
|
|
135
|
+
|
|
136
|
+
cli_main()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# Export main public API
|
|
140
|
+
__all__ = [
|
|
141
|
+
"AgentFrameworkRequest",
|
|
142
|
+
"DevServer",
|
|
143
|
+
"DiscoveryResponse",
|
|
144
|
+
"EntityInfo",
|
|
145
|
+
"EnvVarRequirement",
|
|
146
|
+
"OpenAIError",
|
|
147
|
+
"OpenAIResponse",
|
|
148
|
+
"ResponseStreamEvent",
|
|
149
|
+
"main",
|
|
150
|
+
"serve",
|
|
151
|
+
]
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Copyright (c) Microsoft. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Command line interface for Agent Framework DevUI."""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def setup_logging(level: str = "INFO") -> None:
|
|
14
|
+
"""Configure logging for the server."""
|
|
15
|
+
log_format = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
|
|
16
|
+
logging.basicConfig(level=getattr(logging, level.upper()), format=log_format, datefmt="%Y-%m-%d %H:%M:%S")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_cli_parser() -> argparse.ArgumentParser:
|
|
20
|
+
"""Create the command line argument parser."""
|
|
21
|
+
parser = argparse.ArgumentParser(
|
|
22
|
+
prog="devui",
|
|
23
|
+
description="Launch Agent Framework DevUI - Debug interface with OpenAI compatible API",
|
|
24
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
25
|
+
epilog="""
|
|
26
|
+
Examples:
|
|
27
|
+
devui # Scan current directory
|
|
28
|
+
devui ./agents # Scan specific directory
|
|
29
|
+
devui --port 8000 # Custom port
|
|
30
|
+
devui --headless # API only, no UI
|
|
31
|
+
devui --tracing # Enable OpenTelemetry tracing
|
|
32
|
+
""",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"directory", nargs="?", default=".", help="Directory to scan for entities (default: current directory)"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
parser.add_argument("--port", "-p", type=int, default=8080, help="Port to run server on (default: 8080)")
|
|
40
|
+
|
|
41
|
+
parser.add_argument("--host", default="127.0.0.1", help="Host to bind server to (default: 127.0.0.1)")
|
|
42
|
+
|
|
43
|
+
parser.add_argument("--no-open", action="store_true", help="Don't automatically open browser")
|
|
44
|
+
|
|
45
|
+
parser.add_argument("--headless", action="store_true", help="Run without UI (API only)")
|
|
46
|
+
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--log-level",
|
|
49
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
|
|
50
|
+
default="INFO",
|
|
51
|
+
help="Logging level (default: INFO)",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
parser.add_argument("--reload", action="store_true", help="Enable auto-reload for development")
|
|
55
|
+
|
|
56
|
+
parser.add_argument("--tracing", action="store_true", help="Enable OpenTelemetry tracing for Agent Framework")
|
|
57
|
+
|
|
58
|
+
parser.add_argument("--version", action="version", version=f"Agent Framework DevUI {get_version()}")
|
|
59
|
+
|
|
60
|
+
return parser
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_version() -> str:
|
|
64
|
+
"""Get the package version."""
|
|
65
|
+
try:
|
|
66
|
+
from . import __version__
|
|
67
|
+
|
|
68
|
+
return __version__
|
|
69
|
+
except ImportError:
|
|
70
|
+
return "unknown"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def validate_directory(directory: str) -> str:
|
|
74
|
+
"""Validate and normalize the entities directory."""
|
|
75
|
+
if not directory:
|
|
76
|
+
directory = "."
|
|
77
|
+
|
|
78
|
+
abs_dir = os.path.abspath(directory)
|
|
79
|
+
|
|
80
|
+
if not os.path.exists(abs_dir):
|
|
81
|
+
print(f"❌ Error: Directory '{directory}' does not exist", file=sys.stderr) # noqa: T201
|
|
82
|
+
sys.exit(1)
|
|
83
|
+
|
|
84
|
+
if not os.path.isdir(abs_dir):
|
|
85
|
+
print(f"❌ Error: '{directory}' is not a directory", file=sys.stderr) # noqa: T201
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
|
|
88
|
+
return abs_dir
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def print_startup_info(entities_dir: str, host: str, port: int, ui_enabled: bool, reload: bool) -> None:
|
|
92
|
+
"""Print startup information."""
|
|
93
|
+
print("🤖 Agent Framework DevUI") # noqa: T201
|
|
94
|
+
print("=" * 50) # noqa: T201
|
|
95
|
+
print(f"📁 Entities directory: {entities_dir}") # noqa: T201
|
|
96
|
+
print(f"🌐 Server URL: http://{host}:{port}") # noqa: T201
|
|
97
|
+
print(f"🎨 UI enabled: {'Yes' if ui_enabled else 'No'}") # noqa: T201
|
|
98
|
+
print(f"🔄 Auto-reload: {'Yes' if reload else 'No'}") # noqa: T201
|
|
99
|
+
print("=" * 50) # noqa: T201
|
|
100
|
+
print("🔍 Scanning for entities...") # noqa: T201
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def main() -> None:
|
|
104
|
+
"""Main CLI entry point."""
|
|
105
|
+
parser = create_cli_parser()
|
|
106
|
+
args = parser.parse_args()
|
|
107
|
+
|
|
108
|
+
# Setup logging
|
|
109
|
+
setup_logging(args.log_level)
|
|
110
|
+
|
|
111
|
+
# Validate directory
|
|
112
|
+
entities_dir = validate_directory(args.directory)
|
|
113
|
+
|
|
114
|
+
# Extract parameters directly from args
|
|
115
|
+
ui_enabled = not args.headless
|
|
116
|
+
|
|
117
|
+
# Print startup info
|
|
118
|
+
print_startup_info(entities_dir, args.host, args.port, ui_enabled, args.reload)
|
|
119
|
+
|
|
120
|
+
# Import and start server
|
|
121
|
+
try:
|
|
122
|
+
from . import serve
|
|
123
|
+
|
|
124
|
+
serve(
|
|
125
|
+
entities_dir=entities_dir,
|
|
126
|
+
port=args.port,
|
|
127
|
+
host=args.host,
|
|
128
|
+
auto_open=not args.no_open,
|
|
129
|
+
ui_enabled=ui_enabled,
|
|
130
|
+
tracing_enabled=args.tracing,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
except KeyboardInterrupt:
|
|
134
|
+
print("\n👋 Shutting down Agent Framework DevUI...") # noqa: T201
|
|
135
|
+
sys.exit(0)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.exception("Failed to start server")
|
|
138
|
+
print(f"❌ Error: {e}", file=sys.stderr) # noqa: T201
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
if __name__ == "__main__":
|
|
143
|
+
main()
|