signalwire-agents 0.1.35__py3-none-any.whl → 0.1.37__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.
@@ -18,7 +18,7 @@ A package for building AI agents using SignalWire's AI and SWML capabilities.
18
18
  from .core.logging_config import configure_logging
19
19
  configure_logging()
20
20
 
21
- __version__ = "0.1.35"
21
+ __version__ = "0.1.37"
22
22
 
23
23
  # Import core classes for easier access
24
24
  from .core.agent_base import AgentBase
@@ -62,6 +62,67 @@ def list_skills(*args, **kwargs):
62
62
  except ImportError:
63
63
  raise NotImplementedError("CLI helpers not available")
64
64
 
65
+ def list_skills_with_params():
66
+ """
67
+ Get complete schema for all available skills including parameter metadata
68
+
69
+ This function returns a comprehensive schema for all available skills,
70
+ including their metadata and parameter definitions. This is useful for
71
+ GUI configuration tools, API documentation, or programmatic skill discovery.
72
+
73
+ Returns:
74
+ Dict[str, Dict[str, Any]]: Complete skill schema where keys are skill names
75
+
76
+ Example:
77
+ >>> schema = list_skills_with_params()
78
+ >>> print(schema['web_search']['parameters']['api_key'])
79
+ {
80
+ 'type': 'string',
81
+ 'description': 'Google Custom Search API key',
82
+ 'required': True,
83
+ 'hidden': True,
84
+ 'env_var': 'GOOGLE_SEARCH_API_KEY'
85
+ }
86
+ """
87
+ from signalwire_agents.skills.registry import skill_registry
88
+ return skill_registry.get_all_skills_schema()
89
+
90
+ def register_skill(skill_class):
91
+ """
92
+ Register a custom skill class
93
+
94
+ This allows third-party code to register skill classes directly without
95
+ requiring them to be in a specific directory structure.
96
+
97
+ Args:
98
+ skill_class: A class that inherits from SkillBase
99
+
100
+ Example:
101
+ >>> from my_custom_skills import MyWeatherSkill
102
+ >>> register_skill(MyWeatherSkill)
103
+ >>> # Now you can use it in agents:
104
+ >>> agent.add_skill('my_weather')
105
+ """
106
+ from signalwire_agents.skills.registry import skill_registry
107
+ return skill_registry.register_skill(skill_class)
108
+
109
+ def add_skill_directory(path):
110
+ """
111
+ Add a directory to search for skills
112
+
113
+ This allows third-party skill collections to be registered by path.
114
+ Skills in these directories should follow the same structure as built-in skills.
115
+
116
+ Args:
117
+ path: Path to directory containing skill subdirectories
118
+
119
+ Example:
120
+ >>> add_skill_directory('/opt/custom_skills')
121
+ >>> # Now agent.add_skill('my_custom_skill') will search in this directory
122
+ """
123
+ from signalwire_agents.skills.registry import skill_registry
124
+ return skill_registry.add_skill_directory(path)
125
+
65
126
  __all__ = [
66
127
  "AgentBase",
67
128
  "AgentServer",
@@ -78,5 +139,8 @@ __all__ = [
78
139
  "create_simple_context",
79
140
  "start_agent",
80
141
  "run_agent",
81
- "list_skills"
142
+ "list_skills",
143
+ "list_skills_with_params",
144
+ "register_skill",
145
+ "add_skill_directory"
82
146
  ]
@@ -692,6 +692,13 @@ class AgentBase(
692
692
  # Add answer verb with auto-answer enabled
693
693
  agent_to_use.add_verb("answer", {})
694
694
 
695
+ # Add recording if enabled
696
+ if agent_to_use._record_call:
697
+ agent_to_use.add_verb("record_call", {
698
+ "format": agent_to_use._record_format,
699
+ "stereo": agent_to_use._record_stereo
700
+ })
701
+
695
702
  # Use the AI verb handler to build and validate the AI verb config
696
703
  ai_config = {}
697
704
 
@@ -811,6 +818,14 @@ class AgentBase(
811
818
  # Clear and rebuild the document with the modified AI config
812
819
  agent_to_use.reset_document()
813
820
  agent_to_use.add_verb("answer", {})
821
+
822
+ # Add recording if enabled
823
+ if agent_to_use._record_call:
824
+ agent_to_use.add_verb("record_call", {
825
+ "format": agent_to_use._record_format,
826
+ "stereo": agent_to_use._record_stereo
827
+ })
828
+
814
829
  agent_to_use.add_verb("ai", ai_config)
815
830
 
816
831
  # Return the rendered document as a string
@@ -114,4 +114,67 @@ class SkillBase(ABC):
114
114
 
115
115
  # For multi-instance skills, create key from skill name + tool name
116
116
  tool_name = self.params.get('tool_name', self.SKILL_NAME)
117
- return f"{self.SKILL_NAME}_{tool_name}"
117
+ return f"{self.SKILL_NAME}_{tool_name}"
118
+
119
+ @classmethod
120
+ def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
121
+ """
122
+ Get the parameter schema for this skill
123
+
124
+ This method returns metadata about all parameters the skill accepts,
125
+ including their types, descriptions, default values, and whether they
126
+ are required or should be hidden (e.g., API keys).
127
+
128
+ The base implementation provides common parameters available to all skills.
129
+ Subclasses should override this method and merge their specific parameters
130
+ with the base schema.
131
+
132
+ Returns:
133
+ Dict[str, Dict[str, Any]]: Parameter schema where keys are parameter names
134
+ and values are dictionaries containing:
135
+ - type: Parameter type ("string", "integer", "number", "boolean", "object", "array")
136
+ - description: Human-readable description
137
+ - default: Default value if not provided (optional)
138
+ - required: Whether the parameter is required (default: False)
139
+ - hidden: Whether to hide this field in UIs (for secrets/keys)
140
+ - env_var: Environment variable that can provide this value (optional)
141
+ - enum: List of allowed values (optional)
142
+ - min/max: Minimum/maximum values for numeric types (optional)
143
+
144
+ Example:
145
+ {
146
+ "tool_name": {
147
+ "type": "string",
148
+ "description": "Name for the tool when using multiple instances",
149
+ "default": "my_skill",
150
+ "required": False
151
+ },
152
+ "api_key": {
153
+ "type": "string",
154
+ "description": "API key for the service",
155
+ "required": True,
156
+ "hidden": True,
157
+ "env_var": "MY_API_KEY"
158
+ }
159
+ }
160
+ """
161
+ schema = {}
162
+
163
+ # Add swaig_fields parameter (available to all skills)
164
+ schema["swaig_fields"] = {
165
+ "type": "object",
166
+ "description": "Additional SWAIG function metadata to merge into tool definitions",
167
+ "default": {},
168
+ "required": False
169
+ }
170
+
171
+ # Add tool_name for multi-instance skills
172
+ if cls.SUPPORTS_MULTIPLE_INSTANCES:
173
+ schema["tool_name"] = {
174
+ "type": "string",
175
+ "description": "Custom name for this skill instance (for multiple instances)",
176
+ "default": cls.SKILL_NAME,
177
+ "required": False
178
+ }
179
+
180
+ return schema
@@ -110,4 +110,19 @@ class DateTimeSkill(SkillBase):
110
110
  "Both tools support different timezones"
111
111
  ]
112
112
  }
113
- ]
113
+ ]
114
+
115
+ @classmethod
116
+ def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
117
+ """
118
+ Get the parameter schema for the datetime skill
119
+
120
+ The datetime skill has no custom parameters - it inherits only
121
+ the base parameters from SkillBase.
122
+ """
123
+ # Get base schema from parent
124
+ schema = super().get_parameter_schema()
125
+
126
+ # No additional parameters for datetime skill
127
+
128
+ return schema
@@ -88,4 +88,19 @@ class MathSkill(SkillBase):
88
88
  "Can handle parentheses for complex expressions"
