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
@@ -8,12 +8,11 @@ See LICENSE file in the project root for full license information.
8
8
  """
9
9
 
10
10
  """
11
- SwmlRenderer for generating complete SWML documents for SignalWire AI Agents
11
+ SWML document rendering utilities for SignalWire AI Agents.
12
12
  """
13
13
 
14
- from typing import Dict, List, Any, Optional, Union
15
14
  import json
16
- import yaml
15
+ from typing import Dict, List, Any, Optional, Union
17
16
 
18
17
  from signalwire_agents.core.swml_service import SWMLService
19
18
  from signalwire_agents.core.swml_builder import SWMLBuilder
@@ -23,14 +22,13 @@ class SwmlRenderer:
23
22
  """
24
23
  Renders SWML documents for SignalWire AI Agents with AI and SWAIG components
25
24
 
26
- This class provides backward-compatible methods for rendering SWML documents
27
- while also supporting the new SWMLService architecture. It can work either
28
- standalone (legacy mode) or with a SWMLService instance.
25
+ This class provides methods for rendering SWML documents using the SWMLService architecture.
29
26
  """
30
27
 
31
28
  @staticmethod
32
29
  def render_swml(
33
30
  prompt: Union[str, List[Dict[str, Any]]],
31
+ service: SWMLService,
34
32
  post_prompt: Optional[str] = None,
35
33
  post_prompt_url: Optional[str] = None,
36
34
  swaig_functions: Optional[List[Dict[str, Any]]] = None,
@@ -43,341 +41,154 @@ class SwmlRenderer:
43
41
  record_format: str = "mp4",
44
42
  record_stereo: bool = True,
45
43
  format: str = "json",
46
- default_webhook_url: Optional[str] = None,
47
- service: Optional[SWMLService] = None
44
+ default_webhook_url: Optional[str] = None
48
45
  ) -> str:
49
46
  """
50
47
  Generate a complete SWML document with AI configuration
51
48
 
52
49
  Args:
53
- prompt: Either a string prompt or a POM in list-of-dict format
54
- post_prompt: Optional post-prompt text (for summary)
55
- post_prompt_url: URL to receive the post-prompt result
50
+ prompt: AI prompt text or POM structure
51
+ service: SWMLService instance to use for document building
52
+ post_prompt: Optional post-prompt text
53
+ post_prompt_url: Optional post-prompt URL
56
54
  swaig_functions: List of SWAIG function definitions
57
- startup_hook_url: URL for startup hook
58
- hangup_hook_url: URL for hangup hook
59
- prompt_is_pom: Whether prompt is a POM object or raw text
60
- params: Additional AI params (temperature, etc)
61
- add_answer: Whether to auto-add the answer block after AI
62
- record_call: Whether to add a record_call block
63
- record_format: Format for recording the call
55
+ startup_hook_url: Optional startup hook URL
56
+ hangup_hook_url: Optional hangup hook URL
57
+ prompt_is_pom: Whether prompt is POM format
58
+ params: Additional AI verb parameters
59
+ add_answer: Whether to add answer verb
60
+ record_call: Whether to add record_call verb
61
+ record_format: Recording format
64
62
  record_stereo: Whether to record in stereo
65
- format: Output format, 'json' or 'yaml'
66
- default_webhook_url: Optional default webhook URL for all SWAIG functions
67
- service: Optional SWMLService instance to use
63
+ format: Output format (json or yaml)
64
+ default_webhook_url: Default webhook URL for SWAIG functions
68
65
 
69
66
  Returns:
70
67
  SWML document as a string
