signalwire-agents 0.1.20__py3-none-any.whl → 0.1.23__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 (37) hide show
  1. signalwire_agents/__init__.py +1 -1
  2. signalwire_agents/agent_server.py +50 -11
  3. signalwire_agents/core/__init__.py +2 -2
  4. signalwire_agents/core/agent/__init__.py +14 -0
  5. signalwire_agents/core/agent/config/__init__.py +14 -0
  6. signalwire_agents/core/agent/config/ephemeral.py +176 -0
  7. signalwire_agents/core/agent/deployment/__init__.py +0 -0
  8. signalwire_agents/core/agent/deployment/handlers/__init__.py +0 -0
  9. signalwire_agents/core/agent/prompt/__init__.py +14 -0
  10. signalwire_agents/core/agent/prompt/manager.py +288 -0
  11. signalwire_agents/core/agent/routing/__init__.py +0 -0
  12. signalwire_agents/core/agent/security/__init__.py +0 -0
  13. signalwire_agents/core/agent/swml/__init__.py +0 -0
  14. signalwire_agents/core/agent/tools/__init__.py +15 -0
  15. signalwire_agents/core/agent/tools/decorator.py +95 -0
  16. signalwire_agents/core/agent/tools/registry.py +192 -0
  17. signalwire_agents/core/agent_base.py +131 -413
  18. signalwire_agents/core/data_map.py +3 -15
  19. signalwire_agents/core/skill_manager.py +0 -17
  20. signalwire_agents/core/swaig_function.py +0 -2
  21. signalwire_agents/core/swml_builder.py +207 -11
  22. signalwire_agents/core/swml_renderer.py +123 -312
  23. signalwire_agents/core/swml_service.py +25 -94
  24. signalwire_agents/search/index_builder.py +1 -1
  25. signalwire_agents/skills/api_ninjas_trivia/__init__.py +3 -0
  26. signalwire_agents/skills/api_ninjas_trivia/skill.py +192 -0
  27. signalwire_agents/skills/play_background_file/__init__.py +3 -0
  28. signalwire_agents/skills/play_background_file/skill.py +197 -0
  29. signalwire_agents/skills/weather_api/__init__.py +3 -0
  30. signalwire_agents/skills/weather_api/skill.py +154 -0
  31. {signalwire_agents-0.1.20.dist-info → signalwire_agents-0.1.23.dist-info}/METADATA +5 -8
  32. {signalwire_agents-0.1.20.dist-info → signalwire_agents-0.1.23.dist-info}/RECORD +37 -18
  33. {signalwire_agents-0.1.20.data → signalwire_agents-0.1.23.data}/data/schema.json +0 -0
  34. {signalwire_agents-0.1.20.dist-info → signalwire_agents-0.1.23.dist-info}/WHEEL +0 -0
  35. {signalwire_agents-0.1.20.dist-info → signalwire_agents-0.1.23.dist-info}/entry_points.txt +0 -0
  36. {signalwire_agents-0.1.20.dist-info → signalwire_agents-0.1.23.dist-info}/licenses/LICENSE +0 -0
  37. {signalwire_agents-0.1.20.dist-info → signalwire_agents-0.1.23.dist-info}/top_level.txt +0 -0
@@ -257,7 +257,6 @@ class DataMap:
257
257
 
258
258
  Args:
259
259
  foreach_config: Either:
260
- - String: JSON path to array in response (deprecated, kept for compatibility)
261
260
  - Dict: Foreach configuration with keys:
262
261
  - input_key: Key in API response containing the array
263
262
  - output_key: Name for the built string variable
@@ -278,20 +277,7 @@ class DataMap:
278
277
  if not self._webhooks:
279
278
  raise ValueError("Must add webhook before setting foreach")
280
279
 
281
- if isinstance(foreach_config, str):
282
- # Legacy support - convert string to basic foreach config
283
- # Extract the key from "${response.key}" format if present
284
- if foreach_config.startswith("${response.") and foreach_config.endswith("}"):
285
- key = foreach_config[12:-1] # Remove "${response." and "}"
286
- else:
287
- key = foreach_config
288
-
289
- foreach_data = {
290
- "input_key": key,
291
- "output_key": "foreach_output",
292
- "append": "${this}"
293
- }
294
- else:
280
+ if isinstance(foreach_config, dict):
295
281
  # New format - validate required keys