89
89
  ]
90
90
  }
91
- ]
91
+ ]
92
+
93
+ @classmethod
94
+ def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
95
+ """
96
+ Get the parameter schema for the math skill
97
+
98
+ The math skill has no custom parameters - it inherits only
99
+ the base parameters from SkillBase.
100
+ """
101
+ # Get base schema from parent
102
+ schema = super().get_parameter_schema()
103
+
104
+ # No additional parameters for math skill
105
+
106
+ return schema
@@ -12,7 +12,7 @@ import importlib
12
12
  import importlib.util
13
13
  import inspect
14
14
  import sys
15
- from typing import Dict, List, Type, Optional
15
+ from typing import Dict, List, Type, Optional, Any
16
16
  from pathlib import Path
17
17
 
18
18
  from signalwire_agents.core.skill_base import SkillBase
@@ -23,28 +23,61 @@ class SkillRegistry:
23
23
 
24
24
  def __init__(self):
25
25
  self._skills: Dict[str, Type[SkillBase]] = {}
26
+ self._external_paths: List[Path] = [] # Additional paths to search for skills
27
+ self._entry_points_loaded = False
26
28
  self.logger = get_logger("skill_registry")
27
- # Remove _discovered flag since we're not doing discovery anymore
28
29
 
29
30
  def _load_skill_on_demand(self, skill_name: str) -> Optional[Type[SkillBase]]:
30
31
  """Load a skill on-demand by name"""
31
32
  if skill_name in self._skills:
32
33
  return self._skills[skill_name]
33
-
34
- # Try to load the skill from its expected directory
34
+
35
+ # First, ensure entry points are loaded
36
+ self._load_entry_points()
37
+
38
+ # Check if skill was loaded from entry points
39
+ if skill_name in self._skills:
40
+ return self._skills[skill_name]
41
+
42
+ # Search in built-in skills directory
35
43
  skills_dir = Path(__file__).parent