71
68
  """
72
- # If we have a service, use it to build the document
73
- if service:
74
- # Create a builder for the service
75
- builder = SWMLBuilder(service)
76
-
77
- # Reset the document to start fresh
78
- builder.reset()
79
-
80
- # Add answer block if requested
81
- if add_answer:
82
- builder.answer()
83
-
84
- # Add record_call if requested
85
- if record_call:
86
- # TODO: Add record_call to builder API
87
- service.add_verb("record_call", {
88
- "format": record_format,
89
- "stereo": record_stereo
90
- })
91
-
92
- # Configure SWAIG object for AI verb
93
- swaig_config = {}
94
- functions = []
95
-
96
- # Add startup hook if provided
97
- if startup_hook_url:
98
- functions.append({
99
- "function": "startup_hook",
100
- "description": "Called when the call starts",
101
- "parameters": {
102
- "type": "object",
103
- "properties": {}
104
- },
105
- "web_hook_url": startup_hook_url
106
- })
107
-
108
- # Add hangup hook if provided
109
- if hangup_hook_url:
110
- functions.append({
111
- "function": "hangup_hook",
112
- "description": "Called when the call ends",
113
- "parameters": {
114
- "type": "object",
115
- "properties": {}
116
- },
117
- "web_hook_url": hangup_hook_url
118
- })
119
-
120
- # Add regular functions if provided
121
- if swaig_functions:
122
- for func in swaig_functions:
123
- # Skip special hooks as we've already added them
124
- if func.get("function") not in ["startup_hook", "hangup_hook"]:
125
- functions.append(func)
126
-
127
- # Only add SWAIG if we have functions or a default URL
128
- if functions or default_webhook_url:
129
- swaig_config = {}
130
-
131
- # Add defaults if we have a default webhook URL
132
- if default_webhook_url:
133
- swaig_config["defaults"] = {
134
- "web_hook_url": default_webhook_url
135
- }
136
-
137
- # Add functions if we have any
138
- if functions:
139
- swaig_config["functions"] = functions
140
-
141
- # Add AI verb with appropriate configuration
142
- builder.ai(
143
- prompt_text=None if prompt_is_pom else prompt,
144
- prompt_pom=prompt if prompt_is_pom else None,
145
- post_prompt=post_prompt,
146
- post_prompt_url=post_prompt_url,
147
- swaig=swaig_config if swaig_config else None,
148
- **(params or {})
149
- )
150
-
151
- # Get the document as a dictionary or string based on format
152
- if format.lower() == "yaml":
153
- import yaml
154
- return yaml.dump(builder.build(), sort_keys=False)
155
- else:
156
- return builder.render()
157
- else:
158
- # Legacy implementation (unchanged for backward compatibility)
159
- # Start building the SWML document
160
- swml = {
161
- "version": "1.0.0",
162
- "sections": {
163
- "main": []
164
- }
165
- }
166
-
167
- # Build the AI block
168
- ai_block = {
169
- "ai": {
170
- "prompt": {}
171
- }
172
- }
173
-
174
- # Set prompt based on type
175
- if prompt_is_pom:
176
- ai_block["ai"]["prompt"]["pom"] = prompt
177
- else:
178
- ai_block["ai"]["prompt"]["text"] = prompt
179
-
180
- # Add post_prompt if provided
181
- if post_prompt:
182
- ai_block["ai"]["post_prompt"] = {
183
- "text": post_prompt
184
- }
185
-
186
- # Add post_prompt_url if provided
187
- if post_prompt_url:
188
- ai_block["ai"]["post_prompt_url"] = post_prompt_url
189
-
190
- # SWAIG is a dictionary not an array (fix from old implementation)
191
- ai_block["ai"]["SWAIG"] = {}
192
-
69
+ # Use the service to build the document
70
+ builder = SWMLBuilder(service)
71
+
72
+ # Reset the document to start fresh
73
+ builder.reset()
74
+
75
+ # Add answer block if requested
76
+ if add_answer:
77
+ builder.answer()
78
+
79
+ # Add record_call if requested
80
+ if record_call:
81
+ service.add_verb("record_call", {
82
+ "format": record_format,
83
+ "stereo": record_stereo
84
+ })
85
+
86
+ # Configure SWAIG object for AI verb
87
+ swaig_config = {}
88
+ functions = []
89
+
90
+ # Add startup hook if provided
91
+ if startup_hook_url:
92
+ functions.append({
93
+ "function": "startup_hook",
94
+ "description": "Called when the call starts",
95
+ "parameters": {
96
+ "type": "object",
97
+ "properties": {}
98
+ },
99
+ "web_hook_url": startup_hook_url
100
+ })
101
+
102
+ # Add hangup hook if provided
103
+ if hangup_hook_url:
104
+ functions.append({
105
+ "function": "hangup_hook",
106
+ "description": "Called when the call ends",
107
+ "parameters": {
108
+ "type": "object",
109
+ "properties": {}
110
+ },
111
+ "web_hook_url": hangup_hook_url
112
+ })
113
+
114
+ # Add regular functions if provided
115
+ if swaig_functions:
116
+ for func in swaig_functions:
117
+ # Skip special hooks as we've already added them
118
+ if func.get("function") not in ["startup_hook", "hangup_hook"]:
119
+ functions.append(func)
120
+
121
+ # Only add SWAIG if we have functions or a default URL
122
+ if functions or default_webhook_url:
193
123
  # Add defaults if we have a default webhook URL
194
124
  if default_webhook_url:
195
- ai_block["ai"]["SWAIG"]["defaults"] = {
125
+ swaig_config["defaults"] = {
196
126
  "web_hook_url": default_webhook_url
197
127
  }
198
128
 
199
- # Collect all functions
200
- functions = []
201
-
202
- # Add SWAIG hooks if provided
203
- if startup_hook_url:
204
- startup_hook = {
205
- "function": "startup_hook",
206
- "description": "Called when the call starts",
207
- "parameters": {
208
- "type": "object",
209
- "properties": {}
210
- },
211
- "web_hook_url": startup_hook_url
212
- }
213
- functions.append(startup_hook)
214
-
215
- if hangup_hook_url:
216
- hangup_hook = {
217
- "function": "hangup_hook",
218
- "description": "Called when the call ends",
219
- "parameters": {
220
- "type": "object",
221
- "properties": {}
222
- },
223
- "web_hook_url": hangup_hook_url
224
- }
225
- functions.append(hangup_hook)
226
-
227
- # Add regular functions from the provided list
228
- if swaig_functions:
229
- for func in swaig_functions:
230
- # Skip special hooks as we've already added them
231
- if func.get("function") not in ["startup_hook", "hangup_hook"]:
232
- functions.append(func)
233
-
234
- # Add functions to SWAIG if we have any
129
+ # Add functions if we have any
235
130
  if functions:
236
- ai_block["ai"]["SWAIG"]["functions"] = functions
237
-
238
- # Add AI params if provided (but not rendering settings)
239
- if params:
240
- # Filter out non-AI parameters that should be separate SWML methods
241
- ai_params = {k: v for k, v in params.items()
242
- if k not in ["auto_answer", "record_call", "record_format", "record_stereo"]}
243
-
244
- # Only update if we have valid AI parameters
245
- if ai_params:
246
- ai_block["ai"]["params"] = ai_params
247
-
248
- # Start building the SWML blocks
249
- main_blocks = []
250
-
251
- # Add answer block first if requested (to answer the call)
252
- if add_answer:
253
- main_blocks.append({"answer": {}})
254
-
255
- # Add record_call block next if requested
256
- if record_call:
257
- main_blocks.append({
258
- "record_call": {
259
- "format": record_format,
260
- "stereo": record_stereo # SWML expects a boolean not a string
261
- }
262
- })
263
-
264
- # Add the AI block
265
- main_blocks.append(ai_block)
266
-
267
- # Set the main section to our ordered blocks
268
- swml["sections"]["main"] = main_blocks
269
-
270
- # Return in requested format
271
- if format.lower() == "yaml":
272
- import yaml
273
- return yaml.dump(swml, sort_keys=False)
274
- else:
275
- return json.dumps(swml, indent=2)
131
+ swaig_config["functions"] = functions
132
+
133
+ # Add AI verb with appropriate configuration
134
+ builder.ai(
135
+ prompt_text=None if prompt_is_pom else prompt,
136
+ prompt_pom=prompt if prompt_is_pom else None,
137
+ post_prompt=post_prompt,
138
+ post_prompt_url=post_prompt_url,
139
+ swaig=swaig_config if swaig_config else None,
140
+ **(params or {})
141
+ )
142
+
143
+ # Get the document as a dictionary or string based on format
144
+ if format.lower() == "yaml":
145
+ import yaml
146
+ return yaml.dump(builder.build(), sort_keys=False)
147
+ else:
148
+ return builder.render()
276
149
 
277
150
  @staticmethod
278
151
  def render_function_response_swml(
279
152
  response_text: str,
153
+ service: SWMLService,
280
154
  actions: Optional[List[Dict[str, Any]]] = None,
281
- format: str = "json",
282
- service: Optional[SWMLService] = None
155
+ format: str = "json"
283
156
  ) -> str:
284
157
  """
