signalwire-agents 0.1.1__py3-none-any.whl → 0.1.5__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.
- signalwire_agents/__init__.py +1 -1
- signalwire_agents/agent_server.py +1 -1
- signalwire_agents/core/__init__.py +29 -0
- signalwire_agents/core/agent_base.py +2541 -0
- signalwire_agents/core/function_result.py +123 -0
- signalwire_agents/core/pom_builder.py +204 -0
- signalwire_agents/core/security/__init__.py +9 -0
- signalwire_agents/core/security/session_manager.py +179 -0
- signalwire_agents/core/state/__init__.py +17 -0
- signalwire_agents/core/state/file_state_manager.py +219 -0
- signalwire_agents/core/state/state_manager.py +101 -0
- signalwire_agents/core/swaig_function.py +172 -0
- signalwire_agents/core/swml_builder.py +214 -0
- signalwire_agents/core/swml_handler.py +227 -0
- signalwire_agents/core/swml_renderer.py +368 -0
- signalwire_agents/core/swml_service.py +1057 -0
- signalwire_agents/prefabs/__init__.py +26 -0
- signalwire_agents/prefabs/concierge.py +267 -0
- signalwire_agents/prefabs/faq_bot.py +305 -0
- signalwire_agents/prefabs/info_gatherer.py +263 -0
- signalwire_agents/prefabs/receptionist.py +295 -0
- signalwire_agents/prefabs/survey.py +378 -0
- signalwire_agents/utils/__init__.py +9 -0
- signalwire_agents/utils/pom_utils.py +9 -0
- signalwire_agents/utils/schema_utils.py +357 -0
- signalwire_agents/utils/token_generators.py +9 -0
- signalwire_agents/utils/validators.py +9 -0
- {signalwire_agents-0.1.1.dist-info → signalwire_agents-0.1.5.dist-info}/METADATA +1 -1
- signalwire_agents-0.1.5.dist-info/RECORD +34 -0
- signalwire_agents-0.1.1.dist-info/RECORD +0 -9
- {signalwire_agents-0.1.1.data → signalwire_agents-0.1.5.data}/data/schema.json +0 -0
- {signalwire_agents-0.1.1.dist-info → signalwire_agents-0.1.5.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.1.dist-info → signalwire_agents-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.1.dist-info → signalwire_agents-0.1.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,227 @@
|
|
1
|
+
"""
|
2
|
+
Copyright (c) 2025 SignalWire
|
3
|
+
|
4
|
+
This file is part of the SignalWire AI Agents SDK.
|
5
|
+
|
6
|
+
Licensed under the MIT License.
|
7
|
+
See LICENSE file in the project root for full license information.
|
8
|
+
"""
|
9
|
+
|
10
|
+
"""
|
11
|
+
SWML Verb Handlers - Interface and implementations for SWML verb handling
|
12
|
+
|
13
|
+
This module defines the base interface for SWML verb handlers and provides
|
14
|
+
implementations for specific verbs that require special handling.
|
15
|
+
"""
|
16
|
+
|
17
|
+
from abc import ABC, abstractmethod
|
18
|
+
from typing import Dict, List, Any, Optional, Tuple
|
19
|
+
|
20
|
+
|
21
|
+
class SWMLVerbHandler(ABC):
|
22
|
+
"""
|
23
|
+
Base interface for SWML verb handlers
|
24
|
+
|
25
|
+
This abstract class defines the interface that all SWML verb handlers
|
26
|
+
must implement. Verb handlers provide specialized logic for complex
|
27
|
+
SWML verbs that cannot be handled generically.
|
28
|
+
"""
|
29
|
+
|
30
|
+
@abstractmethod
|
31
|
+
def get_verb_name(self) -> str:
|
32
|
+
"""
|
33
|
+
Get the name of the verb this handler handles
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
The verb name as a string
|
37
|
+
"""
|
38
|
+
pass
|
39
|
+
|
40
|
+
@abstractmethod
|
41
|
+
def validate_config(self, config: Dict[str, Any]) -> Tuple[bool, List[str]]:
|
42
|
+
"""
|
43
|
+
Validate the configuration for this verb
|
44
|
+
|
45
|
+
Args:
|
46
|
+
config: The configuration dictionary for this verb
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
(is_valid, error_messages) tuple
|
50
|
+
"""
|
51
|
+
pass
|
52
|
+
|
53
|
+
@abstractmethod
|
54
|
+
def build_config(self, **kwargs) -> Dict[str, Any]:
|
55
|
+
"""
|
56
|
+
Build a configuration for this verb from the provided arguments
|
57
|
+
|
58
|
+
Args:
|
59
|
+
**kwargs: Keyword arguments specific to this verb
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
Configuration dictionary
|
63
|
+
"""
|
64
|
+
pass
|
65
|
+
|
66
|
+
|
67
|
+
class AIVerbHandler(SWMLVerbHandler):
|
68
|
+
"""
|
69
|
+
Handler for the SWML 'ai' verb
|
70
|
+
|
71
|
+
The 'ai' verb is complex and requires specialized handling, particularly
|
72
|
+
for managing prompts, SWAIG functions, and AI configurations.
|
73
|
+
"""
|
74
|
+
|
75
|
+
def get_verb_name(self) -> str:
|
76
|
+
"""
|
77
|
+
Get the name of the verb this handler handles
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
"ai" as the verb name
|
81
|
+
"""
|
82
|
+
return "ai"
|
83
|
+
|
84
|
+
def validate_config(self, config: Dict[str, Any]) -> Tuple[bool, List[str]]:
|
85
|
+
"""
|
86
|
+
Validate the configuration for the AI verb
|
87
|
+
|
88
|
+
Args:
|
89
|
+
config: The configuration dictionary for the AI verb
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
(is_valid, error_messages) tuple
|
93
|
+
"""
|
94
|
+
errors = []
|
95
|
+
|
96
|
+
# Check required fields
|
97
|
+
if "prompt" not in config:
|
98
|
+
errors.append("Missing required field 'prompt'")
|
99
|
+
|
100
|
+
# Validate prompt structure if present
|
101
|
+
if "prompt" in config:
|
102
|
+
prompt = config["prompt"]
|
103
|
+
if not isinstance(prompt, dict):
|
104
|
+
errors.append("'prompt' must be an object")
|
105
|
+
elif "text" not in prompt and "pom" not in prompt:
|
106
|
+
errors.append("'prompt' must contain either 'text' or 'pom'")
|
107
|
+
|
108
|
+
# Validate SWAIG structure if present
|
109
|
+
if "SWAIG" in config:
|
110
|
+
swaig = config["SWAIG"]
|
111
|
+
if not isinstance(swaig, dict):
|
112
|
+
errors.append("'SWAIG' must be an object")
|
113
|
+
|
114
|
+
return len(errors) == 0, errors
|
115
|
+
|
116
|
+
def build_config(self,
|
117
|
+
prompt_text: Optional[str] = None,
|
118
|
+
prompt_pom: Optional[List[Dict[str, Any]]] = None,
|
119
|
+
post_prompt: Optional[str] = None,
|
120
|
+
post_prompt_url: Optional[str] = None,
|
121
|
+
swaig: Optional[Dict[str, Any]] = None,
|
122
|
+
**kwargs) -> Dict[str, Any]:
|
123
|
+
"""
|
124
|
+
Build a configuration for the AI verb
|
125
|
+
|
126
|
+
Args:
|
127
|
+
prompt_text: Text prompt for the AI (mutually exclusive with prompt_pom)
|
128
|
+
prompt_pom: POM structure for the AI prompt (mutually exclusive with prompt_text)
|
129
|
+
post_prompt: Optional post-prompt text
|
130
|
+
post_prompt_url: Optional URL for post-prompt processing
|
131
|
+
swaig: Optional SWAIG configuration
|
132
|
+
**kwargs: Additional AI parameters
|
133
|
+
|
134
|
+
Returns:
|
135
|
+
AI verb configuration dictionary
|
136
|
+
"""
|
137
|
+
config = {}
|
138
|
+
|
139
|
+
# Add prompt (either text or POM)
|
140
|
+
if prompt_text is not None:
|
141
|
+
config["prompt"] = {"text": prompt_text}
|
142
|
+
elif prompt_pom is not None:
|
143
|
+
config["prompt"] = {"pom": prompt_pom}
|
144
|
+
else:
|
145
|
+
raise ValueError("Either prompt_text or prompt_pom must be provided")
|
146
|
+
|
147
|
+
# Add post-prompt if provided
|
148
|
+
if post_prompt is not None:
|
149
|
+
config["post_prompt"] = {"text": post_prompt}
|
150
|
+
|
151
|
+
# Add post-prompt URL if provided
|
152
|
+
if post_prompt_url is not None:
|
153
|
+
config["post_prompt_url"] = post_prompt_url
|
154
|
+
|
155
|
+
# Add SWAIG if provided
|
156
|
+
if swaig is not None:
|
157
|
+
config["SWAIG"] = swaig
|
158
|
+
|
159
|
+
# Add any additional parameters
|
160
|
+
if "params" not in config:
|
161
|
+
config["params"] = {}
|
162
|
+
|
163
|
+
for key, value in kwargs.items():
|
164
|
+
# Special handling for certain parameters
|
165
|
+
if key == "languages":
|
166
|
+
config["languages"] = value
|
167
|
+
elif key == "hints":
|
168
|
+
config["hints"] = value
|
169
|
+
elif key == "pronounce":
|
170
|
+
config["pronounce"] = value
|
171
|
+
elif key == "global_data":
|
172
|
+
config["global_data"] = value
|
173
|
+
else:
|
174
|
+
# Add to params object
|
175
|
+
config["params"][key] = value
|
176
|
+
|
177
|
+
return config
|
178
|
+
|
179
|
+
|
180
|
+
class VerbHandlerRegistry:
|
181
|
+
"""
|
182
|
+
Registry for SWML verb handlers
|
183
|
+
|
184
|
+
This class maintains a registry of handlers for special SWML verbs
|
185
|
+
and provides methods for accessing and using them.
|
186
|
+
"""
|
187
|
+
|
188
|
+
def __init__(self):
|
189
|
+
"""Initialize the registry with default handlers"""
|
190
|
+
self._handlers = {}
|
191
|
+
|
192
|
+
# Register default handlers
|
193
|
+
self.register_handler(AIVerbHandler())
|
194
|
+
|
195
|
+
def register_handler(self, handler: SWMLVerbHandler) -> None:
|
196
|
+
"""
|
197
|
+
Register a new verb handler
|
198
|
+
|
199
|
+
Args:
|
200
|
+
handler: The handler to register
|
201
|
+
"""
|
202
|
+
verb_name = handler.get_verb_name()
|
203
|
+
self._handlers[verb_name] = handler
|
204
|
+
|
205
|
+
def get_handler(self, verb_name: str) -> Optional[SWMLVerbHandler]:
|
206
|
+
"""
|
207
|
+
Get the handler for a specific verb
|
208
|
+
|
209
|
+
Args:
|
210
|
+
verb_name: The name of the verb
|
211
|
+
|
212
|
+
Returns:
|
213
|
+
The handler if found, None otherwise
|
214
|
+
"""
|
215
|
+
return self._handlers.get(verb_name)
|
216
|
+
|
217
|
+
def has_handler(self, verb_name: str) -> bool:
|
218
|
+
"""
|
219
|
+
Check if a handler exists for a specific verb
|
220
|
+
|
221
|
+
Args:
|
222
|
+
verb_name: The name of the verb
|
223
|
+
|
224
|
+
Returns:
|
225
|
+
True if a handler exists, False otherwise
|
226
|
+
"""
|
227
|
+
return verb_name in self._handlers
|
@@ -0,0 +1,368 @@
|
|
1
|
+
"""
|
2
|
+
Copyright (c) 2025 SignalWire
|
3
|
+
|
4
|
+
This file is part of the SignalWire AI Agents SDK.
|
5
|
+
|
6
|
+
Licensed under the MIT License.
|
7
|
+
See LICENSE file in the project root for full license information.
|
8
|
+
"""
|
9
|
+
|
10
|
+
"""
|
11
|
+
SwmlRenderer for generating complete SWML documents for SignalWire AI Agents
|
12
|
+
"""
|
13
|
+
|
14
|
+
from typing import Dict, List, Any, Optional, Union
|
15
|
+
import json
|
16
|
+
import yaml
|
17
|
+
|
18
|
+
from signalwire_agents.core.swml_service import SWMLService
|
19
|
+
from signalwire_agents.core.swml_builder import SWMLBuilder
|
20
|
+
|
21
|
+
|
22
|
+
class SwmlRenderer:
|
23
|
+
"""
|
24
|
+
Renders SWML documents for SignalWire AI Agents with AI and SWAIG components
|
25
|
+
|
26
|
+
This class provides backward-compatible methods for rendering SWML documents
|
27
|
+
while also supporting the new SWMLService architecture. It can work either
|
28
|
+
standalone (legacy mode) or with a SWMLService instance.
|
29
|
+
"""
|
30
|
+
|
31
|
+
@staticmethod
|
32
|
+
def render_swml(
|
33
|
+
prompt: Union[str, List[Dict[str, Any]]],
|
34
|
+
post_prompt: Optional[str] = None,
|
35
|
+
post_prompt_url: Optional[str] = None,
|
36
|
+
swaig_functions: Optional[List[Dict[str, Any]]] = None,
|
37
|
+
startup_hook_url: Optional[str] = None,
|
38
|
+
hangup_hook_url: Optional[str] = None,
|
39
|
+
prompt_is_pom: bool = False,
|
40
|
+
params: Optional[Dict[str, Any]] = None,
|
41
|
+
add_answer: bool = False,
|
42
|
+
record_call: bool = False,
|
43
|
+
record_format: str = "mp4",
|
44
|
+
record_stereo: bool = True,
|
45
|
+
format: str = "json",
|
46
|
+
default_webhook_url: Optional[str] = None,
|
47
|
+
service: Optional[SWMLService] = None
|
48
|
+
) -> str:
|
49
|
+
"""
|
50
|
+
Generate a complete SWML document with AI configuration
|
51
|
+
|
52
|
+
Args:
|
53
|
+
prompt: Either a string prompt or a POM in list-of-dict format
|
54
|
+
post_prompt: Optional post-prompt text (for summary)
|
55
|
+
post_prompt_url: URL to receive the post-prompt result
|
56
|
+
swaig_functions: List of SWAIG function definitions
|
57
|
+
startup_hook_url: URL for startup hook
|
58
|
+
hangup_hook_url: URL for hangup hook
|
59
|
+
prompt_is_pom: Whether prompt is a POM object or raw text
|
60
|
+
params: Additional AI params (temperature, etc)
|
61
|
+
add_answer: Whether to auto-add the answer block after AI
|
62
|
+
record_call: Whether to add a record_call block
|
63
|
+
record_format: Format for recording the call
|
64
|
+
record_stereo: Whether to record in stereo
|
65
|
+
format: Output format, 'json' or 'yaml'
|
66
|
+
default_webhook_url: Optional default webhook URL for all SWAIG functions
|
67
|
+
service: Optional SWMLService instance to use
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
SWML document as a string
|
71
|
+
"""
|
72
|
+
# If we have a service, use it to build the document
|
73
|
+
if service:
|
74
|
+
# Create a builder for the service
|
75
|
+
builder = SWMLBuilder(service)
|
76
|
+
|
77
|
+
# Reset the document to start fresh
|
78
|
+
builder.reset()
|
79
|
+
|
80
|
+
# Add answer block if requested
|
81
|
+
if add_answer:
|
82
|
+
builder.answer()
|
83
|
+
|
84
|
+
# Add record_call if requested
|
85
|
+
if record_call:
|
86
|
+
# TODO: Add record_call to builder API
|
87
|
+
service.add_verb("record_call", {
|
88
|
+
"format": record_format,
|
89
|
+
"stereo": record_stereo
|
90
|
+
})
|
91
|
+
|
92
|
+
# Configure SWAIG object for AI verb
|
93
|
+
swaig_config = {}
|
94
|
+
functions = []
|
95
|
+
|
96
|
+
# Add startup hook if provided
|
97
|
+
if startup_hook_url:
|
98
|
+
functions.append({
|
99
|
+
"function": "startup_hook",
|
100
|
+
"description": "Called when the call starts",
|
101
|
+
"parameters": {
|
102
|
+
"type": "object",
|
103
|
+
"properties": {}
|
104
|
+
},
|
105
|
+
"web_hook_url": startup_hook_url
|
106
|
+
})
|
107
|
+
|
108
|
+
# Add hangup hook if provided
|
109
|
+
if hangup_hook_url:
|
110
|
+
functions.append({
|
111
|
+
"function": "hangup_hook",
|
112
|
+
"description": "Called when the call ends",
|
113
|
+
"parameters": {
|
114
|
+
"type": "object",
|
115
|
+
"properties": {}
|
116
|
+
},
|
117
|
+
"web_hook_url": hangup_hook_url
|
118
|
+
})
|
119
|
+
|
120
|
+
# Add regular functions if provided
|
121
|
+
if swaig_functions:
|
122
|
+
for func in swaig_functions:
|
123
|
+
# Skip special hooks as we've already added them
|
124
|
+
if func.get("function") not in ["startup_hook", "hangup_hook"]:
|
125
|
+
functions.append(func)
|
126
|
+
|
127
|
+
# Only add SWAIG if we have functions or a default URL
|
128
|
+
if functions or default_webhook_url:
|
129
|
+
swaig_config = {}
|
130
|
+
|
131
|
+
# Add defaults if we have a default webhook URL
|
132
|
+
if default_webhook_url:
|
133
|
+
swaig_config["defaults"] = {
|
134
|
+
"web_hook_url": default_webhook_url
|
135
|
+
}
|
136
|
+
|
137
|
+
# Add functions if we have any
|
138
|
+
if functions:
|
139
|
+
swaig_config["functions"] = functions
|
140
|
+
|
141
|
+
# Add AI verb with appropriate configuration
|
142
|
+
builder.ai(
|
143
|
+
prompt_text=None if prompt_is_pom else prompt,
|
144
|
+
prompt_pom=prompt if prompt_is_pom else None,
|
145
|
+
post_prompt=post_prompt,
|
146
|
+
post_prompt_url=post_prompt_url,
|
147
|
+
swaig=swaig_config if swaig_config else None,
|
148
|
+
**(params or {})
|
149
|
+
)
|
150
|
+
|
151
|
+
# Get the document as a dictionary or string based on format
|
152
|
+
if format.lower() == "yaml":
|
153
|
+
import yaml
|
154
|
+
return yaml.dump(builder.build(), sort_keys=False)
|
155
|
+
else:
|
156
|
+
return builder.render()
|
157
|
+
else:
|
158
|
+
# Legacy implementation (unchanged for backward compatibility)
|
159
|
+
# Start building the SWML document
|
160
|
+
swml = {
|
161
|
+
"version": "1.0.0",
|
162
|
+
"sections": {
|
163
|
+
"main": []
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
# Build the AI block
|
168
|
+
ai_block = {
|
169
|
+
"ai": {
|
170
|
+
"prompt": {}
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
# Set prompt based on type
|
175
|
+
if prompt_is_pom:
|
176
|
+
ai_block["ai"]["prompt"]["pom"] = prompt
|
177
|
+
else:
|
178
|
+
ai_block["ai"]["prompt"]["text"] = prompt
|
179
|
+
|
180
|
+
# Add post_prompt if provided
|
181
|
+
if post_prompt:
|
182
|
+
ai_block["ai"]["post_prompt"] = {
|
183
|
+
"text": post_prompt
|
184
|
+
}
|
185
|
+
|
186
|
+
# Add post_prompt_url if provided
|
187
|
+
if post_prompt_url:
|
188
|
+
ai_block["ai"]["post_prompt_url"] = post_prompt_url
|
189
|
+
|
190
|
+
# SWAIG is a dictionary not an array (fix from old implementation)
|
191
|
+
ai_block["ai"]["SWAIG"] = {}
|
192
|
+
|
193
|
+
# Add defaults if we have a default webhook URL
|
194
|
+
if default_webhook_url:
|
195
|
+
ai_block["ai"]["SWAIG"]["defaults"] = {
|
196
|
+
"web_hook_url": default_webhook_url
|
197
|
+
}
|
198
|
+
|
199
|
+
# Collect all functions
|
200
|
+
functions = []
|
201
|
+
|
202
|
+
# Add SWAIG hooks if provided
|
203
|
+
if startup_hook_url:
|
204
|
+
startup_hook = {
|
205
|
+
"function": "startup_hook",
|
206
|
+
"description": "Called when the call starts",
|
207
|
+
"parameters": {
|
208
|
+
"type": "object",
|
209
|
+
"properties": {}
|
210
|
+
},
|
211
|
+
"web_hook_url": startup_hook_url
|
212
|
+
}
|
213
|
+
functions.append(startup_hook)
|
214
|
+
|
215
|
+
if hangup_hook_url:
|
216
|
+
hangup_hook = {
|
217
|
+
"function": "hangup_hook",
|
218
|
+
"description": "Called when the call ends",
|
219
|
+
"parameters": {
|
220
|
+
"type": "object",
|
221
|
+
"properties": {}
|
222
|
+
},
|
223
|
+
"web_hook_url": hangup_hook_url
|
224
|
+
}
|
225
|
+
functions.append(hangup_hook)
|
226
|
+
|
227
|
+
# Add regular functions from the provided list
|
228
|
+
if swaig_functions:
|
229
|
+
for func in swaig_functions:
|
230
|
+
# Skip special hooks as we've already added them
|
231
|
+
if func.get("function") not in ["startup_hook", "hangup_hook"]:
|
232
|
+
functions.append(func)
|
233
|
+
|
234
|
+
# Add functions to SWAIG if we have any
|
235
|
+
if functions:
|
236
|
+
ai_block["ai"]["SWAIG"]["functions"] = functions
|
237
|
+
|
238
|
+
# Add AI params if provided (but not rendering settings)
|
239
|
+
if params:
|
240
|
+
# Filter out non-AI parameters that should be separate SWML methods
|
241
|
+
ai_params = {k: v for k, v in params.items()
|
242
|
+
if k not in ["auto_answer", "record_call", "record_format", "record_stereo"]}
|
243
|
+
|
244
|
+
# Only update if we have valid AI parameters
|
245
|
+
if ai_params:
|
246
|
+
ai_block["ai"]["params"] = ai_params
|
247
|
+
|
248
|
+
# Start building the SWML blocks
|
249
|
+
main_blocks = []
|
250
|
+
|
251
|
+
# Add answer block first if requested (to answer the call)
|
252
|
+
if add_answer:
|
253
|
+
main_blocks.append({"answer": {}})
|
254
|
+
|
255
|
+
# Add record_call block next if requested
|
256
|
+
if record_call:
|
257
|
+
main_blocks.append({
|
258
|
+
"record_call": {
|
259
|
+
"format": record_format,
|
260
|
+
"stereo": record_stereo # SWML expects a boolean not a string
|
261
|
+
}
|
262
|
+
})
|
263
|
+
|
264
|
+
# Add the AI block
|
265
|
+
main_blocks.append(ai_block)
|
266
|
+
|
267
|
+
# Set the main section to our ordered blocks
|
268
|
+
swml["sections"]["main"] = main_blocks
|
269
|
+
|
270
|
+
# Return in requested format
|
271
|
+
if format.lower() == "yaml":
|
272
|
+
return yaml.dump(swml, sort_keys=False)
|
273
|
+
else:
|
274
|
+
return json.dumps(swml, indent=2)
|
275
|
+
|
276
|
+
@staticmethod
|
277
|
+
def render_function_response_swml(
|
278
|
+
response_text: str,
|
279
|
+
actions: Optional[List[Dict[str, Any]]] = None,
|
280
|
+
format: str = "json",
|
281
|
+
service: Optional[SWMLService] = None
|
282
|
+
) -> str:
|
283
|
+
"""
|
284
|
+
Generate a SWML document for a function response
|
285
|
+
|
286
|
+
Args:
|
287
|
+
response_text: Text to say/display
|
288
|
+
actions: List of SWML actions to execute
|
289
|
+
format: Output format, 'json' or 'yaml'
|
290
|
+
service: Optional SWMLService instance to use
|
291
|
+
|
292
|
+
Returns:
|
293
|
+
SWML document as a string
|
294
|
+
"""
|
295
|
+
if service:
|
296
|
+
# Use the service to build the document
|
297
|
+
service.reset_document()
|
298
|
+
|
299
|
+
# Add a play block for the response if provided
|
300
|
+
if response_text:
|
301
|
+
service.add_verb("play", {
|
302
|
+
"url": f"say:{response_text}"
|
303
|
+
})
|
304
|
+
|
305
|
+
# Add any actions
|
306
|
+
if actions:
|
307
|
+
for action in actions:
|
308
|
+
if action["type"] == "play":
|
309
|
+
service.add_verb("play", {
|
310
|
+
"url": action["url"]
|
311
|
+
})
|
312
|
+
elif action["type"] == "transfer":
|
313
|
+
service.add_verb("connect", [
|
314
|
+
{"to": action["dest"]}
|
315
|
+
])
|
316
|
+
elif action["type"] == "hang_up":
|
317
|
+
service.add_verb("hangup", {})
|
318
|
+
# Additional action types could be added here
|
319
|
+
|
320
|
+
# Return in requested format
|
321
|
+
if format.lower() == "yaml":
|
322
|
+
import yaml
|
323
|
+
return yaml.dump(service.get_document(), sort_keys=False)
|
324
|
+
else:
|
325
|
+
return service.render_document()
|
326
|
+
else:
|
327
|
+
# Legacy implementation (unchanged for backward compatibility)
|
328
|
+
swml = {
|
329
|
+
"version": "1.0.0",
|
330
|
+
"sections": {
|
331
|
+
"main": []
|
332
|
+
}
|
333
|
+
}
|
334
|
+
|
335
|
+
# Add a play block for the response if provided
|
336
|
+
if response_text:
|
337
|
+
swml["sections"]["main"].append({
|
338
|
+
"play": {
|
339
|
+
"url": f"say:{response_text}"
|
340
|
+
}
|
341
|
+
})
|
342
|
+
|
343
|
+
# Add any actions
|
344
|
+
if actions:
|
345
|
+
for action in actions:
|
346
|
+
if action["type"] == "play":
|
347
|
+
swml["sections"]["main"].append({
|
348
|
+
"play": {
|
349
|
+
"url": action["url"]
|
350
|
+
}
|
351
|
+
})
|
352
|
+
elif action["type"] == "transfer":
|
353
|
+
swml["sections"]["main"].append({
|
354
|
+
"connect": [
|
355
|
+
{"to": action["dest"]}
|
356
|
+
]
|
357
|
+
})
|
358
|
+
elif action["type"] == "hang_up":
|
359
|
+
swml["sections"]["main"].append({
|
360
|
+
"hangup": {}
|
361
|
+
})
|
362
|
+
# Additional action types could be added here
|
363
|
+
|
364
|
+
# Return in requested format
|
365
|
+
if format.lower() == "yaml":
|
366
|
+
return yaml.dump(swml, sort_keys=False)
|
367
|
+
else:
|
368
|
+
return json.dumps(swml)
|