36
- skill_dir = skills_dir / skill_name
44
+ skill_class = self._load_skill_from_path(skill_name, skills_dir)
45
+ if skill_class:
46
+ return skill_class
47
+
48
+ # Search in external paths
49
+ for external_path in self._external_paths:
50
+ skill_class = self._load_skill_from_path(skill_name, external_path)
51
+ if skill_class:
52
+ return skill_class
53
+
54
+ # Search in environment variable paths
55
+ env_paths = os.environ.get('SIGNALWIRE_SKILL_PATHS', '').split(':')
56
+ for path_str in env_paths:
57
+ if path_str:
58
+ skill_class = self._load_skill_from_path(skill_name, Path(path_str))
59
+ if skill_class:
60
+ return skill_class
61
+
62
+ self.logger.debug(f"Skill '{skill_name}' not found in any registered paths")
63
+ return None
64
+
65
+ def _load_skill_from_path(self, skill_name: str, base_path: Path) -> Optional[Type[SkillBase]]:
66
+ """Try to load a skill from a specific base path"""
67
+ skill_dir = base_path / skill_name
37
68
  skill_file = skill_dir / "skill.py"
38
69
 
39
70
  if not skill_file.exists():
40
- self.logger.debug(f"Skill '{skill_name}' not found at {skill_file}")
41
71
  return None
42
72
 
43
73
  try:
44
- # Import the skill module
45
- module_name = f"signalwire_agents.skills.{skill_name}.skill"
74
+ # Create unique module name to avoid conflicts
75
+ module_name = f"signalwire_agents_external.{base_path.name}.{skill_name}.skill"
46
76
  spec = importlib.util.spec_from_file_location(module_name, skill_file)
47
77
  module = importlib.util.module_from_spec(spec)
78
+
79
+ # Add to sys.modules to handle relative imports
80
+ sys.modules[module_name] = module
48
81
  spec.loader.exec_module(module)
49
82
 
50
83
  # Find SkillBase subclasses in the module
@@ -52,6 +85,7 @@ class SkillRegistry:
52
85
  if (inspect.isclass(obj) and
53
86
  issubclass(obj, SkillBase) and
54
87
  obj != SkillBase and
88
+ hasattr(obj, 'SKILL_NAME') and
55
89
  obj.SKILL_NAME == skill_name): # Match exact skill name
56
90
 
57
91
  self.register_skill(obj)
@@ -75,7 +109,25 @@ class SkillRegistry:
75
109
  pass
76
110
 
77
111
  def register_skill(self, skill_class: Type[SkillBase]) -> None:
78
- """Register a skill class"""
112
+ """
113
+ Register a skill class directly
114
+
115
+ This allows third-party code to register skill classes without
116
+ requiring them to be in a specific directory structure.
117
+
118
+ Args:
119
+ skill_class: A class that inherits from SkillBase
120
+
121
+ Example:
122
+ from my_custom_skills import MyWeatherSkill
123
+ skill_registry.register_skill(MyWeatherSkill)
124
+ """
125
+ if not issubclass(skill_class, SkillBase):
126
+ raise ValueError(f"{skill_class} must inherit from SkillBase")
127
+
128
+ if not hasattr(skill_class, 'SKILL_NAME') or skill_class.SKILL_NAME is None:
129
+ raise ValueError(f"{skill_class} must define SKILL_NAME")
130
+
79
131
  if skill_class.SKILL_NAME in self._skills:
80
132
  self.logger.warning(f"Skill '{skill_class.SKILL_NAME}' already registered")
81
133
  return
@@ -115,6 +167,257 @@ class SkillRegistry:
115
167
  })
116
168
 
117
169
  return available_skills