285
158
  Generate a SWML document for a function response
286
159
 
287
160
  Args:
288
- response_text: Text to say/display
289
- actions: List of SWML actions to execute
290
- format: Output format, 'json' or 'yaml'
291
- service: Optional SWMLService instance to use
161
+ response_text: Text response to include in the document
162
+ service: SWMLService instance to use
163
+ actions: Optional list of actions to perform
164
+ format: Output format (json or yaml)
292
165
 
293
166
  Returns:
294
167
  SWML document as a string
295
168
  """
296
- if service:
297
- # Use the service to build the document
298
- service.reset_document()
299
-
300
- # Add a play block for the response if provided
301
- if response_text:
302
- service.add_verb("play", {
303
- "url": f"say:{response_text}"
304
- })
305
-
306
- # Add any actions
307
- if actions:
308
- for action in actions:
309
- # Support both type-based actions and direct SWML verbs
310
- if "type" in action:
311
- # Type-based action format
312
- if action["type"] == "play":
313
- service.add_verb("play", {
314
- "url": action["url"]
315
- })
316
- elif action["type"] == "transfer":
317
- service.add_verb("connect", [
318
- {"to": action["dest"]}
319
- ])
320
- elif action["type"] == "hang_up":
321
- service.add_verb("hangup", {})
322
- # Additional action types could be added here
323
- else:
324
- # Direct SWML verb format
325
- for verb_name, verb_config in action.items():
326
- service.add_verb(verb_name, verb_config)
327
-
328
- # Return in requested format
329
- if format.lower() == "yaml":
330
- import yaml
331
- return yaml.dump(service.get_document(), sort_keys=False)
332
- else:
333
- return service.render_document()
169
+ # Use the service to build the document
170
+ service.reset_document()
171
+
172
+ # Add a play block for the response if provided
173
+ if response_text:
174
+ service.add_verb("play", {"text": response_text})
175
+
176
+ # Add any actions that were provided
177
+ if actions:
178
+ for action in actions:
179
+ if "play" in action:
180
+ service.add_verb("play", action["play"])
181
+ elif "hangup" in action:
182
+ service.add_verb("hangup", action["hangup"])
183
+ elif "transfer" in action:
184
+ service.add_verb("transfer", action["transfer"])
185
+ elif "ai" in action:
186
+ service.add_verb("ai", action["ai"])
187
+ # Add more action types as needed
188
+
189
+ # Get the document as a dictionary or string based on format
190
+ if format.lower() == "yaml":
191
+ import yaml
192
+ return yaml.dump(service.get_document(), sort_keys=False)
334
193
  else:
335
- # Legacy implementation (unchanged for backward compatibility)
336
- swml = {
337
- "version": "1.0.0",
338
- "sections": {
339
- "main": []
340
- }
341
- }
342
-
343
- # Add a play block for the response if provided
344
- if response_text:
345
- swml["sections"]["main"].append({
346
- "play": {
347
- "url": f"say:{response_text}"
348
- }
349
- })
350
-
351
- # Add any actions
352
- if actions:
353
- for action in actions:
354
- # Support both type-based actions and direct SWML verbs
355
- if "type" in action:
356
- # Type-based action format
357
- if action["type"] == "play":
358
- swml["sections"]["main"].append({
359
- "play": {
360
- "url": action["url"]
361
- }
362
- })
363
- elif action["type"] == "transfer":
364
- swml["sections"]["main"].append({
365
- "connect": [
366
- {"to": action["dest"]}
367
- ]
368
- })
369
- elif action["type"] == "hang_up":
370
- swml["sections"]["main"].append({
371
- "hangup": {}
372
- })
373
- # Additional action types could be added here
374
- else:
375
- # Direct SWML verb format - add the action as-is
376
- swml["sections"]["main"].append(action)
377
-
378
- # Return in requested format
379
- if format.lower() == "yaml":
380
- import yaml
381
- return yaml.dump(swml, sort_keys=False)
382
- else:
383
- return json.dumps(swml)
194
+ return service.render_document()
@@ -82,8 +82,13 @@ class SWMLService:
82
82
  self.route = route.rstrip("/") # Ensure no trailing slash
83
83
  self.host = host
84
84
  self.port = port
85
- self.ssl_enabled = False
86
- self.domain = None
85
+
86
+ # Initialize SSL configuration from environment variables
87
+ ssl_enabled_env = os.environ.get('SWML_SSL_ENABLED', '').lower()
88
+ self.ssl_enabled = ssl_enabled_env in ('true', '1', 'yes')
89
+ self.domain = os.environ.get('SWML_DOMAIN')
90
+ self.ssl_cert_path = os.environ.get('SWML_SSL_CERT_PATH')
91
+ self.ssl_key_path = os.environ.get('SWML_SSL_KEY_PATH')
87
92
 
88
93
  # Initialize proxy detection attributes
89
94
  self._proxy_url_base = os.environ.get('SWML_PROXY_URL_BASE')
@@ -749,13 +754,15 @@ class SWMLService:
749
754
  """
