signalwire-agents 0.1.6__py3-none-any.whl → 1.0.7__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.
Files changed (140) hide show
  1. signalwire_agents/__init__.py +130 -4
  2. signalwire_agents/agent_server.py +438 -32
  3. signalwire_agents/agents/bedrock.py +296 -0
  4. signalwire_agents/cli/__init__.py +18 -0
  5. signalwire_agents/cli/build_search.py +1367 -0
  6. signalwire_agents/cli/config.py +80 -0
  7. signalwire_agents/cli/core/__init__.py +10 -0
  8. signalwire_agents/cli/core/agent_loader.py +470 -0
  9. signalwire_agents/cli/core/argparse_helpers.py +179 -0
  10. signalwire_agents/cli/core/dynamic_config.py +71 -0
  11. signalwire_agents/cli/core/service_loader.py +303 -0
  12. signalwire_agents/cli/execution/__init__.py +10 -0
  13. signalwire_agents/cli/execution/datamap_exec.py +446 -0
  14. signalwire_agents/cli/execution/webhook_exec.py +134 -0
  15. signalwire_agents/cli/init_project.py +1225 -0
  16. signalwire_agents/cli/output/__init__.py +10 -0
  17. signalwire_agents/cli/output/output_formatter.py +255 -0
  18. signalwire_agents/cli/output/swml_dump.py +186 -0
  19. signalwire_agents/cli/simulation/__init__.py +10 -0
  20. signalwire_agents/cli/simulation/data_generation.py +374 -0
  21. signalwire_agents/cli/simulation/data_overrides.py +200 -0
  22. signalwire_agents/cli/simulation/mock_env.py +282 -0
  23. signalwire_agents/cli/swaig_test_wrapper.py +52 -0
  24. signalwire_agents/cli/test_swaig.py +809 -0
  25. signalwire_agents/cli/types.py +81 -0
  26. signalwire_agents/core/__init__.py +2 -2
  27. signalwire_agents/core/agent/__init__.py +12 -0
  28. signalwire_agents/core/agent/config/__init__.py +12 -0
  29. signalwire_agents/core/agent/deployment/__init__.py +9 -0
  30. signalwire_agents/core/agent/deployment/handlers/__init__.py +9 -0
  31. signalwire_agents/core/agent/prompt/__init__.py +14 -0
  32. signalwire_agents/core/agent/prompt/manager.py +306 -0
  33. signalwire_agents/core/agent/routing/__init__.py +9 -0
  34. signalwire_agents/core/agent/security/__init__.py +9 -0
  35. signalwire_agents/core/agent/swml/__init__.py +9 -0
  36. signalwire_agents/core/agent/tools/__init__.py +15 -0
  37. signalwire_agents/core/agent/tools/decorator.py +97 -0
  38. signalwire_agents/core/agent/tools/registry.py +210 -0
  39. signalwire_agents/core/agent_base.py +959 -2166
  40. signalwire_agents/core/auth_handler.py +233 -0
  41. signalwire_agents/core/config_loader.py +259 -0
  42. signalwire_agents/core/contexts.py +707 -0
  43. signalwire_agents/core/data_map.py +487 -0
  44. signalwire_agents/core/function_result.py +1150 -1
  45. signalwire_agents/core/logging_config.py +376 -0
  46. signalwire_agents/core/mixins/__init__.py +28 -0
  47. signalwire_agents/core/mixins/ai_config_mixin.py +442 -0
  48. signalwire_agents/core/mixins/auth_mixin.py +287 -0
  49. signalwire_agents/core/mixins/prompt_mixin.py +358 -0
  50. signalwire_agents/core/mixins/serverless_mixin.py +368 -0
  51. signalwire_agents/core/mixins/skill_mixin.py +55 -0
  52. signalwire_agents/core/mixins/state_mixin.py +153 -0
  53. signalwire_agents/core/mixins/tool_mixin.py +230 -0
  54. signalwire_agents/core/mixins/web_mixin.py +1134 -0
  55. signalwire_agents/core/security/session_manager.py +174 -86
  56. signalwire_agents/core/security_config.py +333 -0
  57. signalwire_agents/core/skill_base.py +200 -0
  58. signalwire_agents/core/skill_manager.py +244 -0
  59. signalwire_agents/core/swaig_function.py +33 -9
  60. signalwire_agents/core/swml_builder.py +212 -12
  61. signalwire_agents/core/swml_handler.py +43 -13
  62. signalwire_agents/core/swml_renderer.py +123 -297
  63. signalwire_agents/core/swml_service.py +277 -260
  64. signalwire_agents/prefabs/concierge.py +6 -2
  65. signalwire_agents/prefabs/info_gatherer.py +149 -33
  66. signalwire_agents/prefabs/receptionist.py +14 -22
  67. signalwire_agents/prefabs/survey.py +6 -2
  68. signalwire_agents/schema.json +9218 -5489
  69. signalwire_agents/search/__init__.py +137 -0
  70. signalwire_agents/search/document_processor.py +1223 -0
  71. signalwire_agents/search/index_builder.py +804 -0
  72. signalwire_agents/search/migration.py +418 -0
  73. signalwire_agents/search/models.py +30 -0
  74. signalwire_agents/search/pgvector_backend.py +752 -0
  75. signalwire_agents/search/query_processor.py +502 -0
  76. signalwire_agents/search/search_engine.py +1264 -0
  77. signalwire_agents/search/search_service.py +574 -0
  78. signalwire_agents/skills/README.md +452 -0
  79. signalwire_agents/skills/__init__.py +23 -0
  80. signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
  81. signalwire_agents/skills/api_ninjas_trivia/__init__.py +12 -0
  82. signalwire_agents/skills/api_ninjas_trivia/skill.py +237 -0
  83. signalwire_agents/skills/datasphere/README.md +210 -0
  84. signalwire_agents/skills/datasphere/__init__.py +12 -0
  85. signalwire_agents/skills/datasphere/skill.py +310 -0
  86. signalwire_agents/skills/datasphere_serverless/README.md +258 -0
  87. signalwire_agents/skills/datasphere_serverless/__init__.py +10 -0
  88. signalwire_agents/skills/datasphere_serverless/skill.py +237 -0
  89. signalwire_agents/skills/datetime/README.md +132 -0
  90. signalwire_agents/skills/datetime/__init__.py +10 -0
  91. signalwire_agents/skills/datetime/skill.py +126 -0
  92. signalwire_agents/skills/joke/README.md +149 -0
  93. signalwire_agents/skills/joke/__init__.py +10 -0
  94. signalwire_agents/skills/joke/skill.py +109 -0
  95. signalwire_agents/skills/math/README.md +161 -0
  96. signalwire_agents/skills/math/__init__.py +10 -0
  97. signalwire_agents/skills/math/skill.py +105 -0
  98. signalwire_agents/skills/mcp_gateway/README.md +230 -0
  99. signalwire_agents/skills/mcp_gateway/__init__.py +10 -0
  100. signalwire_agents/skills/mcp_gateway/skill.py +421 -0
  101. signalwire_agents/skills/native_vector_search/README.md +210 -0
  102. signalwire_agents/skills/native_vector_search/__init__.py +10 -0
  103. signalwire_agents/skills/native_vector_search/skill.py +820 -0
  104. signalwire_agents/skills/play_background_file/README.md +218 -0
  105. signalwire_agents/skills/play_background_file/__init__.py +12 -0
  106. signalwire_agents/skills/play_background_file/skill.py +242 -0
  107. signalwire_agents/skills/registry.py +459 -0
  108. signalwire_agents/skills/spider/README.md +236 -0
  109. signalwire_agents/skills/spider/__init__.py +13 -0
  110. signalwire_agents/skills/spider/skill.py +598 -0
  111. signalwire_agents/skills/swml_transfer/README.md +395 -0
  112. signalwire_agents/skills/swml_transfer/__init__.py +10 -0
  113. signalwire_agents/skills/swml_transfer/skill.py +359 -0
  114. signalwire_agents/skills/weather_api/README.md +178 -0
  115. signalwire_agents/skills/weather_api/__init__.py +12 -0
  116. signalwire_agents/skills/weather_api/skill.py +191 -0
  117. signalwire_agents/skills/web_search/README.md +163 -0
  118. signalwire_agents/skills/web_search/__init__.py +10 -0
  119. signalwire_agents/skills/web_search/skill.py +739 -0
  120. signalwire_agents/skills/wikipedia_search/README.md +228 -0
  121. signalwire_agents/{core/state → skills/wikipedia_search}/__init__.py +5 -4
  122. signalwire_agents/skills/wikipedia_search/skill.py +210 -0
  123. signalwire_agents/utils/__init__.py +14 -0
  124. signalwire_agents/utils/schema_utils.py +111 -44
  125. signalwire_agents/web/__init__.py +17 -0
  126. signalwire_agents/web/web_service.py +559 -0
  127. signalwire_agents-1.0.7.data/data/share/man/man1/sw-agent-init.1 +307 -0
  128. signalwire_agents-1.0.7.data/data/share/man/man1/sw-search.1 +483 -0
  129. signalwire_agents-1.0.7.data/data/share/man/man1/swaig-test.1 +308 -0
  130. signalwire_agents-1.0.7.dist-info/METADATA +992 -0
  131. signalwire_agents-1.0.7.dist-info/RECORD +142 -0
  132. {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/WHEEL +1 -1
  133. signalwire_agents-1.0.7.dist-info/entry_points.txt +4 -0
  134. signalwire_agents/core/state/file_state_manager.py +0 -219
  135. signalwire_agents/core/state/state_manager.py +0 -101
  136. signalwire_agents-0.1.6.data/data/schema.json +0 -5611
  137. signalwire_agents-0.1.6.dist-info/METADATA +0 -199
  138. signalwire_agents-0.1.6.dist-info/RECORD +0 -34
  139. {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/licenses/LICENSE +0 -0
  140. {signalwire_agents-0.1.6.dist-info → signalwire_agents-1.0.7.dist-info}/top_level.txt +0 -0
@@ -19,6 +19,19 @@ class SwaigFunctionResult:
19
19
  Wrapper around SWAIG function responses that handles proper formatting
20
20
  of response text and actions.
21
21
 
22
+ The result object has three main components:
23
+ 1. response: Text the AI should say back to the user
24
+ 2. action: List of structured actions to execute
25
+ 3. post_process: Whether to let AI take another turn before executing actions
26
+
27
+ Post-processing behavior:
28
+ - post_process=False (default): Execute actions immediately after AI response
29
+ - post_process=True: Let AI respond to user one more time, then execute actions
30
+
31
+ This is useful for confirmation workflows like:
32
+ "I'll transfer you to sales. Do you have any other questions first?"
33
+ (AI can handle follow-up, then execute the transfer)
34
+
22
35
  Example:
23
36
  return SwaigFunctionResult("Found your order")
24
37
 
@@ -42,16 +55,31 @@ class SwaigFunctionResult:
42
55
  {"play": {"url": "music.mp3"}}
43
56
  ])