170
+
171
+ def get_all_skills_schema(self) -> Dict[str, Dict[str, Any]]:
172
+ """
173
+ Get complete schema for all available skills including parameter metadata
174
+
175
+ This method scans all available skills and returns a comprehensive schema
176
+ that includes skill metadata and parameter definitions suitable for GUI
177
+ configuration or API documentation.
178
+
179
+ Returns:
180
+ Dict[str, Dict[str, Any]]: Complete skill schema where keys are skill names
181
+ and values contain:
182
+ - name: Skill name
183
+ - description: Skill description
184
+ - version: Skill version
185
+ - supports_multiple_instances: Whether multiple instances are allowed
186
+ - required_packages: List of required Python packages
187
+ - required_env_vars: List of required environment variables
188
+ - parameters: Parameter schema from get_parameter_schema()
189
+ - source: Where the skill was loaded from ('built-in', 'external', 'entry_point', 'registered')
190
+
191
+ Example:
192
+ {
193
+ "web_search": {
194
+ "name": "web_search",
195
+ "description": "Search the web for information",
196
+ "version": "1.0.0",
197
+ "supports_multiple_instances": True,
198
+ "required_packages": ["bs4", "requests"],
199
+ "required_env_vars": [],
200
+ "parameters": {
201
+ "api_key": {
202
+ "type": "string",
203
+ "description": "Google API key",
204
+ "required": True,
205
+ "hidden": True,
206
+ "env_var": "GOOGLE_SEARCH_API_KEY"
207
+ },
208
+ ...
209
+ },
210
+ "source": "built-in"
211
+ }
212
+ }
213
+ """
214
+ skills_schema = {}
215
+
216
+ # Load entry points first
217
+ self._load_entry_points()
218
+
219
+ # Helper function to add skill to schema
220
+ def add_skill_to_schema(skill_class, source):
221
+ try:
222
+ # Get parameter schema
223
+ try:
224
+ parameter_schema = skill_class.get_parameter_schema()
225
+ except AttributeError:
226
+ # Skill doesn't implement get_parameter_schema yet
227
+ parameter_schema = {}
228
+
229
+ skills_schema[skill_class.SKILL_NAME] = {
230
+ "name": skill_class.SKILL_NAME,
231
+ "description": skill_class.SKILL_DESCRIPTION,
232
+ "version": getattr(skill_class, 'SKILL_VERSION', '1.0.0'),
233
+ "supports_multiple_instances": getattr(skill_class, 'SUPPORTS_MULTIPLE_INSTANCES', False),
234
+ "required_packages": getattr(skill_class, 'REQUIRED_PACKAGES', []),
235
+ "required_env_vars": getattr(skill_class, 'REQUIRED_ENV_VARS', []),
236
+ "parameters": parameter_schema,
237
+ "source": source
238
+ }
239
+ except Exception as e:
240
+ self.logger.error(f"Failed to get schema for skill '{skill_class.SKILL_NAME}': {e}")
241
+
242
+ # Add already registered skills first (includes entry points)
243
+ for skill_name, skill_class in self._skills.items():
244
+ add_skill_to_schema(skill_class, 'registered')
245
+
246
+ # Scan built-in skills directory
247
+ skills_dir = Path(__file__).parent
248
+ for item in skills_dir.iterdir():
249
+ if item.is_dir() and not item.name.startswith('__'):
250
+ skill_file = item / "skill.py"
251
+ if skill_file.exists() and item.name not in skills_schema:
252
+ try:
253
+ skill_class = self._load_skill_on_demand(item.name)
254
+ if skill_class:
255
+ add_skill_to_schema(skill_class, 'built-in')
256
+ except Exception as e:
257
+ self.logger.error(f"Failed to load skill '{item.name}': {e}")
258
+
259
+ # Scan external directories
260
+ for external_path in self._external_paths:
261
+ if external_path.exists():
262
+ for item in external_path.iterdir():
263
+ if item.is_dir() and not item.name.startswith('__'):
264
+ skill_file = item / "skill.py"
265
+ if skill_file.exists() and item.name not in skills_schema:
266
+ try:
267
+ skill_class = self._load_skill_on_demand(item.name)
268
+ if skill_class:
269
+ add_skill_to_schema(skill_class, 'external')
270
+ except Exception as e:
271
+ self.logger.error(f"Failed to load skill '{item.name}': {e}")
272
+
273
+ # Scan environment variable paths
274
+ env_paths = os.environ.get('SIGNALWIRE_SKILL_PATHS', '').split(':')
275
+ for path_str in env_paths:
276
+ if path_str:
277
+ env_path = Path(path_str)
278
+ if env_path.exists():
279
+ for item in env_path.iterdir():
280
+ if item.is_dir() and not item.name.startswith('__'):
281
+ skill_file = item / "skill.py"
282
+ if skill_file.exists() and item.name not in skills_schema:
283
+ try:
284
+ skill_class = self._load_skill_on_demand(item.name)
285
+ if skill_class:
286
+ add_skill_to_schema(skill_class, 'external')
287
+ except Exception as e:
288
+ self.logger.error(f"Failed to load skill '{item.name}': {e}")
289
+
290
+ return skills_schema
291
+
292
+ def add_skill_directory(self, path: str) -> None:
293
+ """
294
+ Add a directory to search for skills
295
+
296
+ This allows third-party skill collections to be registered by path.
297
+ Skills in these directories should follow the same structure as built-in skills:
298
+ - Each skill in its own subdirectory
299
+ - skill.py file containing the skill class
300
+
301
+ Args:
302
+ path: Path to directory containing skill subdirectories
303
+
304
+ Example:
305
+ skill_registry.add_skill_directory('/opt/custom_skills')
306
+ # Now agent.add_skill('my_custom_skill') will search in this directory
307
+ """
308
+ skill_path = Path(path)
309
+ if not skill_path.exists():
310
+ raise ValueError(f"Skill directory does not exist: {path}")
311
+ if not skill_path.is_dir():
312
+ raise ValueError(f"Path is not a directory: {path}")
313
+
314
+ if skill_path not in self._external_paths:
315
+ self._external_paths.append(skill_path)
316
+ self.logger.info(f"Added external skill directory: {path}")
317
+
318
+ def _load_entry_points(self) -> None:
319
+ """
320
+ Load skills from Python entry points
321
+
322
+ This allows installed packages to register skills via setup.py:
323
+
324
+ entry_points={
325
+ 'signalwire_agents.skills': [
326
+ 'weather = my_package.skills:WeatherSkill',
327
+ 'stock = my_package.skills:StockSkill',
328
+ ]
329
+ }
330
+ """
331
+ if self._entry_points_loaded:
332
+ return
333
+
334
+ self._entry_points_loaded = True
335
+
336
+ try:
337
+ import pkg_resources
338
+
339
+ for entry_point in pkg_resources.iter_entry_points('signalwire_agents.skills'):
340
+ try:
341
+ skill_class = entry_point.load()
342
+ if issubclass(skill_class, SkillBase):
343
+ self.register_skill(skill_class)
344
+ self.logger.info(f"Loaded skill '{skill_class.SKILL_NAME}' from entry point '{entry_point.name}'")
345
+ else:
346
+ self.logger.warning(f"Entry point '{entry_point.name}' does not provide a SkillBase subclass")
347
+ except Exception as e:
348
+ self.logger.error(f"Failed to load skill from entry point '{entry_point.name}': {e}")
349
+
350
+ except ImportError:
351
+ # pkg_resources not available, try importlib.metadata (Python 3.8+)
352
+ try:
353
+ from importlib import metadata
354
+
355
+ entry_points = metadata.entry_points()
356
+ if hasattr(entry_points, 'select'):
357
+ # Python 3.10+
358
+ skill_entries = entry_points.select(group='signalwire_agents.skills')
359
+ else:
360
+ # Python 3.8-3.9
361
+ skill_entries = entry_points.get('signalwire_agents.skills', [])
362
+
363
+ for entry_point in skill_entries:
364
+ try:
365
+ skill_class = entry_point.load()
366
+ if issubclass(skill_class, SkillBase):
367
+ self.register_skill(skill_class)
368
+ self.logger.info(f"Loaded skill '{skill_class.SKILL_NAME}' from entry point '{entry_point.name}'")
369
+ else:
370
+ self.logger.warning(f"Entry point '{entry_point.name}' does not provide a SkillBase subclass")
371
+ except Exception as e:
372
+ self.logger.error(f"Failed to load skill from entry point '{entry_point.name}': {e}")
373
+
374
+ except ImportError:
375
+ # Neither pkg_resources nor importlib.metadata available
376
+ self.logger.debug("Entry point loading not available - install setuptools or use Python 3.8+")
377
+
378
+ def list_all_skill_sources(self) -> Dict[str, List[str]]:
379
+ """
380
+ List all skill sources and the skills available from each
381
+
382
+ Returns a dictionary mapping source types to lists of skill names:
383
+ {
384
+ 'built-in': ['datetime', 'math', ...],
385
+ 'external_paths': ['custom_skill1', ...],
386
+ 'entry_points': ['weather', ...],
387
+ 'registered': ['my_skill', ...]
388
+ }
389
+ """
390
+ sources = {
391
+ 'built-in': [],
392
+ 'external_paths': [],
393
+ 'entry_points': [],
394
+ 'registered': []
395
+ }
396
+
397
+ # Built-in skills
398
+ skills_dir = Path(__file__).parent
399
+ for item in skills_dir.iterdir():
400
+ if item.is_dir() and not item.name.startswith('__'):
401
+ skill_file = item / "skill.py"
402
+ if skill_file.exists():
403
+ sources['built-in'].append(item.name)
404
+
405
+ # External path skills
406
+ for external_path in self._external_paths:
407
+ if external_path.exists():
408
+ for item in external_path.iterdir():
409
+ if item.is_dir() and not item.name.startswith('__'):
410
+ skill_file = item / "skill.py"
411
+ if skill_file.exists():
412
+ sources['external_paths'].append(item.name)
413
+
414
+ # Already registered skills
415
+ for skill_name in self._skills:
416
+ # Determine source of registered skill
417
+ if skill_name not in sources['built-in']:
418
+ sources['registered'].append(skill_name)
419
+
420
+ return sources
118
421
 