296
282
  required_keys = ["input_key", "output_key", "append"]
297
283
  missing_keys = [key for key in required_keys if key not in foreach_config]
@@ -299,6 +285,8 @@ class DataMap:
299
285
  raise ValueError(f"foreach config missing required keys: {missing_keys}")
300
286
 
301
287
  foreach_data = foreach_config
288
+ else:
289
+ raise ValueError("foreach_config must be a dictionary")
302
290
 
303
291
  self._webhooks[-1]["foreach"] = foreach_data
304
292
  return self
@@ -134,13 +134,6 @@ class SkillManager:
134
134
  if skill_identifier in self.loaded_skills:
135
135
  instance_key = skill_identifier
136
136
  skill_instance = self.loaded_skills[skill_identifier]
137
- else:
138
- # Try to find by skill name (for backwards compatibility)
139
- for key, instance in self.loaded_skills.items():
140
- if instance.SKILL_NAME == skill_identifier:
141
- instance_key = key
142
- skill_instance = instance
143
- break
144
137
 
145
138
  if skill_instance is None:
146
139
  self.logger.warning(f"Skill '{skill_identifier}' is not loaded")
@@ -173,11 +166,6 @@ class SkillManager:
173
166
  if skill_identifier in self.loaded_skills:
174
167
  return True
175
168
 
176
- # Try to find by skill name (for backwards compatibility)
177
- for instance in self.loaded_skills.values():
178
- if instance.SKILL_NAME == skill_identifier:
179
- return True
180
-
181
169
  return False
182
170
 
183
171
  def get_skill(self, skill_identifier: str) -> Optional[SkillBase]:
@@ -194,9 +182,4 @@ class SkillManager:
194
182
  if skill_identifier in self.loaded_skills:
195
183
  return self.loaded_skills[skill_identifier]
196
184
 
197
- # Try to find by skill name (for backwards compatibility)
198
- for instance in self.loaded_skills.values():
199
- if instance.SKILL_NAME == skill_identifier:
200
- return instance
201
-
202
185
  return None
@@ -179,5 +179,3 @@ class SWAIGFunction:
179
179
 
180
180
  return function_def
181
181
 
182
- # Add an alias for backward compatibility
183
- SwaigFunction = SWAIGFunction
@@ -14,6 +14,7 @@ This module provides a fluent builder API for creating SWML documents.
14
14
  It allows for chaining method calls to build up a document step by step.
15
15
  """
16
16
 
17
+ import types
17
18
  from typing import Dict, List, Any, Optional, Union, TypeVar
18
19
  try:
19
20
  from typing import Self # Python 3.11+
@@ -43,6 +44,12 @@ class SWMLBuilder:
43
44
  service: The SWMLService to delegate to
44
45
  """
45
46
  self.service = service
47
+
48
+ # Dictionary to cache dynamically created methods
49
+ self._verb_methods_cache = {}
50
+
51
+ # Create auto-vivified methods for all verbs
52
+ self._create_verb_methods()
46
53
 
47
54
  def answer(self, max_duration: Optional[int] = None, codecs: Optional[str] = None) -> Self:
48
55
  """
@@ -55,7 +62,12 @@ class SWMLBuilder:
55
62
  Returns:
56
63
  Self for method chaining
57
64
  """
58
- self.service.add_answer_verb(max_duration, codecs)
65
+ config = {}
66
+ if max_duration is not None:
67
+ config["max_duration"] = max_duration
68
+ if codecs is not None:
69
+ config["codecs"] = codecs
70
+ self.service.add_verb("answer", config)
59
71
  return self
60
72
 
61
73
  def hangup(self, reason: Optional[str] = None) -> Self:
@@ -68,7 +80,10 @@ class SWMLBuilder:
68
80
  Returns:
69
81
  Self for method chaining
70
82
  """
71
- self.service.add_hangup_verb(reason)
83
+ config = {}
84
+ if reason is not None:
85
+ config["reason"] = reason
86
+ self.service.add_verb("hangup", config)
72
87
  return self
73
88
 
74
89
  def ai(self,
@@ -92,14 +107,26 @@ class SWMLBuilder:
92
107
  Returns:
93
108
  Self for method chaining
94
109
  """
