agent-framework-devui 0.0.1a0__py3-none-any.whl → 1.0.0b251007__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.

@@ -0,0 +1,421 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ """Utility functions for DevUI."""
4
+
5
+ import inspect
6
+ import json
7
+ import logging
8
+ from dataclasses import fields, is_dataclass
9
+ from typing import Any, get_args, get_origin
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ # ============================================================================
14
+ # Type System Utilities
15
+ # ============================================================================
16
+
17
+
18
+ def is_serialization_mixin(cls: type) -> bool:
19
+ """Check if class is a SerializationMixin subclass.
20
+
21
+ Args:
22
+ cls: Class to check
23
+
24
+ Returns:
25
+ True if class is a SerializationMixin subclass
26
+ """
27
+ try:
28
+ from agent_framework._serialization import SerializationMixin
29
+
30
+ return isinstance(cls, type) and issubclass(cls, SerializationMixin)
31
+ except ImportError:
32
+ return False
33
+
34
+
35
+ def _type_to_schema(type_hint: Any, field_name: str) -> dict[str, Any]:
36
+ """Convert a type hint to JSON schema.
37
+
38
+ Args:
39
+ type_hint: Type hint to convert
40
+ field_name: Name of the field (for documentation)
41
+
42
+ Returns:
43
+ JSON schema dict
44
+ """
45
+ type_str = str(type_hint)
46
+
47
+ # Handle None/Optional
48
+ if type_hint is type(None):
49
+ return {"type": "null"}
50
+
51
+ # Handle basic types
52
+ if type_hint is str or "str" in type_str:
53
+ return {"type": "string"}
54
+ if type_hint is int or "int" in type_str:
55
+ return {"type": "integer"}
56
+ if type_hint is float or "float" in type_str:
57
+ return {"type": "number"}
58
+ if type_hint is bool or "bool" in type_str:
59
+ return {"type": "boolean"}
60
+
61
+ # Handle Literal types (for enum-like values)
62
+ if "Literal" in type_str:
63
+ origin = get_origin(type_hint)
64
+ if origin is not None:
65
+ args = get_args(type_hint)
66
+ if args:
67
+ return {"type": "string", "enum": list(args)}
68
+
69
+ # Handle Union/Optional
70
+ if "Union" in type_str or "Optional" in type_str:
71
+ origin = get_origin(type_hint)
72
+ if origin is not None:
73
+ args = get_args(type_hint)
74
+ # Filter out None type
75
+ non_none_args = [arg for arg in args if arg is not type(None)]
76
+ if len(non_none_args) == 1:
77
+ return _type_to_schema(non_none_args[0], field_name)
78
+ # Multiple types - pick first non-None
79
+ if non_none_args:
80
+ return _type_to_schema(non_none_args[0], field_name)
81
+
82
+ # Handle collections
83
+ if "list" in type_str or "List" in type_str or "Sequence" in type_str:
84
+ origin = get_origin(type_hint)
85
+ if origin is not None:
86
+ args = get_args(type_hint)
87
+ if args:
88
+ items_schema = _type_to_schema(args[0], field_name)
89
+ return {"type": "array", "items": items_schema}
90
+ return {"type": "array"}
91
+
92
+ if "dict" in type_str or "Dict" in type_str or "Mapping" in type_str:
93
+ return {"type": "object"}
94
+
95
+ # Default fallback
96
+ return {"type": "string", "description": f"Type: {type_hint}"}
97
+
98
+
99
+ def generate_schema_from_serialization_mixin(cls: type[Any]) -> dict[str, Any]:
100
+ """Generate JSON schema from SerializationMixin class.
101
+
102
+ Introspects the __init__ signature to extract parameter types and defaults.
103
+
104
+ Args:
105
+ cls: SerializationMixin subclass
106
+
107
+ Returns:
108
+ JSON schema dict
109
+ """
110
+ sig = inspect.signature(cls)
111
+
112
+ # Get type hints
113
+ try:
114
+ from typing import get_type_hints
115
+
116
+ type_hints = get_type_hints(cls)
117
+ except Exception:
118
+ type_hints = {}
119
+
120
+ properties: dict[str, Any] = {}
121
+ required: list[str] = []
122
+
123
+ for param_name, param in sig.parameters.items():
124
+ if param_name in ("self", "kwargs"):
125
+ continue
126
+
127
+ # Get type annotation
128
+ param_type = type_hints.get(param_name, str)
129
+
130
+ # Generate schema for this parameter
131
+ param_schema = _type_to_schema(param_type, param_name)
132
+ properties[param_name] = param_schema
133
+
134
+ # Check if required (no default value, not VAR_KEYWORD)
135
+ if param.default == inspect.Parameter.empty and param.kind != inspect.Parameter.VAR_KEYWORD:
136
+ required.append(param_name)
137
+
138
+ schema: dict[str, Any] = {"type": "object", "properties": properties}
139
+
140
+ if required:
141
+ schema["required"] = required
142
+
143
+ return schema
144
+
145
+
146
+ def generate_schema_from_dataclass(cls: type[Any]) -> dict[str, Any]:
147
+ """Generate JSON schema from dataclass.
148
+
149
+ Args:
150
+ cls: Dataclass type
151
+
152
+ Returns:
153
+ JSON schema dict
154
+ """
155
+ if not is_dataclass(cls):
156
+ return {"type": "object"}
157
+
158
+ properties: dict[str, Any] = {}
159
+ required: list[str] = []
160
+
161
+ for field in fields(cls):
162
+ # Generate schema for field type
163
+ field_schema = _type_to_schema(field.type, field.name)
164
+ properties[field.name] = field_schema
165
+
166
+ # Check if required (no default value)
167
+ if field.default == field.default_factory: # No default
168
+ required.append(field.name)
169
+
170
+ schema: dict[str, Any] = {"type": "object", "properties": properties}
171
+
172
+ if required:
173
+ schema["required"] = required
174
+
175
+ return schema
176
+
177
+
178
+ def generate_input_schema(input_type: type) -> dict[str, Any]:
179
+ """Generate JSON schema for workflow input type.
180
+
181
+ Supports multiple input types in priority order:
182
+ 1. Built-in types (str, dict, int, etc.)
183
+ 2. Pydantic models (via model_json_schema)
184
+ 3. SerializationMixin classes (via __init__ introspection)
185
+ 4. Dataclasses (via fields introspection)
186
+ 5. Fallback to string
187
+
188
+ Args:
189
+ input_type: Input type to generate schema for
190
+
191
+ Returns:
192
+ JSON schema dict
193
+ """
194
+ # 1. Built-in types
195
+ if input_type is str:
196
+ return {"type": "string"}
197
+ if input_type is dict:
198
+ return {"type": "object"}
199
+ if input_type is int:
200
+ return {"type": "integer"}
201
+ if input_type is float:
202
+ return {"type": "number"}
203
+ if input_type is bool:
204
+ return {"type": "boolean"}
205
+
206
+ # 2. Pydantic models (legacy support)
207
+ if hasattr(input_type, "model_json_schema"):
208
+ return input_type.model_json_schema() # type: ignore
209
+
210
+ # 3. SerializationMixin classes (ChatMessage, etc.)
211
+ if is_serialization_mixin(input_type):
212
+ return generate_schema_from_serialization_mixin(input_type)
213
+
214
+ # 4. Dataclasses
215
+ if is_dataclass(input_type):
216
+ return generate_schema_from_dataclass(input_type)
217
+
218
+ # 5. Fallback to string
219
+ type_name = getattr(input_type, "__name__", str(input_type))
220
+ return {"type": "string", "description": f"Input type: {type_name}"}
221
+
222
+
223
+ # ============================================================================
224
+ # Input Parsing Utilities
225
+ # ============================================================================
226
+
227
+
228
+ def parse_input_for_type(input_data: Any, target_type: type) -> Any:
229
+ """Parse input data to match the target type.
230
+
231
+ Handles conversion from raw input (string, dict) to the expected type:
232
+ - Built-in types: direct conversion
233
+ - Pydantic models: use model_validate or model_validate_json
234
+ - SerializationMixin: use from_dict or construct from string
235
+ - Dataclasses: construct from dict
236
+
237
+ Args:
238
+ input_data: Raw input data (string, dict, or already correct type)
239
+ target_type: Expected type for the input
240
+
241
+ Returns:
242
+ Parsed input matching target_type, or original input if parsing fails
243
+ """
244
+ # If already correct type, return as-is
245
+ if isinstance(input_data, target_type):
246
+ return input_data
247
+
248
+ # Handle string input
249
+ if isinstance(input_data, str):
250
+ return _parse_string_input(input_data, target_type)
251
+
252
+ # Handle dict input
253
+ if isinstance(input_data, dict):
254
+ return _parse_dict_input(input_data, target_type)
255
+
256
+ # Fallback: return original
257
+ return input_data
258
+
259
+
260
+ def _parse_string_input(input_str: str, target_type: type) -> Any:
261
+ """Parse string input to target type.
262
+
263
+ Args:
264
+ input_str: Input string
265
+ target_type: Target type
266
+
267
+ Returns:
268
+ Parsed input or original string
269
+ """
270
+ # Built-in types
271
+ if target_type is str:
272
+ return input_str
273
+ if target_type is int:
274
+ try:
275
+ return int(input_str)
276
+ except ValueError:
277
+ return input_str
278
+ elif target_type is float:
279
+ try:
280
+ return float(input_str)
281
+ except ValueError:
282
+ return input_str
283
+ elif target_type is bool:
284
+ return input_str.lower() in ("true", "1", "yes")
285
+
286
+ # Pydantic models
287
+ if hasattr(target_type, "model_validate_json"):
288
+ try:
289
+ # Try parsing as JSON first
290
+ if input_str.strip().startswith("{"):
291
+ return target_type.model_validate_json(input_str) # type: ignore
292
+
293
+ # Try common field names with the string value
294
+ common_fields = ["text", "message", "content", "input", "data"]
295
+ for field in common_fields:
296
+ try:
297
+ return target_type(**{field: input_str}) # type: ignore
298
+ except Exception as e:
299
+ logger.debug(f"Failed to parse string input with field '{field}': {e}")
300
+ continue
301
+ except Exception as e:
302
+ logger.debug(f"Failed to parse string as Pydantic model: {e}")
303
+
304
+ # SerializationMixin (like ChatMessage)
305
+ if is_serialization_mixin(target_type):
306
+ try:
307
+ # Try parsing as JSON dict first
308
+ if input_str.strip().startswith("{"):
309
+ data = json.loads(input_str)
310
+ if hasattr(target_type, "from_dict"):
311
+ return target_type.from_dict(data) # type: ignore
312
+ return target_type(**data) # type: ignore
313
+
314
+ # For ChatMessage specifically: create from text
315
+ # Try common field patterns
316
+ common_fields = ["text", "message", "content"]
317
+ sig = inspect.signature(target_type)
318
+ params = list(sig.parameters.keys())
319
+
320
+ # If it has 'text' param, use it
321
+ if "text" in params:
322
+ try:
323
+ return target_type(role="user", text=input_str) # type: ignore
324
+ except Exception as e:
325
+ logger.debug(f"Failed to create SerializationMixin with text field: {e}")
326
+
327
+ # Try other common fields
328
+ for field in common_fields:
329
+ if field in params:
330
+ try:
331
+ return target_type(**{field: input_str}) # type: ignore
332
+ except Exception as e:
333
+ logger.debug(f"Failed to create SerializationMixin with field '{field}': {e}")
334
+ continue
335
+ except Exception as e:
336
+ logger.debug(f"Failed to parse string as SerializationMixin: {e}")
337
+
338
+ # Dataclasses
339
+ if is_dataclass(target_type):
340
+ try:
341
+ # Try parsing as JSON
342
+ if input_str.strip().startswith("{"):
343
+ data = json.loads(input_str)
344
+ return target_type(**data) # type: ignore
345
+
346
+ # Try common field names
347
+ common_fields = ["text", "message", "content", "input", "data"]
348
+ for field in common_fields:
349
+ try:
350
+ return target_type(**{field: input_str}) # type: ignore
351
+ except Exception as e:
352
+ logger.debug(f"Failed to create dataclass with field '{field}': {e}")
353
+ continue
354
+ except Exception as e:
355
+ logger.debug(f"Failed to parse string as dataclass: {e}")
356
+
357
+ # Fallback: return original string
358
+ return input_str
359
+
360
+
361
+ def _parse_dict_input(input_dict: dict[str, Any], target_type: type) -> Any:
362
+ """Parse dict input to target type.
363
+
364
+ Args:
365
+ input_dict: Input dictionary
366
+ target_type: Target type
367
+
368
+ Returns:
369
+ Parsed input or original dict
370
+ """
371
+ # Handle primitive types - extract from common field names
372
+ if target_type in (str, int, float, bool):
373
+ try:
374
+ # If it's already the right type, return as-is
375
+ if isinstance(input_dict, target_type):
376
+ return input_dict
377
+
378
+ # Try "input" field first (common for workflow inputs)
379
+ if "input" in input_dict:
380
+ return target_type(input_dict["input"]) # type: ignore
381
+
382
+ # If single-key dict, extract the value
383
+ if len(input_dict) == 1:
384
+ value = next(iter(input_dict.values()))
385
+ return target_type(value) # type: ignore
386
+
387
+ # Otherwise, return as-is
388
+ return input_dict
389
+ except (ValueError, TypeError) as e:
390
+ logger.debug(f"Failed to convert dict to {target_type}: {e}")
391
+ return input_dict
392
+
393
+ # If target is dict, return as-is
394
+ if target_type is dict:
395
+ return input_dict
396
+
397
+ # Pydantic models
398
+ if hasattr(target_type, "model_validate"):
399
+ try:
400
+ return target_type.model_validate(input_dict) # type: ignore
401
+ except Exception as e:
402
+ logger.debug(f"Failed to validate dict as Pydantic model: {e}")
403
+
404
+ # SerializationMixin
405
+ if is_serialization_mixin(target_type):
406
+ try:
407
+ if hasattr(target_type, "from_dict"):
408
+ return target_type.from_dict(input_dict) # type: ignore
409
+ return target_type(**input_dict) # type: ignore
410
+ except Exception as e:
411
+ logger.debug(f"Failed to parse dict as SerializationMixin: {e}")
412
+
413
+ # Dataclasses
414
+ if is_dataclass(target_type):
415
+ try:
416
+ return target_type(**input_dict) # type: ignore
417
+ except Exception as e:
418
+ logger.debug(f"Failed to parse dict as dataclass: {e}")
419
+
420
+ # Fallback: return original dict
421
+ return input_dict
@@ -0,0 +1,72 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ """Agent Framework DevUI Models - OpenAI-compatible types and custom extensions."""
4
+
5
+ # Import discovery models
6
+ # Import all OpenAI types directly from the openai package
7
+ from openai.types.responses import (
8
+ Response,
9
+ ResponseErrorEvent,
10
+ ResponseFunctionCallArgumentsDeltaEvent,
11
+ ResponseInputParam,
12
+ ResponseOutputMessage,
13
+ ResponseOutputText,
14
+ ResponseReasoningTextDeltaEvent,
15
+ ResponseStreamEvent,
16
+ ResponseTextDeltaEvent,
17
+ ResponseUsage,
18
+ ToolParam,
19
+ )
20
+ from openai.types.responses.response_usage import InputTokensDetails, OutputTokensDetails
21
+ from openai.types.shared import Metadata, ResponsesModel
22
+
23
+ from ._discovery_models import DiscoveryResponse, EntityInfo
24
+ from ._openai_custom import (
25
+ AgentFrameworkRequest,
26
+ OpenAIError,
27
+ ResponseFunctionResultComplete,
28
+ ResponseFunctionResultDelta,
29
+ ResponseTraceEvent,
30
+ ResponseTraceEventComplete,
31
+ ResponseTraceEventDelta,
32
+ ResponseUsageEventComplete,
33
+ ResponseUsageEventDelta,
34
+ ResponseWorkflowEventComplete,
35
+ ResponseWorkflowEventDelta,
36
+ )
37
+
38
+ # Type alias for compatibility
39
+ OpenAIResponse = Response
40
+
41
+ # Export all types for easy importing
42
+ __all__ = [
43
+ "AgentFrameworkRequest",
44
+ "DiscoveryResponse",
45
+ "EntityInfo",
46
+ "InputTokensDetails",
47
+ "Metadata",
48
+ "OpenAIError",
49
+ "OpenAIResponse",
50
+ "OutputTokensDetails",
51
+ "Response",
52
+ "ResponseErrorEvent",
53
+ "ResponseFunctionCallArgumentsDeltaEvent",
54
+ "ResponseFunctionResultComplete",
55
+ "ResponseFunctionResultDelta",
56
+ "ResponseInputParam",
57
+ "ResponseOutputMessage",
58
+ "ResponseOutputText",
59
+ "ResponseReasoningTextDeltaEvent",
60
+ "ResponseStreamEvent",
61
+ "ResponseTextDeltaEvent",
62
+ "ResponseTraceEvent",
63
+ "ResponseTraceEventComplete",
64
+ "ResponseTraceEventDelta",
65
+ "ResponseUsage",
66
+ "ResponseUsageEventComplete",
67
+ "ResponseUsageEventDelta",
68
+ "ResponseWorkflowEventComplete",
69
+ "ResponseWorkflowEventDelta",
70
+ "ResponsesModel",
71
+ "ToolParam",
72
+ ]
@@ -0,0 +1,58 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ """Discovery API models for entity information."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Any
8
+
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class EnvVarRequirement(BaseModel):
13
+ """Environment variable requirement for an entity."""
14
+
15
+ name: str
16
+ description: str
17
+ required: bool = True
18
+ example: str | None = None
19
+
20
+
21
+ class EntityInfo(BaseModel):
22
+ """Entity information for discovery and detailed views."""
23
+
24
+ # Always present (core entity data)
25
+ id: str
26
+ type: str # "agent", "workflow"
27
+ name: str
28
+ description: str | None = None
29
+ framework: str
30
+ tools: list[str | dict[str, Any]] | None = None
31
+ metadata: dict[str, Any] = Field(default_factory=dict)
32
+
33
+ # Source information
34
+ source: str = "directory" # "directory", "in_memory", "remote_gallery"
35
+ original_url: str | None = None
36
+
37
+ # Environment variable requirements
38
+ required_env_vars: list[EnvVarRequirement] | None = None
39
+
40
+ # Agent-specific fields (optional, populated when available)
41
+ instructions: str | None = None
42
+ model_id: str | None = None
43
+ chat_client_type: str | None = None
44
+ context_providers: list[str] | None = None
45
+ middleware: list[str] | None = None
46
+
47
+ # Workflow-specific fields (populated only for detailed info requests)
48
+ executors: list[str] | None = None
49
+ workflow_dump: dict[str, Any] | None = None
50
+ input_schema: dict[str, Any] | None = None
51
+ input_type_name: str | None = None
52
+ start_executor_id: str | None = None
53
+
54
+
55
+ class DiscoveryResponse(BaseModel):
56
+ """Response model for entity discovery."""
57
+
58
+ entities: list[EntityInfo] = Field(default_factory=list)