119
422
  # Global registry instance
120
423
  skill_registry = SkillRegistry()
@@ -262,4 +262,63 @@ class WebSearchSkill(SkillBase):
262
262
  "Include relevant URLs so users can read more if interested"
263
263
  ]
264
264
  }
265
- ]
265
+ ]
266
+
267
+ @classmethod
268
+ def get_parameter_schema(cls) -> Dict[str, Dict[str, Any]]:
269
+ """
270
+ Get the parameter schema for the web search skill
271
+
272
+ Returns all configurable parameters for web search including
273
+ API credentials, search settings, and response customization.
274
+ """
275
+ # Get base schema from parent
276
+ schema = super().get_parameter_schema()
277
+
278
+ # Add web search specific parameters
279
+ schema.update({
280
+ "api_key": {
281
+ "type": "string",
282
+ "description": "Google Custom Search API key",
283
+ "required": True,
284
+ "hidden": True, # Mark as hidden since it's a secret
285
+ "env_var": "GOOGLE_SEARCH_API_KEY"
286
+ },
287
+ "search_engine_id": {
288
+ "type": "string",
289
+ "description": "Google Custom Search Engine ID",
290
+ "required": True,
291
+ "hidden": True, # Also a secret
292
+ "env_var": "GOOGLE_SEARCH_ENGINE_ID"
293
+ },
294
+ "num_results": {
295
+ "type": "integer",
296
+ "description": "Default number of search results to return",
297
+ "default": 1,
298
+ "required": False,
299
+ "min": 1,
300
+ "max": 10
301
+ },
302
+ "delay": {
303
+ "type": "number",
304
+ "description": "Delay between scraping pages in seconds",
305
+ "default": 0,
306
+ "required": False,
307
+ "min": 0
308
+ },
309
+ "max_content_length": {
310
+ "type": "integer",
311
+ "description": "Maximum content length per scraped page (characters)",
312
+ "default": 2000,
313
+ "required": False,
314
+ "min": 100
315
+ },
316
+ "no_results_message": {
317
+ "type": "string",
318
+ "description": "Message to show when no results are found. Use {query} as placeholder.",
319
+ "default": "I couldn't find any results for '{query}'. This might be due to a very specific query or temporary issues. Try rephrasing your search or asking about a different topic.",
320
+ "required": False
321
+ }
322
+ })
323
+
324
+ return schema
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: signalwire_agents
3
- Version: 0.1.35
3
+ Version: 0.1.37
4
4
  Summary: SignalWire AI Agents SDK