44
57
  )
58
+
59
+ # With post-processing enabled
60
+ return (
61
+ SwaigFunctionResult("Let me transfer you to billing", post_process=True)
62
+ .connect("+15551234567", final=True)
63
+ )
64
+
65
+ # Using the connect helper
66
+ return (
67
+ SwaigFunctionResult("I'll transfer you to our sales team now")
68
+ .connect("sales@company.com", final=False, from_addr="+15559876543")
69
+ )
45
70
  """
46
- def __init__(self, response: Optional[str] = None):
71
+ def __init__(self, response: Optional[str] = None, post_process: bool = False):
47
72
  """
48
73
  Initialize a new SWAIG function result
49
74
 
50
75
  Args:
51
76
  response: Optional natural language response to include
77
+ post_process: Whether to let AI take another turn before executing actions.
78
+ Defaults to False (execute actions immediately after response).
52
79
  """
53
80
  self.response = response or ""
54
81
  self.action: List[Dict[str, Any]] = []
82
+ self.post_process = post_process
55
83
 
56
84
  def set_response(self, response: str) -> 'SwaigFunctionResult':
57
85
  """
@@ -66,6 +94,23 @@ class SwaigFunctionResult:
66
94
  self.response = response
67
95
  return self
68
96
 
97
+ def set_post_process(self, post_process: bool) -> 'SwaigFunctionResult':
98
+ """
99
+ Set whether to enable post-processing for this result.
100
+
101
+ Post-processing allows the AI to take one more turn with the user
102
+ before executing any actions. This is useful for confirmation workflows.
103
+
104
+ Args:
105
+ post_process: True to let AI respond once more before executing actions,
106
+ False to execute actions immediately after the response.
107
+
108
+ Returns:
109
+ Self for method chaining
110
+ """
111
+ self.post_process = post_process
112
+ return self
113
+
69
114
  def add_action(self, name: str, data: Any) -> 'SwaigFunctionResult':
