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.

@@ -0,0 +1,191 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ """Session management for agent execution tracking."""
4
+
5
+ import logging
6
+ import uuid
7
+ from datetime import datetime
8
+ from typing import Any
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ # Type aliases for better readability
13
+ SessionData = dict[str, Any]
14
+ RequestRecord = dict[str, Any]
15
+ SessionSummary = dict[str, Any]
16
+
17
+
18
+ class SessionManager:
19
+ """Manages execution sessions for tracking requests and context."""
20
+
21
+ def __init__(self) -> None:
22
+ """Initialize the session manager."""
23
+ self.sessions: dict[str, SessionData] = {}
24
+
25
+ def create_session(self, session_id: str | None = None) -> str:
26
+ """Create a new execution session.
27
+
28
+ Args:
29
+ session_id: Optional session ID, if not provided a new one is generated
30
+
31
+ Returns:
32
+ Session ID
33
+ """
34
+ if not session_id:
35
+ session_id = str(uuid.uuid4())
36
+
37
+ self.sessions[session_id] = {
38
+ "id": session_id,
39
+ "created_at": datetime.now(),
40
+ "requests": [],
41
+ "context": {},
42
+ "active": True,
43
+ }
44
+
45
+ logger.debug(f"Created session: {session_id}")
46
+ return session_id
47
+
48
+ def get_session(self, session_id: str) -> SessionData | None:
49
+ """Get session information.
50
+
51
+ Args:
52
+ session_id: Session ID
53
+
54
+ Returns:
55
+ Session data or None if not found
56
+ """
57
+ return self.sessions.get(session_id)
58
+
59
+ def close_session(self, session_id: str) -> None:
60
+ """Close and cleanup a session.
61
+
62
+ Args:
63
+ session_id: Session ID to close
64
+ """
65
+ if session_id in self.sessions:
66
+ self.sessions[session_id]["active"] = False
67
+ logger.debug(f"Closed session: {session_id}")
68
+
69
+ def add_request_record(
70
+ self, session_id: str, entity_id: str, executor_name: str, request_input: Any, model: str
71
+ ) -> str:
72
+ """Add a request record to a session.
73
+
74
+ Args:
75
+ session_id: Session ID
76
+ entity_id: ID of the entity being executed
77
+ executor_name: Name of the executor
78
+ request_input: Input for the request
79
+ model: Model name
80
+
81
+ Returns:
82
+ Request ID
83
+ """
84
+ session = self.get_session(session_id)
85
+ if not session:
86
+ return ""
87
+
88
+ request_record: RequestRecord = {
89
+ "id": str(uuid.uuid4()),
90
+ "timestamp": datetime.now(),
91
+ "entity_id": entity_id,
92
+ "executor": executor_name,
93
+ "input": request_input,
94
+ "model": model,
95
+ "stream": True,
96
+ }
97
+ session["requests"].append(request_record)
98
+ return str(request_record["id"])
99
+
100
+ def update_request_record(self, session_id: str, request_id: str, updates: dict[str, Any]) -> None:
101
+ """Update a request record in a session.
102
+
103
+ Args:
104
+ session_id: Session ID
105
+ request_id: Request ID to update
106
+ updates: Dictionary of updates to apply
107
+ """
108
+ session = self.get_session(session_id)
109
+ if not session:
110
+ return
111
+
112
+ for request in session["requests"]:
113
+ if request["id"] == request_id:
114
+ request.update(updates)
115
+ break
116
+
117
+ def get_session_history(self, session_id: str) -> SessionSummary | None:
118
+ """Get session execution history.
119
+
120
+ Args:
121
+ session_id: Session ID
122
+
123
+ Returns:
124
+ Session history or None if not found
125
+ """
126
+ session = self.get_session(session_id)
127
+ if not session:
128
+ return None
129
+
130
+ return {
131
+ "session_id": session_id,
132
+ "created_at": session["created_at"].isoformat(),
133
+ "active": session["active"],
134
+ "request_count": len(session["requests"]),
135
+ "requests": [
136
+ {
137
+ "id": req["id"],
138
+ "timestamp": req["timestamp"].isoformat(),
139
+ "entity_id": req["entity_id"],
140
+ "executor": req["executor"],
141
+ "model": req["model"],
142
+ "input_length": len(str(req["input"])) if req["input"] else 0,
143
+ "execution_time": req.get("execution_time"),
144
+ "status": req.get("status", "unknown"),
145
+ }
146
+ for req in session["requests"]
147
+ ],
148
+ }
149
+
150
+ def get_active_sessions(self) -> list[SessionSummary]:
151
+ """Get list of active sessions.
152
+
153
+ Returns:
154
+ List of active session summaries
155
+ """
156
+ active_sessions = []
157
+
158
+ for session_id, session in self.sessions.items():
159
+ if session["active"]:
160
+ active_sessions.append({
161
+ "session_id": session_id,
162
+ "created_at": session["created_at"].isoformat(),
163
+ "request_count": len(session["requests"]),
164
+ "last_activity": (
165
+ session["requests"][-1]["timestamp"].isoformat()
166
+ if session["requests"]
167
+ else session["created_at"].isoformat()
168
+ ),
169
+ })
170
+
171
+ return active_sessions
172
+
173
+ def cleanup_old_sessions(self, max_age_hours: int = 24) -> None:
174
+ """Cleanup old sessions to prevent memory leaks.
175
+
176
+ Args:
177
+ max_age_hours: Maximum age of sessions to keep in hours
178
+ """
179
+ cutoff_time = datetime.now().timestamp() - (max_age_hours * 3600)
180
+
181
+ sessions_to_remove = []
182
+ for session_id, session in self.sessions.items():
183
+ if session["created_at"].timestamp() < cutoff_time:
184
+ sessions_to_remove.append(session_id)
185
+
186
+ for session_id in sessions_to_remove:
187
+ del self.sessions[session_id]
188
+ logger.debug(f"Cleaned up old session: {session_id}")
189
+
190
+ if sessions_to_remove:
191
+ logger.info(f"Cleaned up {len(sessions_to_remove)} old sessions")
@@ -0,0 +1,168 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ """Simplified tracing integration for Agent Framework Server."""
4
+
5
+ import logging
6
+ from collections.abc import Generator, Sequence
7
+ from contextlib import contextmanager
8
+ from datetime import datetime
9
+ from typing import Any
10
+
11
+ from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
12
+
13
+ from .models import ResponseTraceEvent
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class SimpleTraceCollector(SpanExporter):
19
+ """Simple trace collector that captures spans for direct yielding."""
20
+
21
+ def __init__(self, session_id: str | None = None, entity_id: str | None = None) -> None:
22
+ """Initialize trace collector.
23
+
24
+ Args:
25
+ session_id: Session identifier for context
26
+ entity_id: Entity identifier for context
27
+ """
28
+ self.session_id = session_id
29
+ self.entity_id = entity_id
30
+ self.collected_events: list[ResponseTraceEvent] = []
31
+
32
+ def export(self, spans: Sequence[Any]) -> SpanExportResult:
33
+ """Collect spans as trace events.
34
+
35
+ Args:
36
+ spans: Sequence of OpenTelemetry spans
37
+
38
+ Returns:
39
+ SpanExportResult indicating success
40
+ """
41
+ logger.debug(f"SimpleTraceCollector received {len(spans)} spans")
42
+
43
+ try:
44
+ for span in spans:
45
+ trace_event = self._convert_span_to_trace_event(span)
46
+ if trace_event:
47
+ self.collected_events.append(trace_event)
48
+ logger.debug(f"Collected trace event: {span.name}")
49
+
50
+ return SpanExportResult.SUCCESS
51
+
52
+ except Exception as e:
53
+ logger.error(f"Error collecting trace spans: {e}")
54
+ return SpanExportResult.FAILURE
55
+
56
+ def force_flush(self, timeout_millis: int = 30000) -> bool:
57
+ """Force flush spans (no-op for simple collection)."""
58
+ return True
59
+
60
+ def get_pending_events(self) -> list[ResponseTraceEvent]:
61
+ """Get and clear pending trace events.
62
+
63
+ Returns:
64
+ List of collected trace events, clearing the internal list
65
+ """
66
+ events = self.collected_events.copy()
67
+ self.collected_events.clear()
68
+ return events
69
+
70
+ def _convert_span_to_trace_event(self, span: Any) -> ResponseTraceEvent | None:
71
+ """Convert OpenTelemetry span to ResponseTraceEvent.
72
+
73
+ Args:
74
+ span: OpenTelemetry span
75
+
76
+ Returns:
77
+ ResponseTraceEvent or None if conversion fails
78
+ """
79
+ try:
80
+ start_time = span.start_time / 1_000_000_000 # Convert from nanoseconds
81
+ end_time = span.end_time / 1_000_000_000 if span.end_time else None
82
+ duration_ms = ((end_time - start_time) * 1000) if end_time else None
83
+
84
+ # Build trace data
85
+ trace_data = {
86
+ "type": "trace_span",
87
+ "span_id": str(span.context.span_id),
88
+ "trace_id": str(span.context.trace_id),
89
+ "parent_span_id": str(span.parent.span_id) if span.parent else None,
90
+ "operation_name": span.name,
91
+ "start_time": start_time,
92
+ "end_time": end_time,
93
+ "duration_ms": duration_ms,
94
+ "attributes": dict(span.attributes) if span.attributes else {},
95
+ "status": str(span.status.status_code) if hasattr(span, "status") else "OK",
96
+ "session_id": self.session_id,
97
+ "entity_id": self.entity_id,
98
+ }
99
+
100
+ # Add events if available
101
+ if hasattr(span, "events") and span.events:
102
+ trace_data["events"] = [
103
+ {
104
+ "name": event.name,
105
+ "timestamp": event.timestamp / 1_000_000_000,
106
+ "attributes": dict(event.attributes) if event.attributes else {},
107
+ }
108
+ for event in span.events
109
+ ]
110
+
111
+ # Add error information if span failed
112
+ if hasattr(span, "status") and span.status.status_code.name == "ERROR":
113
+ trace_data["error"] = span.status.description or "Unknown error"
114
+
115
+ return ResponseTraceEvent(type="trace_event", data=trace_data, timestamp=datetime.now().isoformat())
116
+
117
+ except Exception as e:
118
+ logger.warning(f"Failed to convert span {getattr(span, 'name', 'unknown')}: {e}")
119
+ return None
120
+
121
+
122
+ @contextmanager
123
+ def capture_traces(
124
+ session_id: str | None = None, entity_id: str | None = None
125
+ ) -> Generator[SimpleTraceCollector, None, None]:
126
+ """Context manager to capture traces during execution.
127
+
128
+ Args:
129
+ session_id: Session identifier for context
130
+ entity_id: Entity identifier for context
131
+
132
+ Yields:
133
+ SimpleTraceCollector instance to get trace events from
134
+ """
135
+ collector = SimpleTraceCollector(session_id, entity_id)
136
+
137
+ try:
138
+ from opentelemetry import trace
139
+ from opentelemetry.sdk.trace import TracerProvider
140
+ from opentelemetry.sdk.trace.export import SimpleSpanProcessor
141
+
142
+ # Get current tracer provider and add our collector
143
+ provider = trace.get_tracer_provider()
144
+ processor = SimpleSpanProcessor(collector)
145
+
146
+ # Check if this is a real TracerProvider (not the default NoOpTracerProvider)
147
+ if isinstance(provider, TracerProvider):
148
+ provider.add_span_processor(processor)
149
+ logger.debug(f"Added trace collector to TracerProvider for session: {session_id}, entity: {entity_id}")
150
+
151
+ try:
152
+ yield collector
153
+ finally:
154
+ # Clean up - shutdown processor
155
+ try:
156
+ processor.shutdown()
157
+ except Exception as e:
158
+ logger.debug(f"Error shutting down processor: {e}")
159
+ else:
160
+ logger.warning(f"No real TracerProvider available, got: {type(provider)}")
161
+ yield collector
162
+
163
+ except ImportError:
164
+ logger.debug("OpenTelemetry not available")
165
+ yield collector
166
+ except Exception as e:
167
+ logger.error(f"Error setting up trace capture: {e}")
168
+ yield collector
@@ -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,51 @@
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
+ # Workflow-specific fields (populated only for detailed info requests)
41
+ executors: list[str] | None = None
42
+ workflow_dump: dict[str, Any] | None = None
43
+ input_schema: dict[str, Any] | None = None
44
+ input_type_name: str | None = None
45
+ start_executor_id: str | None = None
46
+
47
+
48
+ class DiscoveryResponse(BaseModel):
49
+ """Response model for entity discovery."""
50
+
51
+ entities: list[EntityInfo] = Field(default_factory=list)
@@ -0,0 +1,209 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ """Custom OpenAI-compatible event types for Agent Framework extensions.
4
+
5
+ These are custom event types that extend beyond the standard OpenAI Responses API
6
+ to support Agent Framework specific features like workflows, traces, and function results.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any, Literal
12
+
13
+ from pydantic import BaseModel, ConfigDict
14
+
15
+ # Custom Agent Framework OpenAI event types for structured data
16
+
17
+
18
+ class ResponseWorkflowEventDelta(BaseModel):
19
+ """Structured workflow event with completion tracking."""
20
+
21
+ type: Literal["response.workflow_event.delta"] = "response.workflow_event.delta"
22
+ delta: dict[str, Any]
23
+ executor_id: str | None = None
24
+ is_complete: bool = False # Track if this is the final part
25
+ item_id: str
26
+ output_index: int = 0
27
+ sequence_number: int
28
+
29
+
30
+ class ResponseWorkflowEventComplete(BaseModel):
31
+ """Complete workflow event data."""
32
+
33
+ type: Literal["response.workflow_event.complete"] = "response.workflow_event.complete"
34
+ data: dict[str, Any] # Complete event data, not delta
35
+ executor_id: str | None = None
36
+ item_id: str
37
+ output_index: int = 0
38
+ sequence_number: int
39
+
40
+
41
+ class ResponseFunctionResultDelta(BaseModel):
42
+ """Structured function result with completion tracking."""
43
+
44
+ type: Literal["response.function_result.delta"] = "response.function_result.delta"
45
+ delta: dict[str, Any]
46
+ call_id: str
47
+ is_complete: bool = False
48
+ item_id: str
49
+ output_index: int = 0
50
+ sequence_number: int
51
+
52
+
53
+ class ResponseFunctionResultComplete(BaseModel):
54
+ """Complete function result data."""
55
+
56
+ type: Literal["response.function_result.complete"] = "response.function_result.complete"
57
+ data: dict[str, Any] # Complete function result data, not delta
58
+ call_id: str
59
+ item_id: str
60
+ output_index: int = 0
61
+ sequence_number: int
62
+
63
+
64
+ class ResponseTraceEventDelta(BaseModel):
65
+ """Structured trace event with completion tracking."""
66
+
67
+ type: Literal["response.trace.delta"] = "response.trace.delta"
68
+ delta: dict[str, Any]
69
+ span_id: str | None = None
70
+ is_complete: bool = False
71
+ item_id: str
72
+ output_index: int = 0
73
+ sequence_number: int
74
+
75
+
76
+ class ResponseTraceEventComplete(BaseModel):
77
+ """Complete trace event data."""
78
+
79
+ type: Literal["response.trace.complete"] = "response.trace.complete"
80
+ data: dict[str, Any] # Complete trace data, not delta
81
+ span_id: str | None = None
82
+ item_id: str
83
+ output_index: int = 0
84
+ sequence_number: int
85
+
86
+
87
+ class ResponseUsageEventDelta(BaseModel):
88
+ """Structured usage event with completion tracking."""
89
+
90
+ type: Literal["response.usage.delta"] = "response.usage.delta"
91
+ delta: dict[str, Any]
92
+ is_complete: bool = False
93
+ item_id: str
94
+ output_index: int = 0
95
+ sequence_number: int
96
+
97
+
98
+ class ResponseUsageEventComplete(BaseModel):
99
+ """Complete usage event data."""
100
+
101
+ type: Literal["response.usage.complete"] = "response.usage.complete"
102
+ data: dict[str, Any] # Complete usage data, not delta
103
+ item_id: str
104
+ output_index: int = 0
105
+ sequence_number: int
106
+
107
+
108
+ # Agent Framework extension fields
109
+ class AgentFrameworkExtraBody(BaseModel):
110
+ """Agent Framework specific routing fields for OpenAI requests."""
111
+
112
+ entity_id: str
113
+ thread_id: str | None = None
114
+ input_data: dict[str, Any] | None = None
115
+
116
+ model_config = ConfigDict(extra="allow")
117
+
118
+
119
+ # Agent Framework Request Model - Extending real OpenAI types
120
+ class AgentFrameworkRequest(BaseModel):
121
+ """OpenAI ResponseCreateParams with Agent Framework extensions.
122
+
123
+ This properly extends the real OpenAI API request format while adding
124
+ our custom routing fields in extra_body.
125
+ """
126
+
127
+ # All OpenAI fields from ResponseCreateParams
128
+ model: str
129
+ input: str | list[Any] # ResponseInputParam
130
+ stream: bool | None = False
131
+
132
+ # Common OpenAI optional fields
133
+ instructions: str | None = None
134
+ metadata: dict[str, Any] | None = None
135
+ temperature: float | None = None
136
+ max_output_tokens: int | None = None
137
+ tools: list[dict[str, Any]] | None = None
138
+
139
+ # Agent Framework extension - strongly typed
140
+ extra_body: AgentFrameworkExtraBody | None = None
141
+
142
+ entity_id: str | None = None # Allow entity_id as top-level field
143
+
144
+ model_config = ConfigDict(extra="allow")
145
+
146
+ def get_entity_id(self) -> str | None:
147
+ """Get entity_id from either top-level field or extra_body."""
148
+ # Priority 1: Top-level entity_id field
149
+ if self.entity_id:
150
+ return self.entity_id
151
+
152
+ # Priority 2: entity_id in extra_body
153
+ if self.extra_body and hasattr(self.extra_body, "entity_id"):
154
+ return self.extra_body.entity_id
155
+
156
+ return None
157
+
158
+ def to_openai_params(self) -> dict[str, Any]:
159
+ """Convert to dict for OpenAI client compatibility."""
160
+ data = self.model_dump(exclude={"extra_body", "entity_id"}, exclude_none=True)
161
+ if self.extra_body:
162
+ # Don't merge extra_body into main params to keep them separate
163
+ data["extra_body"] = self.extra_body
164
+ return data
165
+
166
+
167
+ # Error handling
168
+ class ResponseTraceEvent(BaseModel):
169
+ """Trace event for execution tracing."""
170
+
171
+ type: Literal["trace_event"] = "trace_event"
172
+ data: dict[str, Any]
173
+ timestamp: str
174
+
175
+
176
+ class OpenAIError(BaseModel):
177
+ """OpenAI standard error response model."""
178
+
179
+ error: dict[str, Any]
180
+
181
+ @classmethod
182
+ def create(cls, message: str, type: str = "invalid_request_error", code: str | None = None) -> OpenAIError:
183
+ """Create a standard OpenAI error response."""
184
+ error_data = {"message": message, "type": type, "code": code}
185
+ return cls(error=error_data)
186
+
187
+ def to_dict(self) -> dict[str, Any]:
188
+ """Return the error payload as a plain mapping."""
189
+ return {"error": dict(self.error)}
190
+
191
+ def to_json(self) -> str:
192
+ """Return the error payload serialized to JSON."""
193
+ return self.model_dump_json()
194
+
195
+
196
+ # Export all custom types
197
+ __all__ = [
198
+ "AgentFrameworkRequest",
199
+ "OpenAIError",
200
+ "ResponseFunctionResultComplete",
201
+ "ResponseFunctionResultDelta",
202
+ "ResponseTraceEvent",
203
+ "ResponseTraceEventComplete",
204
+ "ResponseTraceEventDelta",
205
+ "ResponseUsageEventComplete",
206
+ "ResponseUsageEventDelta",
207
+ "ResponseWorkflowEventComplete",
208
+ "ResponseWorkflowEventDelta",
209
+ ]