5
5
  Author-email: SignalWire Team <info@signalwire.com>
6
6
  License: MIT
@@ -70,6 +70,19 @@ Requires-Dist: striprtf>=0.0.26; extra == "search-all"
70
70
  Requires-Dist: openpyxl>=3.1.0; extra == "search-all"
71
71
  Requires-Dist: python-pptx>=0.6.21; extra == "search-all"
72
72
  Requires-Dist: python-magic>=0.4.27; extra == "search-all"
73
+ Provides-Extra: all
74
+ Requires-Dist: sentence-transformers>=2.2.0; extra == "all"
75
+ Requires-Dist: scikit-learn>=1.3.0; extra == "all"
76
+ Requires-Dist: nltk>=3.8; extra == "all"
77
+ Requires-Dist: numpy>=1.24.0; extra == "all"
78
+ Requires-Dist: spacy>=3.6.0; extra == "all"
79
+ Requires-Dist: pdfplumber>=0.9.0; extra == "all"
80
+ Requires-Dist: python-docx>=0.8.11; extra == "all"
81
+ Requires-Dist: markdown>=3.4.0; extra == "all"
82
+ Requires-Dist: striprtf>=0.0.26; extra == "all"
83
+ Requires-Dist: openpyxl>=3.1.0; extra == "all"
84
+ Requires-Dist: python-pptx>=0.6.21; extra == "all"
85
+ Requires-Dist: python-magic>=0.4.27; extra == "all"
73
86
  Dynamic: license-file
74
87
 
75
88
  <!-- Header -->
@@ -1,4 +1,4 @@
1
- signalwire_agents/__init__.py,sha256=itkVsKvgCn_Lf4-4fHAms_X8_3K7vcyZMm73sgk9dEU,2608
1
+ signalwire_agents/__init__.py,sha256=Q6aAHGpnIR3oOHNhDmf1Uv_fv9_ouhN0FA8lXn8RbS8,4845
2
2
  signalwire_agents/agent_server.py,sha256=x9HyWia8D3r6KMqY-Q4DtNVivfJWLTx8B-KzUI8okuA,26880
3
3
  signalwire_agents/schema.json,sha256=6-7ccbt39iM1CO36dOfvupRPfd0gnQ0XoAdyo-EFyjo,238042
4
4
  signalwire_agents/cli/__init__.py,sha256=XbxAQFaCIdGXIXJiriVBWoFPOJsC401u21588nO4TG8,388
@@ -23,7 +23,7 @@ signalwire_agents/cli/simulation/data_generation.py,sha256=pxa9aJ6XkI0O8yAIGvBTU
23
23
  signalwire_agents/cli/simulation/data_overrides.py,sha256=3_3pT6j-q2gRufPX2bZ1BrmY7u1IdloLooKAJil33vI,6319
24
24
  signalwire_agents/cli/simulation/mock_env.py,sha256=fvaR_xdLMm8AbpNUbTJOFG9THcti3Zds-0QNDbKMaYk,10249
25
25
  signalwire_agents/core/__init__.py,sha256=xjPq8DmUnWYUG28sd17n430VWPmMH9oZ9W14gYwG96g,806
26
- signalwire_agents/core/agent_base.py,sha256=F0dL5bdWY_n3zIz9qVC8jWu3xxNXS6cO3dFW_RCyuro,43951
26
+ signalwire_agents/core/agent_base.py,sha256=CMMtYlCsMEL-5caoXfjGYMIiRtm8HE67v95Y8ODm6sY,44506
27
27
  signalwire_agents/core/auth_handler.py,sha256=jXrof9WZ1W9qqlQT9WElcmSRafL2kG7207x5SqWN9MU,8481
28
28
  signalwire_agents/core/config_loader.py,sha256=rStVRRUaeMGrMc44ocr0diMQQARZhbKqwMqQ6kqUNos,8722
29
29
  signalwire_agents/core/contexts.py,sha256=g9FgOGMfGCUWlm57YZcv7CvOf-Ub9FdKZIOMu14ADfE,24428