70
115
  """
71
116
  Add a structured action to the response
@@ -93,6 +138,1102 @@ class SwaigFunctionResult:
93
138
  self.action.extend(actions)
94
139
  return self
95
140
 
141
+ def connect(self, destination: str, final: bool = True, from_addr: Optional[str] = None) -> 'SwaigFunctionResult':
142
+ """
143
+ Add a connect action to transfer/connect the call to another destination.
144
+
145
+ This is a convenience method that abstracts the SWML connect verb, so users
146
+ don't need to manually construct SWML documents.
147
+
148
+ Transfer behavior:
149
+ - final=True: Permanent transfer - call exits the agent completely,
150
+ SWML replaces the agent and call continues there
151
+ - final=False: Temporary transfer - if far end hangs up, call returns
152
+ to the agent to continue the conversation
153
+
154
+ Args:
155
+ destination: Where to connect the call (phone number, SIP address, etc.)
156
+ final: Whether this is a permanent transfer (True) or temporary (False).
157
+ Defaults to True for permanent transfers.
158
+ from_addr: Optional caller ID override (phone number or SIP address).
159
+ If None, uses the current call's from address.
160
+
161
+ Returns:
162
+ Self for method chaining
163
+
164
+ Example:
165
+ # Permanent transfer to a phone number
166
+ result.connect("+15551234567", final=True)
167
+
168
+ # Temporary transfer to SIP address with custom caller ID
169
+ result.connect("support@company.com", final=False, from_addr="+15559876543")
170
+ """
171
+ # Build the connect verb parameters
172
+ connect_params = {"to": destination}
173
+ if from_addr is not None:
174
+ connect_params["from"] = from_addr
175
+
176
+ # Create the SWML action
177
+ swml_action = {
178
+ "SWML": {
179
+ "sections": {
180
+ "main": [{"connect": connect_params}]
181
+ },
182
+ "version": "1.0.0"
183
+ },
184
+ "transfer": str(final).lower() # Convert boolean to "true"/"false" string
185
+ }
186
+
187
+ # Add to actions list
188
+ self.action.append(swml_action)
189
+ return self
190
+
191
+ def swml_transfer(self, dest: str, ai_response: str, final: bool = True) -> 'SwaigFunctionResult':
192
+ """
193
+ Add a SWML transfer action with AI response setup for when transfer completes.
194
+
195
+ This is a virtual helper that generates SWML to transfer the call to another
196
+ destination and sets up an AI response for when the transfer completes and
197
+ control returns to the agent.
198
+
199
+ For transfers, you typically want to enable post-processing so the AI speaks
200
+ the response first before executing the transfer.
201
+
202
+ Args:
203
+ dest: Destination URL for the transfer (SWML endpoint, SIP address, etc.)
204
+ ai_response: Message the AI should say when transfer completes and control returns
205
+ final: Whether this is a permanent transfer (True) or temporary (False).
206
+ Defaults to True for permanent transfers (same as connect method).
207
+
208
+ Returns:
209
+ Self for method chaining
210
+
211
+ Example:
212
+ # Permanent transfer (default)
213
+ result = (
214
+ SwaigFunctionResult("I'm transferring you to support", post_process=True)
215
+ .swml_transfer(
216
+ "https://support.example.com/swml",
217
+ "Goodbye!" # Won't be used since final=True by default
218
+ )
219
+ )
220
+
221
+ # Temporary transfer with return
222
+ result.swml_transfer(
223
+ dest,
224
+ "The support call is complete. How else can I help?",
225
+ final=False
226
+ )
227
+ """
228
+ # Create the SWML action structure directly
229
+ swml_action = {
230
+ "SWML": {
231
+ "version": "1.0.0",
232
+ "sections": {
233
+ "main": [
234
+ {"set": {"ai_response": ai_response}},
235
+ {"transfer": {"dest": dest}}
236
+ ]
237
+ }
238
+ },
239
+ "transfer": str(final).lower() # Convert boolean to "true"/"false" string
240
+ }
241
+
242
+ # Add to actions list directly
243
+ self.action.append(swml_action)
244
+
245
+ return self
246
+
247
+ def update_global_data(self, data: Dict[str, Any]) -> 'SwaigFunctionResult':
248
+ """
249
+ Update global agent data variables.
250
+
251
+ This is a convenience method that abstracts the set_global_data action.
252
+ Global data persists across the entire agent session and is available
253
+ in prompt variables and can be accessed by all functions.
254
+
255
+ Args:
256
+ data: Dictionary of key-value pairs to set/update in global data
257
+
258
+ Returns:
259
+ self for method chaining
260
+ """
261
+ return self.add_action("set_global_data", data)
262
+
263
+ def swml_user_event(self, event_data: Dict[str, Any]) -> 'SwaigFunctionResult':
264
+ """
265
+ Send a user event through SWML to update the client UI.
266
+
267
+ This is a convenience method for sending user events to connected clients,
268
+ commonly used for real-time UI updates in interactive applications.
269
+
270
+ Args:
271
+ event_data: Dictionary containing the event type and any associated data
272
+ Example: {"type": "cards_dealt", "player_hand": [...], "score": 21}
273
+
274
+ Returns:
275
+ Self for method chaining
276
+
277
+ Example:
278
+ result = (
279
+ SwaigFunctionResult("You have blackjack!")
280
+ .swml_user_event({
281
+ "type": "cards_dealt",
282
+ "player_hand": player_cards,
283
+ "dealer_hand": dealer_cards,
284
+ "player_score": 21
285
+ })
286
+ )
287
+ """
288
+ swml_action = {
289
+ "sections": {
290
+ "main": [{
291
+ "user_event": {
292
+ "event": event_data
293
+ }
294
+ }]
295
+ },
296
+ "version": "1.0.0"
297
+ }
298
+
299
+ return self.add_action("SWML", swml_action)
300
+
301
+ def swml_change_step(self, step_name: str) -> 'SwaigFunctionResult':
302
+ """
303
+ Change the conversation step in the AI agent's workflow.
304
+
305
+ This is a convenience method for transitioning between conversation steps,
306
+ allowing dynamic workflow control based on user interactions or game state.
307
+
308
+ Args:
309
+ step_name: Name of the step to transition to (e.g., "betting", "playing", "hand_complete")
310
+
311
+ Returns:
312
+ Self for method chaining
313
+
314
+ Example:
315
+ result = (
316
+ SwaigFunctionResult("Starting a new hand")
317
+ .swml_change_step("betting")
318
+ .swml_user_event({"type": "game_reset", "chips": 1000})
319
+ )
320
+ """
321
+ return self.add_action("change_step", step_name)
322
+
323
+ def swml_change_context(self, context_name: str) -> 'SwaigFunctionResult':
324
+ """
325
+ Change the conversation context in the AI agent's workflow.
326
+
327
+ This is a convenience method for switching between different conversation contexts,
328
+ useful for agents that handle multiple distinct workflows or service modes.
329
+
330
+ Args:
331
+ context_name: Name of the context to transition to (e.g., "support", "sales", "technical")
332
+
333
+ Returns:
334
+ Self for method chaining
335
+
336
+ Example:
337
+ result = (
338
+ SwaigFunctionResult("Transferring you to technical support")
339
+ .swml_change_context("technical_support")
340
+ )
341
+ """
342
+ return self.add_action("change_context", context_name)
343
+
344
+ def execute_swml(self, swml_content, transfer: bool = False) -> 'SwaigFunctionResult':
345
+ """
346
+ Execute SWML content with optional transfer behavior.
347
+
348
+ Args:
349
+ swml_content: Can be:
350
+ - String: Raw SWML JSON text
351
+ - Dict: SWML data structure
352
+ - SWML object: SignalWire SWML SDK object with .to_dict() method
353
+ transfer: Boolean - whether call should exit agent after execution
354
+
355
+ Returns:
356
+ self for method chaining
357
+ """
358
+ # Detect input type and normalize to appropriate format
359
+ if isinstance(swml_content, str):
360
+ # Raw SWML string - use as-is
361
+ swml_data = swml_content
362
+ elif hasattr(swml_content, 'to_dict'):
363
+ # SWML SDK object - convert to dict
364
+ swml_data = swml_content.to_dict()
365
+ elif isinstance(swml_content, dict):
366
+ # Dict - use directly
367
+ swml_data = swml_content
368
+ else:
369
+ raise TypeError("swml_content must be string, dict, or SWML object")
370
+
371
+ action = swml_data
372
+ if transfer:
373
+ action["transfer"] = "true"
374
+
375
+ return self.add_action("SWML", action)
376
+
377
+ def hangup(self) -> 'SwaigFunctionResult':
378
+ """
379
+ Terminate the call.
380
+
381
+ Returns:
382
+ self for method chaining
383
+ """
384
+ return self.add_action("hangup", True)
385
+
386
+ def hold(self, timeout: int = 300) -> 'SwaigFunctionResult':
387
+ """
388
+ Put the call on hold with optional timeout.
389
+
390
+ Args:
391
+ timeout: Timeout in seconds (max 900, default 300)
392
+
393
+ Returns:
394
+ self for method chaining
395
+ """
396
+ # Clamp timeout to valid range
397
+ timeout = max(0, min(timeout, 900))
398
+ return self.add_action("hold", timeout)
399
+
400
+ def wait_for_user(self, enabled: Optional[bool] = None, timeout: Optional[int] = None, answer_first: bool = False) -> 'SwaigFunctionResult':
401
+ """
402
+ Control how agent waits for user input.
403
+
404
+ Args:
405
+ enabled: Boolean to enable/disable waiting
406
+ timeout: Number of seconds to wait
407
+ answer_first: Special "answer_first" mode
408
+
409
+ Returns:
410
+ self for method chaining
411
+ """
412
+ if answer_first:
413
+ wait_value = "answer_first"
414
+ elif timeout is not None:
415
+ wait_value = timeout
416
+ elif enabled is not None:
417
+ wait_value = enabled
418
+ else:
419
+ wait_value = True
420
+
421
+ return self.add_action("wait_for_user", wait_value)
422
+
423
+ def stop(self) -> 'SwaigFunctionResult':
424
+ """
425
+ Stop the agent execution.
426
+
427
+ Returns:
428
+ self for method chaining
429
+ """
430
+ return self.add_action("stop", True)
431
+
432
+ def say(self, text: str) -> 'SwaigFunctionResult':
433
+ """
434
+ Make the agent speak specific text.
435
+
436
+ Args:
437
+ text: Text for agent to speak
438
+
439
+ Returns:
440
+ self for method chaining
441
+ """
442
+ return self.add_action("say", text)
443
+
444
+ def play_background_file(self, filename: str, wait: bool = False) -> 'SwaigFunctionResult':
445
+ """
446
+ Play audio or video file in background.
447
+
448
+ Args:
449
+ filename: Audio/video filename/path
450
+ wait: Whether to suppress attention-getting behavior during playback
451
+
452
+ Returns:
453
+ self for method chaining
454
+ """
455
+ if wait:
456
+ return self.add_action("playback_bg", {"file": filename, "wait": True})
457
+ else:
458
+ return self.add_action("playback_bg", filename)
459
+
460
+ def stop_background_file(self) -> 'SwaigFunctionResult':
461
+ """
462
+ Stop currently playing background file.
463
+
464
+ Returns:
465
+ self for method chaining
466
+ """
467
+ return self.add_action("stop_playback_bg", True)
468
+
469
+ def set_end_of_speech_timeout(self, milliseconds: int) -> 'SwaigFunctionResult':
470
+ """
471
+ Adjust end of speech timeout - milliseconds of silence after speaking
472
+ has been detected to finalize speech recognition.
473
+
474
+ Args:
475
+ milliseconds: Timeout in milliseconds
476
+
477
+ Returns:
478
+ self for method chaining
479
+ """
480
+ return self.add_action("end_of_speech_timeout", milliseconds)
481
+
482
+ def set_speech_event_timeout(self, milliseconds: int) -> 'SwaigFunctionResult':
483
+ """
484
+ Adjust speech event timeout - milliseconds since last speech detection
485
+ event to finalize recognition. Works better in noisy environments.
486
+
487
+ Args:
488
+ milliseconds: Timeout in milliseconds
489
+
490
+ Returns:
491
+ self for method chaining
492
+ """
493
+ return self.add_action("speech_event_timeout", milliseconds)
494
+
495
+ def remove_global_data(self, keys: Union[str, List[str]]) -> 'SwaigFunctionResult':
496
+ """
497
+ Remove global agent data variables.
498
+
499
+ Args:
500
+ keys: Single key string or list of keys to remove
501
+
502
+ Returns:
503
+ self for method chaining
504
+ """
505
+ return self.add_action("unset_global_data", keys)
506
+
507
+ def set_metadata(self, data: Dict[str, Any]) -> 'SwaigFunctionResult':
508
+ """
509
+ Set metadata scoped to current function's meta_data_token.
510
+
511
+ Args:
512
+ data: Dictionary of key-value pairs for metadata
513
+
514
+ Returns:
515
+ self for method chaining
516
+ """
517
+ return self.add_action("set_meta_data", data)
518
+
519
+ def remove_metadata(self, keys: Union[str, List[str]]) -> 'SwaigFunctionResult':
520
+ """
521
+ Remove metadata from current function's meta_data_token scope.
522
+
523
+ Args:
524
+ keys: Single key string or list of keys to remove
525
+
526
+ Returns:
527
+ self for method chaining
528
+ """
529
+ return self.add_action("unset_meta_data", keys)
530
+
531
+ def toggle_functions(self, function_toggles: List[Dict[str, Any]]) -> 'SwaigFunctionResult':
532
+ """
533
+ Enable/disable specific SWAIG functions.
534
+
535
+ Args:
536
+ function_toggles: List of dicts with 'function' and 'active' keys
537
+
538
+ Returns:
539
+ self for method chaining
540
+ """
541
+ return self.add_action("toggle_functions", function_toggles)
542
+
543
+ def enable_functions_on_timeout(self, enabled: bool = True) -> 'SwaigFunctionResult':
544
+ """
545
+ Enable function calls on speaker timeout.
546
+
547
+ Args:
548
+ enabled: Whether to enable functions on timeout
549
+
550
+ Returns:
551
+ self for method chaining
552
+ """
553
+ return self.add_action("functions_on_speaker_timeout", enabled)
554
+
555
+ def enable_extensive_data(self, enabled: bool = True) -> 'SwaigFunctionResult':
556
+ """
557
+ Send full data to LLM for this turn only, then use smaller replacement
558
+ in subsequent turns.
559
+
560
+ Args:
561
+ enabled: Whether to send extensive data this turn only
562
+
563
+ Returns:
564
+ self for method chaining
565
+ """
566
+ return self.add_action("extensive_data", enabled)
567
+
568
+ def update_settings(self, settings: Dict[str, Any]) -> 'SwaigFunctionResult':
569
+ """
570
+ Update agent runtime settings.
571
+
572
+ Supported settings:
573
+ - frequency-penalty: Float (-2.0 to 2.0)
574
+ - presence-penalty: Float (-2.0 to 2.0)
575
+ - max-tokens: Integer (0 to 4096)
576
+ - top-p: Float (0.0 to 1.0)
577
+ - confidence: Float (0.0 to 1.0)
578
+ - barge-confidence: Float (0.0 to 1.0)
579
+ - temperature: Float (0.0 to 2.0, clamped to 1.5)
580
+
581
+ Args:
582
+ settings: Dictionary of settings to update
583
+
584
+ Returns:
585
+ self for method chaining
586
+ """
587
+ return self.add_action("settings", settings)
588
+
589
+ def switch_context(self, system_prompt: Optional[str] = None, user_prompt: Optional[str] = None,
590
+ consolidate: bool = False, full_reset: bool = False) -> 'SwaigFunctionResult':
591
+ """
592
+ Change agent context/prompt during conversation.
593
+
594
+ Args:
595
+ system_prompt: New system prompt
596
+ user_prompt: User message to add
597
+ consolidate: Whether to summarize existing conversation
598
+ full_reset: Whether to do complete context reset
599
+
600
+ Returns:
601
+ self for method chaining
602
+ """
603
+ if system_prompt and not user_prompt and not consolidate and not full_reset:
604
+ # Simple string context switch
605
+ return self.add_action("context_switch", system_prompt)
606
+ else:
607
+ # Advanced object context switch
608
+ context_data = {}
609
+ if system_prompt:
610
+ context_data["system_prompt"] = system_prompt
611
+ if user_prompt:
612
+ context_data["user_prompt"] = user_prompt
613
+ if consolidate:
614
+ context_data["consolidate"] = True
615
+ if full_reset:
616
+ context_data["full_reset"] = True
617
+ return self.add_action("context_switch", context_data)
618
+
619
+ def simulate_user_input(self, text: str) -> 'SwaigFunctionResult':
620
+ """
621
+ Queue simulated user input.
622
+
623
+ Args:
624
+ text: Text to simulate as user input
625
+
626
+ Returns:
627
+ self for method chaining
628
+ """
629
+ return self.add_action("user_input", text)
630
+
631
+ def send_sms(self, to_number: str, from_number: str, body: Optional[str] = None,
632
+ media: Optional[List[str]] = None, tags: Optional[List[str]] = None,
633
+ region: Optional[str] = None) -> 'SwaigFunctionResult':
634
+ """
635
+ Send a text message to a PSTN phone number using SWML.
636
+
637
+ This is a virtual helper that generates SWML to send SMS messages.
638
+ Either body or media (or both) must be provided.
639
+
640
+ Args:
641
+ to_number: Phone number in E.164 format to send to
642
+ from_number: Phone number in E.164 format to send from
643
+ body: Body text of the message (optional if media provided)
644
+ media: Array of URLs to send in the message (optional if body provided)
645
+ tags: Array of tags to associate with the message for UI searching
646
+ region: Region to originate the message from
647
+
648
+ Returns:
649
+ self for method chaining
650
+
651
+ Raises:
652
+ ValueError: If neither body nor media is provided
653
+ """
654
+ # Validate that at least body or media is provided
655
+ if not body and not media:
656
+ raise ValueError("Either body or media must be provided")
657
+
658
+ # Build the send_sms parameters
659
+ sms_params = {
660
+ "to_number": to_number,
661
+ "from_number": from_number
662
+ }
663
+
664
+ # Add optional parameters
665
+ if body:
666
+ sms_params["body"] = body
667
+ if media:
668
+ sms_params["media"] = media
669
+ if tags:
670
+ sms_params["tags"] = tags
671
+ if region:
672
+ sms_params["region"] = region
673
+
674
+ # Generate SWML document
675
+ swml_doc = {
676
+ "version": "1.0.0",
677
+ "sections": {
678
+ "main": [
679
+ {"send_sms": sms_params}
680
+ ]
681
+ }
682
+ }
683
+
684
+ # Use execute_swml to add the action
685
+ return self.execute_swml(swml_doc)
686
+
687
+ def pay(self, payment_connector_url: str, input_method: str = "dtmf",
688
+ status_url: Optional[str] = None, payment_method: str = "credit-card",
689
+ timeout: int = 5, max_attempts: int = 1, security_code: bool = True,
690
+ postal_code: Union[bool, str] = True, min_postal_code_length: int = 0,
691
+ token_type: str = "reusable", charge_amount: Optional[str] = None,
692
+ currency: str = "usd", language: str = "en-US", voice: str = "woman",
693
+ description: Optional[str] = None, valid_card_types: str = "visa mastercard amex",
694
+ parameters: Optional[List[Dict[str, str]]] = None,
695
+ prompts: Optional[List[Dict[str, Any]]] = None,
696
+ ai_response: Optional[str] = "The payment status is ${pay_result}, do not mention anything else about collecting payment if successful.") -> 'SwaigFunctionResult':
697
+ """
698
+ Process payment using SWML pay action.
699
+
700
+ This is a virtual helper that generates SWML for payment processing.
701
+
702
+ Args:
703
+ payment_connector_url: URL to make payment requests to (required)
704
+ input_method: Method to collect payment details ("dtmf" or "voice")
705
+ status_url: URL for status change notifications
706
+ payment_method: Payment method ("credit-card" currently supported)
707
+ timeout: Seconds to wait for next digit (default: 5)
708
+ max_attempts: Number of retry attempts (default: 1)
709
+ security_code: Whether to prompt for security code (default: True)
710
+ postal_code: Whether to prompt for postal code, or actual postcode
711
+ min_postal_code_length: Minimum postal code digits (default: 0)
712
+ token_type: Payment type ("one-time" or "reusable", default: "reusable")
713
+ charge_amount: Amount to charge as decimal string
714
+ currency: Currency code (default: "usd")
715
+ language: Language for prompts (default: "en-US")
716
+ voice: TTS voice to use (default: "woman")
717
+ description: Custom payment description
718
+ valid_card_types: Space-separated card types (default: "visa mastercard amex")
719
+ parameters: Array of name/value pairs for payment connector
720
+ prompts: Array of custom prompt configurations
721
+
722
+ Returns:
723
+ self for method chaining
724
+ """
725
+ # Build the pay parameters
726
+ pay_params = {
727
+ "payment_connector_url": payment_connector_url,
728
+ "input": input_method,
729
+ "payment_method": payment_method,
730
+ "timeout": str(timeout),
731
+ "max_attempts": str(max_attempts),
732
+ "security_code": str(security_code).lower(),
733
+ "min_postal_code_length": str(min_postal_code_length),
734
+ "token_type": token_type,
735
+ "currency": currency,
736
+ "language": language,
737
+ "voice": voice,
738
+ "valid_card_types": valid_card_types
739
+ }
740
+
741
+ # Handle postal_code (can be boolean or string)
742
+ if isinstance(postal_code, bool):
743
+ pay_params["postal_code"] = str(postal_code).lower()
744
+ else:
745
+ pay_params["postal_code"] = postal_code
746
+
747
+ # Add optional parameters
748
+ if status_url:
749
+ pay_params["status_url"] = status_url
750
+ if charge_amount:
751
+ pay_params["charge_amount"] = charge_amount
752
+ if description:
753
+ pay_params["description"] = description
754
+ if parameters:
755
+ pay_params["parameters"] = parameters
756
+ if prompts:
757
+ pay_params["prompts"] = prompts
758
+
759
+ # Generate SWML document
760
+ swml_doc = {
761
+ "version": "1.0.0",
762
+ "sections": {
763
+ "main": [
764
+ {"set": {"ai_response": ai_response}},
765
+ {"pay": pay_params}
766
+ ]
767
+ }
768
+ }
769
+
770
+ # Use execute_swml to add the action
771
+ return self.execute_swml(swml_doc)
772
+
773
+ def record_call(self, control_id: Optional[str] = None, stereo: bool = False,
774
+ format: str = "wav", direction: str = "both",
775
+ terminators: Optional[str] = None, beep: bool = False,
776
+ input_sensitivity: float = 44.0, initial_timeout: float = 0.0,
777
+ end_silence_timeout: float = 0.0, max_length: Optional[float] = None,
778
+ status_url: Optional[str] = None) -> 'SwaigFunctionResult':
779
+ """
780
+ Start background call recording using SWML.
781
+
782
+ This is a virtual helper that generates SWML to start recording the call
783
+ in the background. Unlike foreground recording, the script continues
784
+ executing while recording happens in the background.
785
+
786
+ Args:
787
+ control_id: Identifier for this recording (for use with stop_record_call)
788
+ stereo: Record in stereo (default: False)
789
+ format: Recording format - "wav" or "mp3" (default: "wav")
790
+ direction: Audio direction - "speak", "listen", or "both" (default: "both")
791
+ terminators: Digits that stop recording when pressed
792
+ beep: Play beep before recording (default: False)
793
+ input_sensitivity: Input sensitivity for recording (default: 44.0)
794
+ initial_timeout: Time in seconds to wait for speech start (default: 0.0)
795
+ end_silence_timeout: Time in seconds to wait in silence before ending (default: 0.0)
796
+ max_length: Maximum recording length in seconds
797
+ status_url: URL to send recording status events to
798
+
799
+ Returns:
800
+ self for method chaining
801
+ """
802
+ # Validate format parameter
803
+ if format not in ["wav", "mp3"]:
804
+ raise ValueError("format must be 'wav' or 'mp3'")
805
+
806
+ # Validate direction parameter
807
+ if direction not in ["speak", "listen", "both"]:
808
+ raise ValueError("direction must be 'speak', 'listen', or 'both'")
809
+
810
+ # Build the record_call parameters
811
+ record_params = {
812
+ "stereo": stereo,
813
+ "format": format,
814
+ "direction": direction,
815
+ "beep": beep,
816
+ "input_sensitivity": input_sensitivity,
817
+ "initial_timeout": initial_timeout,
818
+ "end_silence_timeout": end_silence_timeout
819
+ }
820
+
821
+ # Add optional parameters
822
+ if control_id:
823
+ record_params["control_id"] = control_id
824
+ if terminators:
825
+ record_params["terminators"] = terminators
826
+ if max_length:
827
+ record_params["max_length"] = max_length
828
+ if status_url:
829
+ record_params["status_url"] = status_url
830
+
831
+ # Generate SWML document
832
+ swml_doc = {
833
+ "version": "1.0.0",
834
+ "sections": {
835
+ "main": [
836
+ {"record_call": record_params}
837
+ ]
838
+ }
839
+ }
840
+
841
+ # Use execute_swml to add the action
842
+ return self.execute_swml(swml_doc)
843
+
844
+ def stop_record_call(self, control_id: Optional[str] = None) -> 'SwaigFunctionResult':
845
+ """
846
+ Stop an active background call recording using SWML.
847
+
848
+ This is a virtual helper that generates SWML to stop a recording that
849
+ was started with record_call().
850
+
851
+ Args:
852
+ control_id: Identifier for the recording to stop. If not provided,
853
+ the most recent recording will be stopped.
854
+
855
+ Returns:
856
+ self for method chaining
857
+ """
858
+ # Build the stop_record_call parameters
859
+ stop_params = {}
860
+ if control_id:
861
+ stop_params["control_id"] = control_id
862
+
863
+ # Generate SWML document
864
+ swml_doc = {
865
+ "version": "1.0.0",
866
+ "sections": {
867
+ "main": [
868
+ {"stop_record_call": stop_params}
869
+ ]
870
+ }
871
+ }
872
+
873
+ # Use execute_swml to add the action
874
+ return self.execute_swml(swml_doc)
875
+
876
+ def join_room(self, name: str) -> 'SwaigFunctionResult':
877
+ """
878
+ Join a RELAY room using SWML.
879
+
880
+ This is a virtual helper that generates SWML to join a RELAY room,
881
+ which enables multi-party communication and collaboration.
882
+
883
+ Args:
884
+ name: The name of the room to join (required)
885
+
886
+ Returns:
887
+ self for method chaining
888
+ """
889
+ # Build the join_room parameters
890
+ join_params = {"name": name}
891
+
892
+ # Generate SWML document
893
+ swml_doc = {
894
+ "version": "1.0.0",
895
+ "sections": {
896
+ "main": [
897
+ {"join_room": join_params}
898
+ ]
899
+ }
900
+ }
901
+
902
+ # Use execute_swml to add the action
903
+ return self.execute_swml(swml_doc)
904
+
905
+ def sip_refer(self, to_uri: str) -> 'SwaigFunctionResult':
906
+ """
907
+ Send SIP REFER to a SIP call using SWML.
908
+
909
+ This is a virtual helper that generates SWML to send a SIP REFER
910
+ message, which is used for call transfer in SIP environments.
911
+
912
+ Args:
913
+ to_uri: The SIP URI to send the REFER to (required)
914
+
915
+ Returns:
916
+ self for method chaining
917
+ """
918
+ # Build the sip_refer parameters
919
+ refer_params = {"to_uri": to_uri}
920
+
921
+ # Generate SWML document
922
+ swml_doc = {
923
+ "version": "1.0.0",
924
+ "sections": {
925
+ "main": [
926
+ {"sip_refer": refer_params}
927
+ ]
928
+ }
929
+ }
930
+
931
+ # Use execute_swml to add the action
932
+ return self.execute_swml(swml_doc)
933
+
934
+ def join_conference(self, name: str, muted: bool = False, beep: str = "true",
935
+ start_on_enter: bool = True, end_on_exit: bool = False,
936
+ wait_url: Optional[str] = None, max_participants: int = 250,
937
+ record: str = "do-not-record", region: Optional[str] = None,
938
+ trim: str = "trim-silence", coach: Optional[str] = None,
939
+ status_callback_event: Optional[str] = None,
940
+ status_callback: Optional[str] = None,
941
+ status_callback_method: str = "POST",
942
+ recording_status_callback: Optional[str] = None,
943
+ recording_status_callback_method: str = "POST",
944
+ recording_status_callback_event: str = "completed",
945
+ result: Optional[Any] = None) -> 'SwaigFunctionResult':
946
+ """
947
+ Join an ad-hoc audio conference with RELAY and CXML calls using SWML.
948
+
949
+ This is a virtual helper that generates SWML to join audio conferences
950
+ with extensive configuration options for call management and recording.
951
+
952
+ Args:
953
+ name: Name of conference (required)
954
+ muted: Whether to join muted (default: False)
955
+ beep: Beep configuration - "true", "false", "onEnter", "onExit" (default: "true")
956
+ start_on_enter: Whether conference starts when this participant enters (default: True)
957
+ end_on_exit: Whether conference ends when this participant exits (default: False)
958
+ wait_url: SWML URL for hold music (default: None for default hold music)
959
+ max_participants: Maximum participants <= 250 (default: 250)
960
+ record: Recording mode - "do-not-record", "record-from-start" (default: "do-not-record")
961
+ region: Conference region (default: None)
962
+ trim: Trim silence - "trim-silence", "do-not-trim" (default: "trim-silence")
963
+ coach: SWML Call ID or CXML CallSid for coaching (default: None)
964
+ status_callback_event: Events to report - "start end join leave mute hold modify speaker announcement" (default: None)
965
+ status_callback: URL for status callbacks (default: None)
966
+ status_callback_method: HTTP method - "GET", "POST" (default: "POST")
967
+ recording_status_callback: URL for recording status callbacks (default: None)
968
+ recording_status_callback_method: HTTP method - "GET", "POST" (default: "POST")
969
+ recording_status_callback_event: Recording events - "in-progress completed absent" (default: "completed")
970
+ result: Switch on return_value when object {} or cond when array [] (default: None)
971
+
972
+ Returns:
973
+ self for method chaining
974
+
975
+ Raises:
976
+ ValueError: If beep value is invalid or max_participants exceeds 250
977
+ """
978
+ # Validate beep parameter
979
+ valid_beep_values = ["true", "false", "onEnter", "onExit"]
980
+ if beep not in valid_beep_values:
981
+ raise ValueError(f"beep must be one of {valid_beep_values}")
982
+
983
+ # Validate max_participants
984
+ if max_participants <= 0 or max_participants > 250:
985
+ raise ValueError("max_participants must be a positive integer <= 250")
986
+
987
+ # Validate record parameter
988
+ valid_record_values = ["do-not-record", "record-from-start"]
989
+ if record not in valid_record_values:
990
+ raise ValueError(f"record must be one of {valid_record_values}")
991
+
992
+ # Validate trim parameter
993
+ valid_trim_values = ["trim-silence", "do-not-trim"]
994
+ if trim not in valid_trim_values:
995
+ raise ValueError(f"trim must be one of {valid_trim_values}")
996
+
997
+ # Validate status_callback_method
998
+ valid_methods = ["GET", "POST"]
999
+ if status_callback_method not in valid_methods:
1000
+ raise ValueError(f"status_callback_method must be one of {valid_methods}")
1001
+ if recording_status_callback_method not in valid_methods:
1002
+ raise ValueError(f"recording_status_callback_method must be one of {valid_methods}")
1003
+
1004
+ # Build the join_conference parameters - start with required parameter
1005
+ if isinstance(name, str) and not name.strip():
1006
+ raise ValueError("name cannot be empty")
1007
+
1008
+ # For simple case, can just be the conference name
1009
+ if (not muted and beep == "true" and start_on_enter and not end_on_exit and
1010
+ wait_url is None and max_participants == 250 and record == "do-not-record" and
1011
+ region is None and trim == "trim-silence" and coach is None and
1012
+ status_callback_event is None and status_callback is None and
1013
+ status_callback_method == "POST" and recording_status_callback is None and
1014
+ recording_status_callback_method == "POST" and recording_status_callback_event == "completed" and
1015
+ result is None):
1016
+ # Simple form - just the conference name
1017
+ join_params = name
1018
+ else:
1019
+ # Full object form with parameters
1020
+ join_params = {"name": name}
1021
+
1022
+ # Add non-default parameters
1023
+ if muted:
1024
+ join_params["muted"] = muted
1025
+ if beep != "true":
1026
+ join_params["beep"] = beep
1027
+ if not start_on_enter:
1028
+ join_params["start_on_enter"] = start_on_enter
1029
+ if end_on_exit:
1030
+ join_params["end_on_exit"] = end_on_exit
1031
+ if wait_url:
1032
+ join_params["wait_url"] = wait_url
1033
+ if max_participants != 250:
1034
+ join_params["max_participants"] = max_participants
1035
+ if record != "do-not-record":
1036
+ join_params["record"] = record
1037
+ if region:
1038
+ join_params["region"] = region
1039
+ if trim != "trim-silence":
1040
+ join_params["trim"] = trim
1041
+ if coach:
1042
+ join_params["coach"] = coach
1043
+ if status_callback_event:
1044
+ join_params["status_callback_event"] = status_callback_event
1045
+ if status_callback:
1046
+ join_params["status_callback"] = status_callback
1047
+ if status_callback_method != "POST":
1048
+ join_params["status_callback_method"] = status_callback_method
1049
+ if recording_status_callback:
1050
+ join_params["recording_status_callback"] = recording_status_callback
1051
+ if recording_status_callback_method != "POST":
1052
+ join_params["recording_status_callback_method"] = recording_status_callback_method
1053
+ if recording_status_callback_event != "completed":
1054
+ join_params["recording_status_callback_event"] = recording_status_callback_event
1055
+ if result is not None:
1056
+ join_params["result"] = result
1057
+
1058
+ # Generate SWML document
1059
+ swml_doc = {
1060
+ "version": "1.0.0",
1061
+ "sections": {
1062
+ "main": [
1063
+ {"join_conference": join_params}
1064
+ ]
1065
+ }
1066
+ }
1067
+
1068
+ # Use execute_swml to add the action
1069
+ return self.execute_swml(swml_doc)
1070
+
1071
+ def tap(self, uri: str, control_id: Optional[str] = None, direction: str = "both",
1072
+ codec: str = "PCMU", rtp_ptime: int = 20,
1073
+ status_url: Optional[str] = None) -> 'SwaigFunctionResult':
1074
+ """
1075
+ Start background call tap using SWML.
1076
+
1077
+ This is a virtual helper that generates SWML to start background call tapping.
1078
+ Media is streamed over Websocket or RTP to customer controlled URI.
1079
+
1080
+ Args:
1081
+ uri: Destination of tap media stream (required)
1082
+ Formats: rtp://IP:port, ws://example.com, or wss://example.com
1083
+ control_id: Identifier for this tap to use with stop_tap (optional)
1084
+ Default is generated and stored in tap_control_id variable
1085
+ direction: Direction of audio to tap (default: "both")
1086
+ "speak" = what party says
1087
+ "hear" = what party hears
1088
+ "both" = what party hears and says
1089
+ codec: Codec for tap media stream - "PCMU" or "PCMA" (default: "PCMU")
1090
+ rtp_ptime: Packetization time in milliseconds for RTP (default: 20)
1091
+ status_url: URL for status change requests (optional)
1092
+
1093
+ Returns:
1094
+ self for method chaining
1095
+
1096
+ Raises:
1097
+ ValueError: If direction or codec values are invalid
1098
+ """
1099
+ # Validate direction parameter
1100
+ valid_directions = ["speak", "hear", "both"]
1101
+ if direction not in valid_directions:
1102
+ raise ValueError(f"direction must be one of {valid_directions}")
1103
+
1104
+ # Validate codec parameter
1105
+ valid_codecs = ["PCMU", "PCMA"]
1106
+ if codec not in valid_codecs:
1107
+ raise ValueError(f"codec must be one of {valid_codecs}")
1108
+
1109
+ # Validate rtp_ptime
1110
+ if rtp_ptime <= 0:
1111
+ raise ValueError("rtp_ptime must be a positive integer")
1112
+
1113
+ # Build the tap parameters
1114
+ tap_params = {"uri": uri}
1115
+
1116
+ # Add optional parameters if they differ from defaults
1117
+ if control_id:
1118
+ tap_params["control_id"] = control_id
1119
+ if direction != "both":
1120
+ tap_params["direction"] = direction
1121
+ if codec != "PCMU":
1122
+ tap_params["codec"] = codec
1123
+ if rtp_ptime != 20:
1124
+ tap_params["rtp_ptime"] = rtp_ptime
1125
+ if status_url:
1126
+ tap_params["status_url"] = status_url
1127
+
1128
+ # Generate SWML document
1129
+ swml_doc = {
1130
+ "version": "1.0.0",
1131
+ "sections": {
1132
+ "main": [
1133
+ {"tap": tap_params}
1134
+ ]
1135
+ }
1136
+ }
1137
+
1138
+ # Use execute_swml to add the action
1139
+ return self.execute_swml(swml_doc)
1140
+
1141
+ def stop_tap(self, control_id: Optional[str] = None) -> 'SwaigFunctionResult':
1142
+ """
1143
+ Stop an active tap stream using SWML.
1144
+
1145
+ This is a virtual helper that generates SWML to stop a tap stream
1146
+ that was started with tap().
1147
+
1148
+ Args:
1149
+ control_id: ID of the tap to stop (optional)
1150
+ If not set, the last tap started will be stopped
1151
+
1152
+ Returns:
1153
+ self for method chaining
1154
+ """
1155
+ # Build the stop_tap parameters
1156
+ if control_id:
1157
+ stop_params = {"control_id": control_id}
1158
+ else:
1159
+ # For simple case with no control_id, use empty object
1160
+ stop_params = {}
1161
+
1162
+ # Generate SWML document
1163
+ swml_doc = {
1164
+ "version": "1.0.0",
1165
+ "sections": {
1166
+ "main": [
1167
+ {"stop_tap": stop_params}
1168
+ ]
1169
+ }
1170
+ }
1171
+
1172
+ # Use execute_swml to add the action
1173
+ return self.execute_swml(swml_doc)
1174
+
1175
+ @staticmethod
1176
+ def create_payment_prompt(for_situation: str, actions: List[Dict[str, str]],
1177
+ card_type: Optional[str] = None,
1178
+ error_type: Optional[str] = None) -> Dict[str, Any]:
1179
+ """
1180
+ Create a payment prompt structure for use with pay() method.
1181
+
1182
+ Args:
1183
+ for_situation: Situation to use prompt for (e.g., "payment-card-number")
1184
+ actions: List of actions with 'type' and 'phrase' keys
1185
+ card_type: Space-separated card types for this prompt
1186
+ error_type: Space-separated error types for this prompt
1187
+
1188
+ Returns:
1189
+ Dictionary representing the prompt structure
1190
+ """
1191
+ prompt = {
1192
+ "for": for_situation,
1193
+ "actions": actions
1194
+ }
1195
+
1196
+ if card_type:
1197
+ prompt["card_type"] = card_type
1198
+ if error_type:
1199
+ prompt["error_type"] = error_type
1200
+
1201
+ return prompt
1202
+
1203
+ @staticmethod
1204
+ def create_payment_action(action_type: str, phrase: str) -> Dict[str, str]:
1205
+ """
1206
+ Create a payment action for use in payment prompts.
1207
+
1208
+ Args:
1209
+ action_type: "Say" for text-to-speech or "Play" for audio file
1210
+ phrase: Sentence to say or URL to play
1211
+
1212
+ Returns:
1213
+ Dictionary representing the action
1214
+ """
1215
+ return {
1216
+ "type": action_type,
1217
+ "phrase": phrase
1218
+ }
1219
+
1220
+ @staticmethod
1221
+ def create_payment_parameter(name: str, value: str) -> Dict[str, str]:
1222
+ """
1223
+ Create a payment parameter for use with pay() method.
1224
+
1225
+ Args:
1226
+ name: Parameter name
1227
+ value: Parameter value
1228
+
1229
+ Returns:
1230
+ Dictionary representing the parameter
1231
+ """
1232
+ return {
1233
+ "name": name,
1234
+ "value": value
1235
+ }
1236
+
96
1237
  def to_dict(self) -> Dict[str, Any]:
97
1238
  """
98
1239
  Convert to the JSON structure expected by SWAIG
@@ -101,6 +1242,9 @@ class SwaigFunctionResult:
101
1242
  - 'response': Text to be spoken by the AI
102
1243
  - 'action': Array of action objects
103
1244
 
1245
+ Optional:
1246
+ - 'post_process': Boolean controlling when actions execute
1247
+
104
1248
  Returns:
105
1249
  Dictionary in SWAIG function response format
106
1250
  """
@@ -115,6 +1259,11 @@ class SwaigFunctionResult:
115
1259
  if self.action:
116
1260
  result["action"] = self.action
117
1261
 
1262
+ # Add post_process if enabled and we have actions
1263
+ # (post_process only matters when there are actions to execute)
1264
+ if self.post_process and self.action:
1265
+ result["post_process"] = True
1266
+
118
1267
  # Ensure we have at least one of response or action
119
1268
  if not result:
120
1269
  # Default response if neither is present