signalwire-agents 0.1.9__py3-none-any.whl → 0.1.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- signalwire_agents/__init__.py +39 -4
- signalwire_agents/agent_server.py +46 -2
- signalwire_agents/cli/__init__.py +9 -0
- signalwire_agents/cli/test_swaig.py +2545 -0
- signalwire_agents/core/agent_base.py +691 -82
- signalwire_agents/core/contexts.py +289 -0
- signalwire_agents/core/data_map.py +499 -0
- signalwire_agents/core/function_result.py +57 -10
- signalwire_agents/core/skill_base.py +31 -1
- signalwire_agents/core/skill_manager.py +89 -23
- signalwire_agents/core/swaig_function.py +13 -1
- signalwire_agents/core/swml_handler.py +37 -13
- signalwire_agents/core/swml_service.py +37 -28
- signalwire_agents/skills/datasphere/__init__.py +12 -0
- signalwire_agents/skills/datasphere/skill.py +229 -0
- signalwire_agents/skills/datasphere_serverless/__init__.py +1 -0
- signalwire_agents/skills/datasphere_serverless/skill.py +156 -0
- signalwire_agents/skills/datetime/skill.py +7 -3
- signalwire_agents/skills/joke/__init__.py +1 -0
- signalwire_agents/skills/joke/skill.py +88 -0
- signalwire_agents/skills/math/skill.py +8 -5
- signalwire_agents/skills/registry.py +23 -4
- signalwire_agents/skills/web_search/skill.py +58 -33
- signalwire_agents/skills/wikipedia/__init__.py +9 -0
- signalwire_agents/skills/wikipedia/skill.py +180 -0
- signalwire_agents/utils/__init__.py +2 -0
- signalwire_agents/utils/schema_utils.py +111 -44
- signalwire_agents/utils/serverless.py +38 -0
- signalwire_agents-0.1.11.dist-info/METADATA +756 -0
- signalwire_agents-0.1.11.dist-info/RECORD +58 -0
- {signalwire_agents-0.1.9.dist-info → signalwire_agents-0.1.11.dist-info}/WHEEL +1 -1
- signalwire_agents-0.1.11.dist-info/entry_points.txt +2 -0
- signalwire_agents-0.1.9.dist-info/METADATA +0 -311
- signalwire_agents-0.1.9.dist-info/RECORD +0 -44
- {signalwire_agents-0.1.9.data → signalwire_agents-0.1.11.data}/data/schema.json +0 -0
- {signalwire_agents-0.1.9.dist-info → signalwire_agents-0.1.11.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.9.dist-info → signalwire_agents-0.1.11.dist-info}/top_level.txt +0 -0
@@ -31,10 +31,6 @@ class SkillManager:
|
|
31
31
|
Returns:
|
32
32
|
tuple: (success, error_message) - error_message is empty string if successful
|
33
33
|
"""
|
34
|
-
if skill_name in self.loaded_skills:
|
35
|
-
self.logger.warning(f"Skill '{skill_name}' is already loaded")
|
36
|
-
return True, ""
|
37
|
-
|
38
34
|
# Get skill class from registry if not provided
|
39
35
|
if skill_class is None:
|
40
36
|
try:
|
@@ -50,8 +46,21 @@ class SkillManager:
|
|
50
46
|
return False, error_msg
|
51
47
|
|
52
48
|
try:
|
53
|
-
# Create skill instance with parameters
|
49
|
+
# Create skill instance with parameters to get the instance key
|
54
50
|
skill_instance = skill_class(self.agent, params)
|
51
|
+
instance_key = skill_instance.get_instance_key()
|
52
|
+
|
53
|
+
# Check if this instance is already loaded
|
54
|
+
if instance_key in self.loaded_skills:
|
55
|
+
# For single-instance skills, this is an error
|
56
|
+
if not skill_instance.SUPPORTS_MULTIPLE_INSTANCES:
|
57
|
+
error_msg = f"Skill '{skill_name}' is already loaded and does not support multiple instances"
|
58
|
+
self.logger.error(error_msg)
|
59
|
+
return False, error_msg
|
60
|
+
else:
|
61
|
+
# For multi-instance skills, just warn and return success
|
62
|
+
self.logger.warning(f"Skill instance '{instance_key}' is already loaded")
|
63
|
+
return True, ""
|
55
64
|
|
56
65
|
# Validate environment variables with specific error details
|
57
66
|
import os
|
@@ -97,9 +106,9 @@ class SkillManager:
|
|
97
106
|
for section in prompt_sections:
|
98
107
|
self.agent.prompt_add_section(**section)
|
99
108
|
|
100
|
-
# Store loaded skill
|
101
|
-
self.loaded_skills[
|
102
|
-
self.logger.info(f"Successfully loaded skill '{skill_name}'")
|
109
|
+
# Store loaded skill using instance key
|
110
|
+
self.loaded_skills[instance_key] = skill_instance
|
111
|
+
self.logger.info(f"Successfully loaded skill instance '{instance_key}' (skill: '{skill_name}')")
|
103
112
|
return True, ""
|
104
113
|
|
105
114
|
except Exception as e:
|
@@ -107,30 +116,87 @@ class SkillManager:
|
|
107
116
|
self.logger.error(error_msg)
|
108
117
|
return False, error_msg
|
109
118
|
|
110
|
-
def unload_skill(self,
|
111
|
-
"""
|
112
|
-
|
113
|
-
|
119
|
+
def unload_skill(self, skill_identifier: str) -> bool:
|
120
|
+
"""
|
121
|
+
Unload a skill and cleanup
|
122
|
+
|
123
|
+
Args:
|
124
|
+
skill_identifier: Either a skill name or an instance key
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
bool: True if successfully unloaded, False otherwise
|
128
|
+
"""
|
129
|
+
# Try to find the skill by identifier (could be skill name or instance key)
|
130
|
+
skill_instance = None
|
131
|
+
instance_key = None
|
132
|
+
|
133
|
+
# First try as direct instance key
|
134
|
+
if skill_identifier in self.loaded_skills:
|
135
|
+
instance_key = skill_identifier
|
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
|
+
|
145
|
+
if skill_instance is None:
|
146
|
+
self.logger.warning(f"Skill '{skill_identifier}' is not loaded")
|
114
147
|
return False
|
115
148
|
|
116
149
|
try:
|
117
|
-
skill_instance = self.loaded_skills[skill_name]
|
118
150
|
skill_instance.cleanup()
|
119
|
-
del self.loaded_skills[
|
120
|
-
self.logger.info(f"Successfully unloaded skill '{
|
151
|
+
del self.loaded_skills[instance_key]
|
152
|
+
self.logger.info(f"Successfully unloaded skill instance '{instance_key}'")
|
121
153
|
return True
|
122
154
|
except Exception as e:
|
123
|
-
self.logger.error(f"Error unloading skill '{
|
155
|
+
self.logger.error(f"Error unloading skill '{skill_identifier}': {e}")
|
124
156
|
return False
|
125
157
|
|
126
158
|
def list_loaded_skills(self) -> List[str]:
|
127
|
-
"""List
|
159
|
+
"""List instance keys of currently loaded skills"""
|
128
160
|
return list(self.loaded_skills.keys())
|
129
161
|
|
130
|
-
def has_skill(self,
|
131
|
-
"""
|
132
|
-
|
162
|
+
def has_skill(self, skill_identifier: str) -> bool:
|
163
|
+
"""
|
164
|
+
Check if skill is currently loaded
|
165
|
+
|
166
|
+
Args:
|
167
|
+
skill_identifier: Either a skill name or an instance key
|
168
|
+
|
169
|
+
Returns:
|
170
|
+
bool: True if loaded, False otherwise
|
171
|
+
"""
|
172
|
+
# First try as direct instance key
|
173
|
+
if skill_identifier in self.loaded_skills:
|
174
|
+
return True
|
175
|
+
|
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
|
+
return False
|
133
182
|
|
134
|
-
def get_skill(self,
|
135
|
-
"""
|
136
|
-
|
183
|
+
def get_skill(self, skill_identifier: str) -> Optional[SkillBase]:
|
184
|
+
"""
|
185
|
+
Get a loaded skill instance by identifier
|
186
|
+
|
187
|
+
Args:
|
188
|
+
skill_identifier: Either a skill name or an instance key
|
189
|
+
|
190
|
+
Returns:
|
191
|
+
SkillBase: The skill instance if found, None otherwise
|
192
|
+
"""
|
193
|
+
# First try as direct instance key
|
194
|
+
if skill_identifier in self.loaded_skills:
|
195
|
+
return self.loaded_skills[skill_identifier]
|
196
|
+
|
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
|
+
return None
|
@@ -27,7 +27,9 @@ class SWAIGFunction:
|
|
27
27
|
description: str,
|
28
28
|
parameters: Dict[str, Dict] = None,
|
29
29
|
secure: bool = False,
|
30
|
-
fillers: Optional[Dict[str, List[str]]] = None
|
30
|
+
fillers: Optional[Dict[str, List[str]]] = None,
|
31
|
+
webhook_url: Optional[str] = None,
|
32
|
+
**extra_swaig_fields
|
31
33
|
):
|
32
34
|
"""
|
33
35
|
Initialize a new SWAIG function
|
@@ -39,6 +41,8 @@ class SWAIGFunction:
|
|
39
41
|
parameters: Dictionary of parameters, keys are parameter names, values are param definitions
|
40
42
|
secure: Whether this function requires token validation
|
41
43
|
fillers: Optional dictionary of filler phrases by language code
|
44
|
+
webhook_url: Optional external webhook URL to use instead of local handling
|
45
|
+
**extra_swaig_fields: Additional SWAIG fields to include in function definition
|
42
46
|
"""
|
43
47
|
self.name = name
|
44
48
|
self.handler = handler
|
@@ -46,6 +50,11 @@ class SWAIGFunction:
|
|
46
50
|
self.parameters = parameters or {}
|
47
51
|
self.secure = secure
|
48
52
|
self.fillers = fillers
|
53
|
+
self.webhook_url = webhook_url
|
54
|
+
self.extra_swaig_fields = extra_swaig_fields
|
55
|
+
|
56
|
+
# Mark as external if webhook_url is provided
|
57
|
+
self.is_external = webhook_url is not None
|
49
58
|
|
50
59
|
def _ensure_parameter_structure(self) -> Dict:
|
51
60
|
"""
|
@@ -165,6 +174,9 @@ class SWAIGFunction:
|
|
165
174
|
# Add fillers if provided
|
166
175
|
if self.fillers and len(self.fillers) > 0:
|
167
176
|
function_def["fillers"] = self.fillers
|
177
|
+
|
178
|
+
# Add any extra SWAIG fields
|
179
|
+
function_def.update(self.extra_swaig_fields)
|
168
180
|
|
169
181
|
return function_def
|
170
182
|
|
@@ -93,17 +93,33 @@ class AIVerbHandler(SWMLVerbHandler):
|
|
93
93
|
"""
|
94
94
|
errors = []
|
95
95
|
|
96
|
-
# Check
|
96
|
+
# Check that prompt is present
|
97
97
|
if "prompt" not in config:
|
98
98
|
errors.append("Missing required field 'prompt'")
|
99
|
+
return False, errors
|
99
100
|
|
100
|
-
|
101
|
-
if
|
102
|
-
prompt
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
101
|
+
prompt = config["prompt"]
|
102
|
+
if not isinstance(prompt, dict):
|
103
|
+
errors.append("'prompt' must be an object")
|
104
|
+
return False, errors
|
105
|
+
|
106
|
+
# Check that prompt contains one of: text, pom, or contexts
|
107
|
+
has_text = "text" in prompt
|
108
|
+
has_pom = "pom" in prompt
|
109
|
+
has_contexts = "contexts" in prompt
|
110
|
+
|
111
|
+
options_count = sum([has_text, has_pom, has_contexts])
|
112
|
+
|
113
|
+
if options_count == 0:
|
114
|
+
errors.append("'prompt' must contain one of: 'text', 'pom', or 'contexts'")
|
115
|
+
elif options_count > 1:
|
116
|
+
errors.append("'prompt' can only contain one of: 'text', 'pom', or 'contexts'")
|
117
|
+
|
118
|
+
# Validate contexts structure if present
|
119
|
+
if has_contexts:
|
120
|
+
contexts = prompt["contexts"]
|
121
|
+
if not isinstance(contexts, dict):
|
122
|
+
errors.append("'prompt.contexts' must be an object")
|
107
123
|
|
108
124
|
# Validate SWAIG structure if present
|
109
125
|
if "SWAIG" in config:
|
@@ -116,6 +132,7 @@ class AIVerbHandler(SWMLVerbHandler):
|
|
116
132
|
def build_config(self,
|
117
133
|
prompt_text: Optional[str] = None,
|
118
134
|
prompt_pom: Optional[List[Dict[str, Any]]] = None,
|
135
|
+
contexts: Optional[Dict[str, Any]] = None,
|
119
136
|
post_prompt: Optional[str] = None,
|
120
137
|
post_prompt_url: Optional[str] = None,
|
121
138
|
swaig: Optional[Dict[str, Any]] = None,
|
@@ -124,8 +141,9 @@ class AIVerbHandler(SWMLVerbHandler):
|
|
124
141
|
Build a configuration for the AI verb
|
125
142
|
|
126
143
|
Args:
|
127
|
-
prompt_text: Text prompt for the AI (mutually exclusive with prompt_pom)
|
128
|
-
prompt_pom: POM structure for the AI prompt (mutually exclusive with prompt_text)
|
144
|
+
prompt_text: Text prompt for the AI (mutually exclusive with prompt_pom and contexts)
|
145
|
+
prompt_pom: POM structure for the AI prompt (mutually exclusive with prompt_text and contexts)
|
146
|
+
contexts: Contexts and steps configuration (mutually exclusive with prompt_text and prompt_pom)
|
129
147
|
post_prompt: Optional post-prompt text
|
130
148
|
post_prompt_url: Optional URL for post-prompt processing
|
131
149
|
swaig: Optional SWAIG configuration
|
@@ -136,13 +154,19 @@ class AIVerbHandler(SWMLVerbHandler):
|
|
136
154
|
"""
|
137
155
|
config = {}
|
138
156
|
|
139
|
-
# Add prompt (either text or
|
157
|
+
# Add prompt (either text, POM, or contexts - mutually exclusive)
|
158
|
+
prompt_options_count = sum(x is not None for x in [prompt_text, prompt_pom, contexts])
|
159
|
+
if prompt_options_count == 0:
|
160
|
+
raise ValueError("One of prompt_text, prompt_pom, or contexts must be provided")
|
161
|
+
elif prompt_options_count > 1:
|
162
|
+
raise ValueError("prompt_text, prompt_pom, and contexts are mutually exclusive")
|
163
|
+
|
140
164
|
if prompt_text is not None:
|
141
165
|
config["prompt"] = {"text": prompt_text}
|
142
166
|
elif prompt_pom is not None:
|
143
167
|
config["prompt"] = {"pom": prompt_pom}
|
144
|
-
|
145
|
-
|
168
|
+
elif contexts is not None:
|
169
|
+
config["prompt"] = {"contexts": contexts}
|
146
170
|
|
147
171
|
# Add post-prompt if provided
|
148
172
|
if post_prompt is not None:
|
@@ -1,3 +1,4 @@
|
|
1
|
+
#!/usr/bin/env python3
|
1
2
|
"""
|
2
3
|
Copyright (c) 2025 SignalWire
|
3
4
|
|
@@ -7,11 +8,9 @@ Licensed under the MIT License.
|
|
7
8
|
See LICENSE file in the project root for full license information.
|
8
9
|
"""
|
9
10
|
|
11
|
+
# -*- coding: utf-8 -*-
|
10
12
|
"""
|
11
|
-
|
12
|
-
|
13
|
-
This class provides the foundation for creating and serving SWML documents.
|
14
|
-
It handles schema validation, document creation, and web service functionality.
|
13
|
+
Base SWML Service for SignalWire Agents
|
15
14
|
"""
|
16
15
|
|
17
16
|
import os
|
@@ -37,11 +36,11 @@ try:
|
|
37
36
|
structlog.stdlib.add_logger_name,
|
38
37
|
structlog.stdlib.add_log_level,
|
39
38
|
structlog.stdlib.PositionalArgumentsFormatter(),
|
40
|
-
structlog.processors.TimeStamper(fmt="
|
39
|
+
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S"),
|
41
40
|
structlog.processors.StackInfoRenderer(),
|
42
41
|
structlog.processors.format_exc_info,
|
43
42
|
structlog.processors.UnicodeDecoder(),
|
44
|
-
structlog.
|
43
|
+
structlog.dev.ConsoleRenderer()
|
45
44
|
],
|
46
45
|
context_class=dict,
|
47
46
|
logger_factory=structlog.stdlib.LoggerFactory(),
|
@@ -188,17 +187,21 @@ class SWMLService:
|
|
188
187
|
"""
|
189
188
|
Create auto-vivified methods for all verbs at initialization time
|
190
189
|
"""
|
191
|
-
|
190
|
+
self.log.debug("creating_verb_methods")
|
192
191
|
|
193
192
|
# Get all verb names from the schema
|
193
|
+
if not self.schema_utils:
|
194
|
+
self.log.warning("no_schema_utils_available")
|
195
|
+
return
|
196
|
+
|
194
197
|
verb_names = self.schema_utils.get_all_verb_names()
|
195
|
-
|
198
|
+
self.log.debug("found_verbs_in_schema", count=len(verb_names))
|
196
199
|
|
197
200
|
# Create a method for each verb
|
198
201
|
for verb_name in verb_names:
|
199
202
|
# Skip verbs that already have specific methods
|
200
203
|
if hasattr(self, verb_name):
|
201
|
-
|
204
|
+
self.log.debug("skipping_verb_has_method", verb=verb_name)
|
202
205
|
continue
|
203
206
|
|
204
207
|
# Handle sleep verb specially since it takes an integer directly
|
@@ -210,7 +213,7 @@ class SWMLService:
|
|
210
213
|
Args:
|
211
214
|
duration: The amount of time to sleep in milliseconds
|
212
215
|
"""
|
213
|
-
|
216
|
+
self.log.debug("executing_sleep_verb", duration=duration)
|
214
217
|
# Sleep verb takes a direct integer parameter in SWML
|
215
218
|
if duration is not None:
|
216
219
|
return self_instance.add_verb("sleep", duration)
|
@@ -226,7 +229,7 @@ class SWMLService:
|
|
226
229
|
# Also cache it for later
|
227
230
|
self._verb_methods_cache[verb_name] = sleep_method
|
228
231
|
|
229
|
-
|
232
|
+
self.log.debug("created_special_method", verb=verb_name)
|
230
233
|
continue
|
231
234
|
|
232
235
|
# Generate the method implementation for normal verbs
|
@@ -235,7 +238,7 @@ class SWMLService:
|
|
235
238
|
"""
|
236
239
|
Dynamically generated method for SWML verb
|
237
240
|
"""
|
238
|
-
|
241
|
+
self.log.debug("executing_verb_method", verb=name, kwargs_count=len(kwargs))
|
239
242
|
config = {}
|
240
243
|
for key, value in kwargs.items():
|
241
244
|
if value is not None:
|
@@ -260,7 +263,7 @@ class SWMLService:
|
|
260
263
|
# Also cache it for later
|
261
264
|
self._verb_methods_cache[verb_name] = method
|
262
265
|
|
263
|
-
|
266
|
+
self.log.debug("created_verb_method", verb=verb_name)
|
264
267
|
|
265
268
|
def __getattr__(self, name: str) -> Any:
|
266
269
|
"""
|
@@ -280,21 +283,26 @@ class SWMLService:
|
|
280
283
|
Raises:
|
281
284
|
AttributeError: If name is not a valid SWML verb
|
282
285
|
"""
|
283
|
-
|
286
|
+
self.log.debug("getattr_called", attribute=name)
|
284
287
|
|
285
288
|
# Simple version to match our test script
|
286
289
|
# First check if this is a valid SWML verb
|
290
|
+
if not self.schema_utils:
|
291
|
+
msg = f"'{self.__class__.__name__}' object has no attribute '{name}' (no schema available)"
|
292
|
+
self.log.debug("getattr_no_schema", attribute=name)
|
293
|
+
raise AttributeError(msg)
|
294
|
+
|
287
295
|
verb_names = self.schema_utils.get_all_verb_names()
|
288
296
|
|
289
297
|
if name in verb_names:
|
290
|
-
|
298
|
+
self.log.debug("getattr_valid_verb", verb=name)
|
291
299
|
|
292
300
|
# Check if we already have this method in the cache
|
293
301
|
if not hasattr(self, '_verb_methods_cache'):
|
294
302
|
self._verb_methods_cache = {}
|
295
303
|
|
296
304
|
if name in self._verb_methods_cache:
|
297
|
-
|
305
|
+
self.log.debug("getattr_cached_method", verb=name)
|
298
306
|
return types.MethodType(self._verb_methods_cache[name], self)
|
299
307
|
|
300
308
|
# Handle sleep verb specially since it takes an integer directly
|
@@ -306,7 +314,7 @@ class SWMLService:
|
|
306
314
|
Args:
|
307
315
|
duration: The amount of time to sleep in milliseconds
|
308
316
|
"""
|
309
|
-
|
317
|
+
self.log.debug("executing_sleep_method", duration=duration)
|
310
318
|
# Sleep verb takes a direct integer parameter in SWML
|
311
319
|
if duration is not None:
|
312
320
|
return self_instance.add_verb("sleep", duration)
|
@@ -317,7 +325,7 @@ class SWMLService:
|
|
317
325
|
raise TypeError("sleep() missing required argument: 'duration'")
|
318
326
|
|
319
327
|
# Cache the method for future use
|
320
|
-
|
328
|
+
self.log.debug("caching_sleep_method", verb=name)
|
321
329
|
self._verb_methods_cache[name] = sleep_method
|
322
330
|
|
323
331
|
# Return the bound method
|
@@ -328,7 +336,7 @@ class SWMLService:
|
|
328
336
|
"""
|
329
337
|
Dynamically generated method for SWML verb
|
330
338
|
"""
|
331
|
-
|
339
|
+
self.log.debug("executing_dynamic_verb", verb=name, kwargs_count=len(kwargs))
|
332
340
|
config = {}
|
333
341
|
for key, value in kwargs.items():
|
334
342
|
if value is not None:
|
@@ -343,7 +351,7 @@ class SWMLService:
|
|
343
351
|
verb_method.__doc__ = f"Add the {name} verb to the document."
|
344
352
|
|
345
353
|
# Cache the method for future use
|
346
|
-
|
354
|
+
self.log.debug("caching_verb_method", verb=name)
|
347
355
|
self._verb_methods_cache[name] = verb_method
|
348
356
|
|
349
357
|
# Return the bound method
|
@@ -351,7 +359,7 @@ class SWMLService:
|
|
351
359
|
|
352
360
|
# Not a valid verb
|
353
361
|
msg = f"'{self.__class__.__name__}' object has no attribute '{name}'"
|
354
|
-
|
362
|
+
self.log.debug("getattr_invalid_attribute", attribute=name, error=msg)
|
355
363
|
raise AttributeError(msg)
|
356
364
|
|
357
365
|
def _find_schema_path(self) -> Optional[str]:
|
@@ -823,7 +831,7 @@ class SWMLService:
|
|
823
831
|
route_with_slash = route_path + "/"
|
824
832
|
|
825
833
|
# Log the incoming path for debugging
|
826
|
-
|
834
|
+
self.log.debug("catch_all_route_hit", path=full_path, route=route_path)
|
827
835
|
|
828
836
|
# Check for exact match to our route (without trailing slash)
|
829
837
|
if full_path == route_path:
|
@@ -851,21 +859,21 @@ class SWMLService:
|
|
851
859
|
return await self._handle_request(request, response)
|
852
860
|
|
853
861
|
# Not our route or not matching our patterns
|
854
|
-
|
862
|
+
self.log.debug("no_route_match", path=full_path)
|
855
863
|
return {"error": "Path not found"}
|
856
864
|
|
857
|
-
#
|
858
|
-
|
865
|
+
# Log all routes for debugging
|
866
|
+
self.log.debug("registered_routes", service=self.name)
|
859
867
|
for route in app.routes:
|
860
868
|
if hasattr(route, "path"):
|
861
|
-
|
869
|
+
self.log.debug("route_registered", path=route.path)
|
862
870
|
|
863
871
|
self._app = app
|
864
872
|
|
865
873
|
host = host or self.host
|
866
874
|
port = port or self.port
|
867
875
|
|
868
|
-
#
|
876
|
+
# Get the auth credentials
|
869
877
|
username, password = self._basic_auth
|
870
878
|
|
871
879
|
# Use correct protocol and host in displayed URL
|
@@ -878,12 +886,13 @@ class SWMLService:
|
|
878
886
|
username=username,
|
879
887
|
password_length=len(password))
|
880
888
|
|
889
|
+
# Print user-friendly startup message (keep for UX)
|
881
890
|
print(f"Service '{self.name}' is available at:")
|
882
891
|
print(f"URL: {protocol}://{display_host}{self.route}")
|
883
892
|
print(f"URL with trailing slash: {protocol}://{display_host}{self.route}/")
|
884
893
|
print(f"Basic Auth: {username}:{password}")
|
885
894
|
|
886
|
-
# Check if SIP routing is enabled and
|
895
|
+
# Check if SIP routing is enabled and log additional info
|
887
896
|
if self._routing_callbacks:
|
888
897
|
print(f"Callback endpoints:")
|
889
898
|
for path in self._routing_callbacks:
|
@@ -0,0 +1,12 @@
|
|
1
|
+
"""
|
2
|
+
Copyright (c) 2025 SignalWire
|
3
|
+
|
4
|
+
This file is part of the SignalWire AI Agents SDK.
|
5
|
+
|
6
|
+
Licensed under the MIT License.
|
7
|
+
See LICENSE file in the project root for full license information.
|
8
|
+
"""
|
9
|
+
|
10
|
+
from .skill import DataSphereSkill
|
11
|
+
|
12
|
+
__all__ = ['DataSphereSkill']
|