@@ -32,7 +32,7 @@ signalwire_agents/core/function_result.py,sha256=MZZUoAHN8AXry-OBNA6Od5l-bKvMM2r
32
32
  signalwire_agents/core/logging_config.py,sha256=x4d_RAjBjVpJOFA2vXnPP2dNr13BZHz091J5rGpC77Y,13142
33
33
  signalwire_agents/core/pom_builder.py,sha256=ywuiIfP8BeLBPo_G4X1teZlG6zTCMkW71CZnmyoDTAQ,6636
34
34
  signalwire_agents/core/security_config.py,sha256=iAnAzKEJQiXL6mMpDaYm3Sjkxwm4x2N9HD6DeWSI8yI,12536
35
- signalwire_agents/core/skill_base.py,sha256=lOpVTLhD9NjStF7Lxh6bAQUGa3DpNYV4agXJRakRuX0,4258
35
+ signalwire_agents/core/skill_base.py,sha256=1b_4ht_T1BVnfzHYqoILb3idrrPYMs5-G-adHo2IVss,6903
36
36
  signalwire_agents/core/skill_manager.py,sha256=9E2mPcn8-97t4OAnHCSdUEOW0nyr_g4zkWe9OQrU48g,7938
37
37
  signalwire_agents/core/swaig_function.py,sha256=VFIYQWltvYMrUfN_lfda-YzuQ2PqF2vhb-PegAliwCc,7076
38
38
  signalwire_agents/core/swml_builder.py,sha256=tJBFDAVTENEfjGLp2h9_AKOYt5O9FrSYLI-nZZVwM1E,15604
@@ -76,7 +76,7 @@ signalwire_agents/search/search_engine.py,sha256=KFF33aPsBdwb2N1aTJmAA0xo3KPANoX
76
76
  signalwire_agents/search/search_service.py,sha256=zidaQb2aRusAODU8C6Nw65ZlTy3DjJ2-bMJavhMl7vk,15608
77
77
  signalwire_agents/skills/README.md,sha256=sM1_08IsKdRDCzYHPLzppJbaK5MvRelsVL6Kd9A9Ubo,12193
78
78
  signalwire_agents/skills/__init__.py,sha256=9AMEcyk2tDaGiUjwVIson_tVWxV4oU_2NnGGNTbHuyQ,533
79
- signalwire_agents/skills/registry.py,sha256=lnr0XFOQ5YC_WgsAT6Id3RMAJ2-nf2ZCdqB7z_huLQI,4889
79
+ signalwire_agents/skills/registry.py,sha256=nV8chVLEDZwZO8p_szAizKdirBy_uj9GkyepJfumCW4,18486
80
80
  signalwire_agents/skills/api_ninjas_trivia/README.md,sha256=SoyS7VFh3eVIiVnQ5gfTfs0a_gAlLwnmT2W2FrbNU0A,6762
81
81
  signalwire_agents/skills/api_ninjas_trivia/__init__.py,sha256=zN305bBQkzlJyUNsPUMPt3gDJbvc-Iigkdh0rBou_RE,267
82
82
  signalwire_agents/skills/api_ninjas_trivia/skill.py,sha256=phioqvXivdoklg7tHE998Ac5S6-0Q90X8OmEjSIGprM,7397
@@ -88,13 +88,13 @@ signalwire_agents/skills/datasphere_serverless/__init__.py,sha256=jpMNDcGiXsVbSC
88
88
  signalwire_agents/skills/datasphere_serverless/skill.py,sha256=zgEoTY8Jm6YKJBzM1kn3j7tf492K-NiKG7DbE3GReeE,6787
89
89
  signalwire_agents/skills/datetime/README.md,sha256=95SzVz-Pcm9MPqZ4D3sSYKMwdpsDNwwCpWFRK027-Pc,4534
90
90
  signalwire_agents/skills/datetime/__init__.py,sha256=Irajm2sUhmQVFgais-J-q-3d58tNnJ4nbLmnphr90nI,234
91
- signalwire_agents/skills/datetime/skill.py,sha256=aBBdcPV5xWX6uWi9W0VqZ0kKOqC3JHrAhG1bZn58ro8,3900
91
+ signalwire_agents/skills/datetime/skill.py,sha256=mA-dxBhZOIbMygBQ3Z4jmFCH-zsD8HnjcWC4asp6Gl0,4370
92
92
  signalwire_agents/skills/joke/README.md,sha256=xUa2_0Pk9neli-UJxI4BPt3Fb1_5Xa3m8RuDlrkfBao,3594
93
93
  signalwire_agents/skills/joke/__init__.py,sha256=8Rc5_nj30bdga2n9H9JSI2WzMn40pjApd-y-tk5WIkI,244
94
94
  signalwire_agents/skills/joke/skill.py,sha256=AFaf6fMy0sxUPJHvcnf3CWMuPqpJP4ODscUexMadEcU,3381
95
95
  signalwire_agents/skills/math/README.md,sha256=Nrv7PxkFPSxdnAN6856Fp1CfvsUwdncpRFFDERxmMe0,5335