750
755
  import uvicorn
751
756
 
752
- # Store SSL configuration
753
- self.ssl_enabled = ssl_enabled if ssl_enabled is not None else False
754
- self.domain = domain
757
+ # Store SSL configuration (override environment if explicitly provided)
758
+ if ssl_enabled is not None:
759
+ self.ssl_enabled = ssl_enabled
760
+ if domain is not None:
761
+ self.domain = domain
755
762
 
756
- # Set SSL paths
757
- ssl_cert_path = ssl_cert
758
- ssl_key_path = ssl_key
763
+ # Set SSL paths (use provided paths or fall back to environment)
764
+ ssl_cert_path = ssl_cert or getattr(self, 'ssl_cert_path', None)
765
+ ssl_key_path = ssl_key or getattr(self, 'ssl_key_path', None)
759
766
 
760
767
  # Validate SSL configuration if enabled
761
768
  if self.ssl_enabled:
@@ -838,7 +845,14 @@ class SWMLService:
838
845
 
839
846
  # Use correct protocol and host in displayed URL
840
847
  protocol = "https" if self.ssl_enabled else "http"
841
- display_host = self.domain if self.ssl_enabled and self.domain else f"{host}:{port}"
848
+
849
+ # Determine display host - include port unless it's the standard port for the protocol
850
+ if self.ssl_enabled and self.domain:
851
+ # Use domain, but include port if it's not the standard HTTPS port (443)
852
+ display_host = f"{self.domain}:{port}" if port != 443 else self.domain
853
+ else:
854
+ # Use host:port for HTTP or when no domain is specified
855
+ display_host = f"{host}:{port}"
842
856
 