95
- self.service.add_ai_verb(
96
- prompt_text=prompt_text,
97
- prompt_pom=prompt_pom,
98
- post_prompt=post_prompt,
99
- post_prompt_url=post_prompt_url,
100
- swaig=swaig,
101
- **kwargs
102
- )
110
+ config = {}
111
+
112
+ # Handle prompt (either text or POM, but not both)
113
+ if prompt_text is not None:
114
+ config["prompt"] = prompt_text
115
+ elif prompt_pom is not None:
116
+ config["prompt"] = prompt_pom
117
+
118
+ # Add optional parameters
119
+ if post_prompt is not None:
120
+ config["post_prompt"] = post_prompt
121
+ if post_prompt_url is not None:
122
+ config["post_prompt_url"] = post_prompt_url
123
+ if swaig is not None:
124
+ config["SWAIG"] = swaig
125
+
126
+ # Add any additional kwargs
127
+ config.update(kwargs)
128
+
129
+ self.service.add_verb("ai", config)
103
130
  return self
104
131
 
105
132
  def play(self, url: Optional[str] = None, urls: Optional[List[str]] = None,
@@ -215,4 +242,173 @@ class SWMLBuilder:
215
242
  Self for method chaining
216
243
  """
217
244
  self.service.reset_document()
218
- return self
245
+ return self
246
+
247
+ def _create_verb_methods(self) -> None:
248
+ """
249
+ Create auto-vivified methods for all verbs at initialization time
250
+
251
+ This creates methods for all SWML verbs defined in the schema,
252
+ allowing for fluent chaining like builder.denoise().goto().play()
253
+ """
254
+ # Get all verb names from the schema
255
+ if not hasattr(self.service, 'schema_utils') or not self.service.schema_utils:
256
+ return
257
+
258
+ verb_names = self.service.schema_utils.get_all_verb_names()
259
+
260
+ # Create a method for each verb
261
+ for verb_name in verb_names:
262
+ # Skip verbs that already have specific methods
263
+ if hasattr(self, verb_name):
264
+ continue
265
+
266
+ # Handle sleep verb specially since it takes an integer directly
267
+ if verb_name == "sleep":
268
+ def sleep_method(self_instance, duration=None, **kwargs):
269
+ """
270
+ Add the sleep verb to the document.
271
+
272
+ Args:
273
+ duration: The amount of time to sleep in milliseconds
274
+ """
275
+ # Sleep verb takes a direct integer parameter in SWML
276
+ if duration is not None:
277
+ self_instance.service.add_verb("sleep", duration)
278
+ elif kwargs:
279
+ # Try to get the value from kwargs
280
+ self_instance.service.add_verb("sleep", next(iter(kwargs.values())))
281
+ else:
282
+ raise TypeError("sleep() missing required argument: 'duration'")
283
+ return self_instance
284
+
285
+ # Set it as an attribute of self
286
+ setattr(self, verb_name, types.MethodType(sleep_method, self))
287
+
288
+ # Also cache it for later
289
+ self._verb_methods_cache[verb_name] = sleep_method
290
+ continue
291
+
292
+ # Generate the method implementation for normal verbs
293
+ def make_verb_method(name):
294
+ def verb_method(self_instance, **kwargs):
295
+ """
296
+ Dynamically generated method for SWML verb - returns self for chaining
297
+ """
298
+ config = {}
299
+ for key, value in kwargs.items():
300
+ if value is not None:
301
+ config[key] = value
302
+ self_instance.service.add_verb(name, config)
303
+ return self_instance
304
+
305
+ # Add docstring to the method
306
+ if hasattr(self.service.schema_utils, 'get_verb_properties'):
307
+ verb_properties = self.service.schema_utils.get_verb_properties(name)
308
+ if "description" in verb_properties:
309
+ verb_method.__doc__ = f"Add the {name} verb to the document.\n\n{verb_properties['description']}\n\nReturns: Self for method chaining"
310
+ else:
311
+ verb_method.__doc__ = f"Add the {name} verb to the document.\n\nReturns: Self for method chaining"
312
+ else:
313
+ verb_method.__doc__ = f"Add the {name} verb to the document.\n\nReturns: Self for method chaining"
314
+
315
+ return verb_method
316
+
317
+ # Create the method with closure over the verb name
318
+ method = make_verb_method(verb_name)
319
+
320
+ # Set it as an attribute of self
321
+ setattr(self, verb_name, types.MethodType(method, self))
322
+
323
+ # Also cache it for later
324
+ self._verb_methods_cache[verb_name] = method
325
+
326
+ def __getattr__(self, name: str) -> Any:
327
+ """
328
+ Dynamically generate and return SWML verb methods when accessed
329
+
330
+ This method is called when an attribute lookup fails through the normal
331
+ mechanisms. It checks if the attribute name corresponds to a SWML verb
332
+ defined in the schema, and if so, dynamically creates a method for that verb.
333
+
334
+ Args:
335
+ name: The name of the attribute being accessed
336
+
337
+ Returns:
338
+ The dynamically created verb method if name is a valid SWML verb,
339
+ otherwise raises AttributeError
340
+
341
+ Raises:
342
+ AttributeError: If name is not a valid SWML verb
343
+ """
344
+ # First check if this is a valid SWML verb
345
+ if not hasattr(self.service, 'schema_utils') or not self.service.schema_utils:
346
+ msg = f"'{self.__class__.__name__}' object has no attribute '{name}' (no schema available)"
347
+ raise AttributeError(msg)
348
+
349
+ verb_names = self.service.schema_utils.get_all_verb_names()
350
+
351
+ if name in verb_names:
352
+ # Check if we already have this method in the cache
353
+ if not hasattr(self, '_verb_methods_cache'):
354
+ self._verb_methods_cache = {}
355
+
356
+ if name in self._verb_methods_cache:
357
+ return types.MethodType(self._verb_methods_cache[name], self)
358
+
359
+ # Handle sleep verb specially since it takes an integer directly
360
+ if name == "sleep":
361
+ def sleep_method(self_instance, duration=None, **kwargs):
362
+ """
363
+ Add the sleep verb to the document.
364
+
365
+ Args:
366
+ duration: The amount of time to sleep in milliseconds
367
+ """
368
+ # Sleep verb takes a direct integer parameter in SWML
369
+ if duration is not None:
370
+ self_instance.service.add_verb("sleep", duration)
371
+ elif kwargs:
372
+ # Try to get the value from kwargs
373
+ self_instance.service.add_verb("sleep", next(iter(kwargs.values())))
374
+ else:
375
+ raise TypeError("sleep() missing required argument: 'duration'")
376
+ return self_instance
377
+
378
+ # Cache the method for future use
379
+ self._verb_methods_cache[name] = sleep_method
380
+
381
+ # Return the bound method
382
+ return types.MethodType(sleep_method, self)
383
+
384
+ # Generate the method implementation for normal verbs
385
+ def verb_method(self_instance, **kwargs):
386
+ """
387
+ Dynamically generated method for SWML verb - returns self for chaining
388
+ """
389
+ config = {}
390
+ for key, value in kwargs.items():
391
+ if value is not None:
392
+ config[key] = value
393
+ self_instance.service.add_verb(name, config)
394
+ return self_instance
395
+
396
+ # Add docstring to the method
397
+ if hasattr(self.service.schema_utils, 'get_verb_properties'):
398
+ verb_properties = self.service.schema_utils.get_verb_properties(name)
399
+ if "description" in verb_properties:
400
+ verb_method.__doc__ = f"Add the {name} verb to the document.\n\n{verb_properties['description']}\n\nReturns: Self for method chaining"
401
+ else:
402
+ verb_method.__doc__ = f"Add the {name} verb to the document.\n\nReturns: Self for method chaining"
403
+ else:
404
+ verb_method.__doc__ = f"Add the {name} verb to the document.\n\nReturns: Self for method chaining"
405
+
406
+ # Cache the method for future use
407
+ self._verb_methods_cache[name] = verb_method
408
+
409
+ # Return the bound method
410
+ return types.MethodType(verb_method, self)
411
+
412
+ # Not a valid verb
413
+ msg = f"'{self.__class__.__name__}' object has no attribute '{name}'"
414
+ raise AttributeError(msg)