96
96
  signalwire_agents/skills/math/__init__.py,sha256=F7emZqBpAAkqJZxA3RNTzRSAXE5e2xu8PtFOPHebfKo,230
97
- signalwire_agents/skills/math/skill.py,sha256=5sErd5x1rFHJg2GlmdJB3LvrmvTNOrZsA2jRnG67Zw8,3342
97
+ signalwire_agents/skills/math/skill.py,sha256=-h0DRX_nFkeSzLfaiOKv0zHScdXiQuz4rkLShanKwmc,3800
98
98
  signalwire_agents/skills/mcp_gateway/README.md,sha256=t-71TTWlEvjgWLTcT3v4kMw9zlrKXTAC_sCjb1haNew,5826
99
99
  signalwire_agents/skills/mcp_gateway/__init__.py,sha256=zLgOa7s0sIQphTNJjvasIAW7llxAApez7moC_e1tzP0,236
100
100
  signalwire_agents/skills/mcp_gateway/skill.py,sha256=iS5ET9jYKnwuYsuFd-aqUJfLUhvX--RG2FfCq8BZ340,14050
@@ -114,7 +114,7 @@ signalwire_agents/skills/weather_api/__init__.py,sha256=WCS--GFBX8straIZPuGAmTDZ
114
114
  signalwire_agents/skills/weather_api/skill.py,sha256=-0gFgTVau2Pxljq9_fmNDnzgUI0JBXtdOCIAdHc8fLs,6217
115
115
  signalwire_agents/skills/web_search/README.md,sha256=Y95cxEScMzhmslUJF8u_Nh15FbEBuus4P-E8_kk2an0,5438
116
116
  signalwire_agents/skills/web_search/__init__.py,sha256=kv4CzmF1lldRZcL_HivieslP7gtTFvxcfprKG4n6b-Q,236
117
- signalwire_agents/skills/web_search/skill.py,sha256=6EwoNABxEH5UkEdXsPT72PQzoVlFUbWsFJR6NuyhglI,10363
117
+ signalwire_agents/skills/web_search/skill.py,sha256=EGu6ff9aAb2W323_XDCcVDW1wbAKTZYK8HQOT__iqtE,12660
118
118
  signalwire_agents/skills/wikipedia_search/README.md,sha256=KFIQ8XhqrTG8NRs72dIbjJacy2DlYEXLtxgy23gyRi4,7585
119
119
  signalwire_agents/skills/wikipedia_search/__init__.py,sha256=yJ6iYTSyJC96mwwUsI_FneFhDBcLYD4xEerBKlWLTb8,375
120
120
  signalwire_agents/skills/wikipedia_search/skill.py,sha256=8HhV8E4yDMe-p3qCxgEYFrL5hNvnJ6KbQzYumpgI7-k,7068
@@ -123,9 +123,9 @@ signalwire_agents/utils/pom_utils.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_Pu
123
123
  signalwire_agents/utils/schema_utils.py,sha256=i4okv_O9bUApwT_jJf4Yoij3bLCrGrW3DC-vzSy2RuY,16392
124
124
  signalwire_agents/utils/token_generators.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_PumJ35Xds,191
125
125
  signalwire_agents/utils/validators.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_PumJ35Xds,191
126
- signalwire_agents-0.1.35.dist-info/licenses/LICENSE,sha256=NYvAsB-rTcSvG9cqHt9EUHAWLiA9YzM4Qfz-mPdvDR0,1067
127
- signalwire_agents-0.1.35.dist-info/METADATA,sha256=_w0Y0_7KAZZ75PXQM5KFYgsOvG5n5P3C57p9tGrBHfE,38377
128
- signalwire_agents-0.1.35.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
129
- signalwire_agents-0.1.35.dist-info/entry_points.txt,sha256=ZDT65zfTO_YyDzi_hwQbCxIhrUfu_t8RpNXMMXlUPWI,144
130
- signalwire_agents-0.1.35.dist-info/top_level.txt,sha256=kDGS6ZYv84K9P5Kyg9_S8P_pbUXoHkso0On_DB5bbWc,18
131
- signalwire_agents-0.1.35.dist-info/RECORD,,
126
+ signalwire_agents-0.1.37.dist-info/licenses/LICENSE,sha256=NYvAsB-rTcSvG9cqHt9EUHAWLiA9YzM4Qfz-mPdvDR0,1067
127
+ signalwire_agents-0.1.37.dist-info/METADATA,sha256=7LjR6KSokIXEFD4Fj4ckOEd4_zxJl1oWRbsew1s6Gv0,38983
128
+ signalwire_agents-0.1.37.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
129
+ signalwire_agents-0.1.37.dist-info/entry_points.txt,sha256=ZDT65zfTO_YyDzi_hwQbCxIhrUfu_t8RpNXMMXlUPWI,144
130
+ signalwire_agents-0.1.37.dist-info/top_level.txt,sha256=kDGS6ZYv84K9P5Kyg9_S8P_pbUXoHkso0On_DB5bbWc,18
131
+ signalwire_agents-0.1.37.dist-info/RECORD,,