843
857
  self.log.info("starting_server",
844
858
  url=f"{protocol}://{display_host}{self.route}",
@@ -932,90 +946,6 @@ class SWMLService:
932
946
 
933
947
  return username, password
934
948
 
935
- # Keep the existing methods for backward compatibility
936
-
937
- def add_answer_verb(self, max_duration: Optional[int] = None, codecs: Optional[str] = None) -> bool:
938
- """
939
- Add an answer verb to the current document
940
-
941
- Args:
942
- max_duration: Maximum duration in seconds
943
- codecs: Comma-separated list of codecs
944
-
945
- Returns:
946
- True if added successfully, False otherwise
947
- """
948
- config = {}
949
- if max_duration is not None:
950
- config["max_duration"] = max_duration
951
- if codecs is not None:
952
- config["codecs"] = codecs
953
-
954
- return self.add_verb("answer", config)
955
-
956
- def add_hangup_verb(self, reason: Optional[str] = None) -> bool:
957
- """
958
- Add a hangup verb to the current document
959
-
960
- Args:
961
- reason: Hangup reason (hangup, busy, decline)
962
-
963
- Returns:
964
- True if added successfully, False otherwise
965
- """
966
- config = {}
967
- if reason is not None:
968
- config["reason"] = reason
969
-
970
- return self.add_verb("hangup", config)
971
-
972
- def add_ai_verb(self,
973
- prompt_text: Optional[str] = None,
974
- prompt_pom: Optional[List[Dict[str, Any]]] = None,
975
- post_prompt: Optional[str] = None,
976
- post_prompt_url: Optional[str] = None,
977
- swaig: Optional[Dict[str, Any]] = None,
978
- **kwargs) -> bool:
979
- """
980
- Add an AI verb to the current document
981
-
982
- Args:
983
- prompt_text: Simple prompt text
984
- prompt_pom: Prompt object model
985
- post_prompt: Post-prompt text
986
- post_prompt_url: Post-prompt URL
987
- swaig: SWAIG configuration
988
- **kwargs: Additional parameters
989
-
990
- Returns:
991
- True if added successfully, False otherwise
992
- """
993
- config = {}
994
-
995
- # Handle prompt
996
- if prompt_text is not None:
997
- config["prompt"] = prompt_text
998
- elif prompt_pom is not None:
999
- config["prompt"] = prompt_pom
1000
-
1001
- # Handle post prompt
1002
- if post_prompt is not None:
1003
- config["post_prompt"] = post_prompt
1004
-
1005
- # Handle post prompt URL
1006
- if post_prompt_url is not None:
1007
- config["post_prompt_url"] = post_prompt_url
1008
-
1009
- # Handle SWAIG
1010
- if swaig is not None:
1011
- config["SWAIG"] = swaig
1012
-
1013
- # Handle additional parameters
1014
- for key, value in kwargs.items():
1015
- if value is not None:
1016
- config[key] = value
1017
-
1018
- return self.add_verb("ai", config)
1019
949
 
1020
950
  def _build_webhook_url(self, endpoint: str, query_params: Optional[Dict[str, str]] = None) -> str:
1021
951
  """
@@ -1043,7 +973,8 @@ class SWMLService:
1043
973
 
1044
974
  # Use domain if available and SSL is enabled
1045
975
  if getattr(self, 'ssl_enabled', False) and getattr(self, 'domain', None):
1046
- host_part = self.domain
976
+ # Use domain, but include port if it's not the standard HTTPS port (443)
977
+ host_part = f"{self.domain}:{self.port}" if self.port != 443 else self.domain
1047
978
  else:
1048
979
  # For local URLs
1049
980
  if self.host in ("0.0.0.0", "127.0.0.1", "localhost"):
@@ -203,7 +203,7 @@ class IndexBuilder:
203
203
  file_types: List[str], exclude_patterns: Optional[List[str]] = None,
204
204
  languages: List[str] = None, tags: Optional[List[str]] = None):
205
205
  """
206
- Build complete search index from a single directory (legacy method)
206
+ Build complete search index from a single directory
207
207
 
208
208
  Args:
209
209
  source_dir: Directory to scan for documents
@@ -0,0 +1,3 @@
1
+ from .skill import ApiNinjasTriviaSkill
2
+
3
+ __all__ = ['ApiNinjasTriviaSkill']