signalwire-agents 0.1.23__tar.gz → 0.1.24__tar.gz

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 (141) hide show
  1. signalwire_agents-0.1.24/CHANGELOG.md +120 -0
  2. {signalwire_agents-0.1.23/signalwire_agents.egg-info → signalwire_agents-0.1.24}/PKG-INFO +47 -2
  3. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/README.md +43 -1
  4. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/pyproject.toml +12 -7
  5. signalwire_agents-0.1.24/requirements-dev.txt +16 -0
  6. signalwire_agents-0.1.24/requirements.txt +11 -0
  7. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/__init__.py +1 -1
  8. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/agent_server.py +2 -1
  9. signalwire_agents-0.1.24/signalwire_agents/cli/config.py +61 -0
  10. signalwire_agents-0.1.24/signalwire_agents/cli/core/__init__.py +1 -0
  11. signalwire_agents-0.1.24/signalwire_agents/cli/core/agent_loader.py +254 -0
  12. signalwire_agents-0.1.24/signalwire_agents/cli/core/argparse_helpers.py +164 -0
  13. signalwire_agents-0.1.24/signalwire_agents/cli/core/dynamic_config.py +62 -0
  14. signalwire_agents-0.1.24/signalwire_agents/cli/execution/__init__.py +1 -0
  15. signalwire_agents-0.1.24/signalwire_agents/cli/execution/datamap_exec.py +437 -0
  16. signalwire_agents-0.1.24/signalwire_agents/cli/execution/webhook_exec.py +125 -0
  17. signalwire_agents-0.1.24/signalwire_agents/cli/output/__init__.py +1 -0
  18. signalwire_agents-0.1.24/signalwire_agents/cli/output/output_formatter.py +132 -0
  19. signalwire_agents-0.1.24/signalwire_agents/cli/output/swml_dump.py +177 -0
  20. signalwire_agents-0.1.24/signalwire_agents/cli/simulation/__init__.py +1 -0
  21. signalwire_agents-0.1.24/signalwire_agents/cli/simulation/data_generation.py +365 -0
  22. signalwire_agents-0.1.24/signalwire_agents/cli/simulation/data_overrides.py +187 -0
  23. signalwire_agents-0.1.24/signalwire_agents/cli/simulation/mock_env.py +271 -0
  24. signalwire_agents-0.1.24/signalwire_agents/cli/test_swaig.py +780 -0
  25. signalwire_agents-0.1.24/signalwire_agents/cli/types.py +72 -0
  26. signalwire_agents-0.1.24/signalwire_agents/core/agent/__init__.py +12 -0
  27. signalwire_agents-0.1.24/signalwire_agents/core/agent/config/__init__.py +12 -0
  28. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/agent/prompt/manager.py +25 -7
  29. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/agent/tools/decorator.py +2 -0
  30. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/agent/tools/registry.py +8 -0
  31. signalwire_agents-0.1.24/signalwire_agents/core/agent_base.py +1039 -0
  32. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/function_result.py +31 -42
  33. signalwire_agents-0.1.24/signalwire_agents/core/mixins/__init__.py +28 -0
  34. signalwire_agents-0.1.24/signalwire_agents/core/mixins/ai_config_mixin.py +373 -0
  35. signalwire_agents-0.1.24/signalwire_agents/core/mixins/auth_mixin.py +287 -0
  36. signalwire_agents-0.1.24/signalwire_agents/core/mixins/prompt_mixin.py +345 -0
  37. signalwire_agents-0.1.24/signalwire_agents/core/mixins/serverless_mixin.py +368 -0
  38. signalwire_agents-0.1.24/signalwire_agents/core/mixins/skill_mixin.py +55 -0
  39. signalwire_agents-0.1.24/signalwire_agents/core/mixins/state_mixin.py +219 -0
  40. signalwire_agents-0.1.24/signalwire_agents/core/mixins/tool_mixin.py +295 -0
  41. signalwire_agents-0.1.24/signalwire_agents/core/mixins/web_mixin.py +1130 -0
  42. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/skill_manager.py +3 -1
  43. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/swaig_function.py +10 -1
  44. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/swml_service.py +140 -58
  45. signalwire_agents-0.1.24/signalwire_agents/skills/README.md +452 -0
  46. signalwire_agents-0.1.24/signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
  47. signalwire_agents-0.1.24/signalwire_agents/skills/datasphere/README.md +210 -0
  48. signalwire_agents-0.1.24/signalwire_agents/skills/datasphere_serverless/README.md +258 -0
  49. signalwire_agents-0.1.24/signalwire_agents/skills/datetime/README.md +132 -0
  50. signalwire_agents-0.1.24/signalwire_agents/skills/joke/README.md +149 -0
  51. signalwire_agents-0.1.24/signalwire_agents/skills/math/README.md +161 -0
  52. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/native_vector_search/skill.py +33 -13
  53. signalwire_agents-0.1.24/signalwire_agents/skills/play_background_file/README.md +218 -0
  54. signalwire_agents-0.1.24/signalwire_agents/skills/spider/README.md +236 -0
  55. signalwire_agents-0.1.24/signalwire_agents/skills/spider/__init__.py +4 -0
  56. signalwire_agents-0.1.24/signalwire_agents/skills/spider/skill.py +479 -0
  57. signalwire_agents-0.1.24/signalwire_agents/skills/swml_transfer/README.md +395 -0
  58. signalwire_agents-0.1.24/signalwire_agents/skills/swml_transfer/__init__.py +1 -0
  59. signalwire_agents-0.1.24/signalwire_agents/skills/swml_transfer/skill.py +257 -0
  60. signalwire_agents-0.1.24/signalwire_agents/skills/weather_api/README.md +178 -0
  61. signalwire_agents-0.1.24/signalwire_agents/skills/web_search/README.md +163 -0
  62. signalwire_agents-0.1.24/signalwire_agents/skills/wikipedia_search/README.md +228 -0
  63. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24/signalwire_agents.egg-info}/PKG-INFO +47 -2
  64. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents.egg-info/SOURCES.txt +45 -1
  65. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents.egg-info/entry_points.txt +1 -1
  66. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents.egg-info/requires.txt +1 -0
  67. signalwire_agents-0.1.23/signalwire_agents/cli/test_swaig.py +0 -2797
  68. signalwire_agents-0.1.23/signalwire_agents/core/agent/__init__.py +0 -14
  69. signalwire_agents-0.1.23/signalwire_agents/core/agent/config/__init__.py +0 -14
  70. signalwire_agents-0.1.23/signalwire_agents/core/agent/config/ephemeral.py +0 -176
  71. signalwire_agents-0.1.23/signalwire_agents/core/agent_base.py +0 -3600
  72. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/LICENSE +0 -0
  73. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/schema.json +0 -0
  74. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/setup.cfg +0 -0
  75. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/setup.py +0 -0
  76. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/cli/__init__.py +0 -0
  77. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/cli/build_search.py +0 -0
  78. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/__init__.py +0 -0
  79. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/agent/deployment/__init__.py +0 -0
  80. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/agent/deployment/handlers/__init__.py +0 -0
  81. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/agent/prompt/__init__.py +0 -0
  82. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/agent/routing/__init__.py +0 -0
  83. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/agent/security/__init__.py +0 -0
  84. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/agent/swml/__init__.py +0 -0
  85. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/agent/tools/__init__.py +0 -0
  86. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/contexts.py +0 -0
  87. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/data_map.py +0 -0
  88. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/logging_config.py +0 -0
  89. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/pom_builder.py +0 -0
  90. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/security/__init__.py +0 -0
  91. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/security/session_manager.py +0 -0
  92. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/skill_base.py +0 -0
  93. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/state/__init__.py +0 -0
  94. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/state/file_state_manager.py +0 -0
  95. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/state/state_manager.py +0 -0
  96. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/swml_builder.py +0 -0
  97. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/swml_handler.py +0 -0
  98. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/core/swml_renderer.py +0 -0
  99. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/prefabs/__init__.py +0 -0
  100. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/prefabs/concierge.py +0 -0
  101. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/prefabs/faq_bot.py +0 -0
  102. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/prefabs/info_gatherer.py +0 -0
  103. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/prefabs/receptionist.py +0 -0
  104. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/prefabs/survey.py +0 -0
  105. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/schema.json +0 -0
  106. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/search/__init__.py +0 -0
  107. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/search/document_processor.py +0 -0
  108. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/search/index_builder.py +0 -0
  109. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/search/query_processor.py +0 -0
  110. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/search/search_engine.py +0 -0
  111. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/search/search_service.py +0 -0
  112. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/__init__.py +0 -0
  113. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/api_ninjas_trivia/__init__.py +0 -0
  114. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/api_ninjas_trivia/skill.py +0 -0
  115. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/datasphere/__init__.py +0 -0
  116. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/datasphere/skill.py +0 -0
  117. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/datasphere_serverless/__init__.py +0 -0
  118. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/datasphere_serverless/skill.py +0 -0
  119. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/datetime/__init__.py +0 -0
  120. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/datetime/skill.py +0 -0
  121. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/joke/__init__.py +0 -0
  122. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/joke/skill.py +0 -0
  123. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/math/__init__.py +0 -0
  124. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/math/skill.py +0 -0
  125. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/native_vector_search/__init__.py +0 -0
  126. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/play_background_file/__init__.py +0 -0
  127. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/play_background_file/skill.py +0 -0
  128. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/registry.py +0 -0
  129. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/weather_api/__init__.py +0 -0
  130. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/weather_api/skill.py +0 -0
  131. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/web_search/__init__.py +0 -0
  132. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/web_search/skill.py +0 -0
  133. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/wikipedia_search/__init__.py +0 -0
  134. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/skills/wikipedia_search/skill.py +0 -0
  135. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/utils/__init__.py +0 -0
  136. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/utils/pom_utils.py +0 -0
  137. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/utils/schema_utils.py +0 -0
  138. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/utils/token_generators.py +0 -0
  139. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents/utils/validators.py +0 -0
  140. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents.egg-info/dependency_links.txt +0 -0
  141. {signalwire_agents-0.1.23 → signalwire_agents-0.1.24}/signalwire_agents.egg-info/top_level.txt +0 -0
@@ -0,0 +1,120 @@
1
+ # Changelog
2
+
3
+ ## [0.1.24] - 2025-06-19
4
+
5
+ - Version bump
6
+
7
+ ## [0.1.23] - 2025-06-17
8
+
9
+ - Version bump
10
+
11
+ ## [0.1.22] - 2025-06-17
12
+
13
+ - Version bump
14
+
15
+ ## [0.1.21] - 2025-06-12
16
+
17
+ - Version bump
18
+
19
+ ## [0.1.20] - 2025-06-10
20
+
21
+ - Version bump
22
+
23
+ ## [0.1.19] - 2025-06-09
24
+
25
+ - Version bump
26
+
27
+ ## [0.1.18] - 2025-06-09
28
+
29
+ - Version bump
30
+
31
+ ## [0.1.17] - 2025-06-02
32
+
33
+ - Version bump
34
+
35
+ ## [0.1.16] - 2025-06-02
36
+
37
+ - Version bump
38
+
39
+ ## [0.1.15] - 2025-05-30
40
+
41
+ - Version bump
42
+
43
+ ## [0.1.14] - 2025-05-29
44
+
45
+ - Version bump
46
+
47
+ ## [0.1.13] - 2025-05-29
48
+
49
+ - Version bump
50
+
51
+ ## [0.1.12] - 2025-05-28
52
+
53
+ - Version bump
54
+
55
+ ## [0.1.11] - 2025-05-28
56
+
57
+ - Version bump
58
+
59
+ ## [0.1.10] - 2025-05-28
60
+
61
+ - Version bump
62
+
63
+ ## [0.1.9] - 2025-05-24
64
+
65
+ - Version bump
66
+
67
+ ## [0.1.8] - 2025-05-23
68
+
69
+ - Version bump
70
+
71
+ ## [0.1.7] - 2025-05-17
72
+
73
+ - Version bump
74
+
75
+ ## [0.1.6] - 2025-05-16
76
+
77
+ - Version bump
78
+
79
+ ## [0.1.5] - 2025-05-16
80
+
81
+ - Version bump
82
+
83
+ ## [0.1.4] - 2025-05-16
84
+
85
+ - Version bump
86
+
87
+ ## [Unreleased]
88
+
89
+ ### Added
90
+
91
+ - **SIP Routing:** Added support for routing SIP requests based on username
92
+ - `SWMLService.extract_sip_username()` method for extracting SIP usernames from request bodies
93
+ - `register_routing_callback()` method for SWMLService to handle custom routing
94
+ - `enable_sip_routing()` method for AgentBase to automatically handle SIP requests
95
+ - `register_sip_username()` method for AgentBase to register custom SIP usernames
96
+ - `setup_sip_routing()` method for AgentServer to enable centralized SIP routing
97
+ - Added global `/sip` endpoint for both SWMLService and AgentBase when SIP routing is enabled
98
+ - Example: Added SIP routing to simple_agent.py
99
+ - Documentation: Added docs/sip_routing.md with full documentation
100
+
101
+ ### Fixed
102
+
103
+ - Fixed the `sleep` verb auto-vivification to properly handle direct integer values
104
+
105
+ ### Changed
106
+
107
+ - **Dynamic Method Generation:** Implemented auto-vivification in SWMLService class for more intuitive verb usage
108
+ - Now you can use `service.play(url="say:Hello")` instead of `service.add_verb("play", {"url": "say:Hello"})`
109
+ - All verbs from the schema are automatically available as methods
110
+ - Special handling for the `sleep` verb which takes a direct integer value
111
+
112
+ ## [0.1.2] - 2023-04-15
113
+
114
+ ### Added
115
+
116
+ - Initial release of the SignalWire AI Agent SDK
117
+ - Base classes for building and serving AI Agents
118
+ - SWML document creation and manipulation
119
+ - Web server for handling agent requests
120
+ - Documentation and examples
@@ -1,9 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: signalwire_agents
3
- Version: 0.1.23
3
+ Version: 0.1.24
4
4
  Summary: SignalWire AI Agents SDK
5
5
  Author-email: SignalWire Team <info@signalwire.com>
6
+ License: MIT
6
7
  Project-URL: Homepage, https://github.com/signalwire/signalwire-agents
8
+ Keywords: signalwire,ai,agents,voice,telephony,swaig,swml
7
9
  Classifier: Development Status :: 4 - Beta
8
10
  Classifier: Intended Audience :: Developers
9
11
  Classifier: License :: OSI Approved :: MIT License
@@ -26,6 +28,7 @@ Requires-Dist: structlog==25.3.0
26
28
  Requires-Dist: uvicorn==0.34.2
27
29
  Requires-Dist: beautifulsoup4==4.12.3
28
30
  Requires-Dist: pytz==2023.3
31
+ Requires-Dist: lxml>=4.9.0
29
32
  Provides-Extra: search-queryonly
30
33
  Requires-Dist: numpy>=1.24.0; extra == "search-queryonly"
31
34
  Requires-Dist: scikit-learn>=1.3.0; extra == "search-queryonly"
@@ -735,7 +738,49 @@ class DynamicAgent(AgentBase):
735
738
  - **Localization**: Language and cultural adaptation based on user location
736
739
  - **Dynamic Pricing**: Adjust features and capabilities based on subscription tiers
737
740
 
738
- The `EphemeralAgentConfig` object provides all the same familiar methods as `AgentBase` (like `add_language()`, `prompt_add_section()`, `set_global_data()`) but applies them per-request instead of at startup.
741
+ ### Preserving Dynamic State in SWAIG Callbacks
742
+
743
+ When using dynamic configuration to add skills or tools based on request parameters, there's a challenge: SWAIG webhook callbacks are separate HTTP requests that won't have the original query parameters. The SDK provides `add_swaig_query_params()` to solve this:
744
+
745
+ ```python
746
+ class DynamicAgent(AgentBase):
747
+ def __init__(self):
748
+ super().__init__(name="dynamic-agent", route="/agent")
749
+ self.set_dynamic_config_callback(self.configure_per_request)
750
+
751
+ def configure_per_request(self, query_params, body_params, headers, agent):
752
+ tier = query_params.get('tier', 'basic')
753
+ region = query_params.get('region', 'us-east')
754
+
755
+ if tier == 'premium':
756
+ # Add premium skills dynamically
757
+ agent.add_skill('advanced_search', {
758
+ 'api_key': 'your-api-key',
759
+ 'num_results': 5
760
+ })
761
+
762
+ # IMPORTANT: Preserve parameters for SWAIG callbacks
763
+ agent.add_swaig_query_params({
764
+ 'tier': tier,
765
+ 'region': region
766
+ })
767
+
768
+ # Now when SignalWire calls the SWAIG webhook, these params
769
+ # will be included, triggering the same dynamic configuration
770
+
771
+ # Initial request: GET /agent?tier=premium&region=eu-west
772
+ # SWAIG callback: POST /swaig/?tier=premium&region=eu-west
773
+ # Result: Premium skills are available in both requests!
774
+ ```
775
+
776
+ **Key Points:**
777
+
778
+ - **Problem**: Dynamically added skills/tools won't exist during SWAIG callbacks without the original request parameters
779
+ - **Solution**: Use `add_swaig_query_params()` to include critical parameters in all SWAIG webhook URLs
780
+ - **Clear State**: Use `clear_swaig_query_params()` if needed to reset parameters between requests
781
+ - **Token Safety**: The SDK automatically renames security tokens from `token` to `__token` to avoid parameter collisions
782
+
783
+ This ensures that any dynamic configuration based on request parameters is consistently applied across the initial SWML request and all subsequent SWAIG function callbacks.
739
784
 
740
785
  For detailed documentation and advanced examples, see the [Agent Guide](docs/agent_guide.md#dynamic-agent-configuration).
741
786
 
@@ -664,7 +664,49 @@ class DynamicAgent(AgentBase):
664
664
  - **Localization**: Language and cultural adaptation based on user location
665
665
  - **Dynamic Pricing**: Adjust features and capabilities based on subscription tiers
666
666
 
667
- The `EphemeralAgentConfig` object provides all the same familiar methods as `AgentBase` (like `add_language()`, `prompt_add_section()`, `set_global_data()`) but applies them per-request instead of at startup.
667
+ ### Preserving Dynamic State in SWAIG Callbacks
668
+
669
+ When using dynamic configuration to add skills or tools based on request parameters, there's a challenge: SWAIG webhook callbacks are separate HTTP requests that won't have the original query parameters. The SDK provides `add_swaig_query_params()` to solve this:
670
+
671
+ ```python
672
+ class DynamicAgent(AgentBase):
673
+ def __init__(self):
674
+ super().__init__(name="dynamic-agent", route="/agent")
675
+ self.set_dynamic_config_callback(self.configure_per_request)
676
+
677
+ def configure_per_request(self, query_params, body_params, headers, agent):
678
+ tier = query_params.get('tier', 'basic')
679
+ region = query_params.get('region', 'us-east')
680
+
681
+ if tier == 'premium':
682
+ # Add premium skills dynamically
683
+ agent.add_skill('advanced_search', {
684
+ 'api_key': 'your-api-key',
685
+ 'num_results': 5
686
+ })
687
+
688
+ # IMPORTANT: Preserve parameters for SWAIG callbacks
689
+ agent.add_swaig_query_params({
690
+ 'tier': tier,
691
+ 'region': region
692
+ })
693
+
694
+ # Now when SignalWire calls the SWAIG webhook, these params
695
+ # will be included, triggering the same dynamic configuration
696
+
697
+ # Initial request: GET /agent?tier=premium&region=eu-west
698
+ # SWAIG callback: POST /swaig/?tier=premium&region=eu-west
699
+ # Result: Premium skills are available in both requests!
700
+ ```
701
+
702
+ **Key Points:**
703
+
704
+ - **Problem**: Dynamically added skills/tools won't exist during SWAIG callbacks without the original request parameters
705
+ - **Solution**: Use `add_swaig_query_params()` to include critical parameters in all SWAIG webhook URLs
706
+ - **Clear State**: Use `clear_swaig_query_params()` if needed to reset parameters between requests
707
+ - **Token Safety**: The SDK automatically renames security tokens from `token` to `__token` to avoid parameter collisions
708
+
709
+ This ensures that any dynamic configuration based on request parameters is consistently applied across the initial SWML request and all subsequent SWAIG function callbacks.
668
710
 
669
711
  For detailed documentation and advanced examples, see the [Agent Guide](docs/agent_guide.md#dynamic-agent-configuration).
670
712
 
@@ -1,15 +1,17 @@
1
1
  [build-system]
2
- requires = ["setuptools>=42", "wheel"]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "signalwire_agents"
7
- version = "0.1.23"
7
+ version = "0.1.24"
8
8
  description = "SignalWire AI Agents SDK"
9
9
  authors = [
10
10
  {name = "SignalWire Team", email = "info@signalwire.com"}
11
11
  ]
12
12
  readme = "README.md"
13
+ license = {text = "MIT"}
14
+ keywords = ["signalwire", "ai", "agents", "voice", "telephony", "swaig", "swml"]
13
15
  requires-python = ">=3.7"
14
16
  classifiers = [
15
17
  "Development Status :: 4 - Beta",
@@ -33,6 +35,7 @@ dependencies = [
33
35
  "uvicorn==0.34.2",
34
36
  "beautifulsoup4==4.12.3",
35
37
  "pytz==2023.3",
38
+ "lxml>=4.9.0",
36
39
  ]
37
40
 
38
41
  # Optional dependencies for search functionality
@@ -98,7 +101,7 @@ search-all = [
98
101
  Homepage = "https://github.com/signalwire/signalwire-agents"
99
102
 
100
103
  [project.scripts]
101
- swaig-test = "signalwire_agents.cli.test_swaig:console_entry_point"
104
+ swaig-test = "signalwire_agents.cli.test_swaig_main:main"
102
105
  sw-search = "signalwire_agents.cli.build_search:console_entry_point"
103
106
 
104
107
  [tool.setuptools]
@@ -106,7 +109,9 @@ packages = {find = {where = ["."], include = ["signalwire_agents*"]}}
106
109
  include-package-data = true
107
110
 
108
111
  [tool.setuptools.package-data]
109
- signalwire_agents = ["schema.json"]
110
-
111
- [tool.setuptools.data-files]
112
- "." = ["schema.json"]
112
+ signalwire_agents = [
113
+ "schema.json",
114
+ "**/*.json",
115
+ "skills/*/README.md",
116
+ "skills/README.md"
117
+ ]
@@ -0,0 +1,16 @@
1
+ # Development and Testing Dependencies
2
+ pytest>=7.4.0
3
+ pytest-cov>=4.1.0
4
+ pytest-asyncio>=0.21.0
5
+ pytest-mock>=3.11.0
6
+ pytest-xdist>=3.3.0 # For parallel test execution
7
+ pytest-html>=3.2.0 # For HTML test reports
8
+ pytest-timeout>=2.1.0 # For test timeouts
9
+ coverage>=7.2.0
10
+ mock>=5.1.0
11
+ responses>=0.23.0 # For mocking HTTP requests
12
+ freezegun>=1.2.0 # For mocking datetime
13
+ factory-boy>=3.3.0 # For test data factories
14
+ faker>=19.0.0 # For generating fake data
15
+ httpx>=0.24.0 # For async HTTP testing
16
+ aiofiles>=23.0.0 # For async file operations in tests
@@ -0,0 +1,11 @@
1
+ fastapi==0.115.12
2
+ pydantic==2.11.4
3
+ PyYAML==6.0.2
4
+ Requests==2.32.3
5
+ setuptools==66.1.1
6
+ signalwire_pom==2.7.1
7
+ structlog==25.3.0
8
+ uvicorn==0.34.2
9
+ beautifulsoup4==4.12.3
10
+ pytz==2023.3
11
+ lxml==5.3.0
@@ -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.23"
21
+ __version__ = "0.1.24"
22
22
 
23
23
  # Import core classes for easier access
24
24
  from .core.agent_base import AgentBase
@@ -610,8 +610,9 @@ class AgentServer:
610
610
  self.logger.info(f"Starting server on {protocol}://{display_host}")
611
611
  for route, agent in self.agents.items():
612
612
  username, password = agent.get_basic_auth_credentials()
613
+ agent_url = agent.get_full_url(include_auth=False)
613
614
  self.logger.info(f"Agent '{agent.get_name()}' available at:")
614
- self.logger.info(f"URL: {protocol}://{display_host}{route}")
615
+ self.logger.info(f"URL: {agent_url}")
615
616
  self.logger.info(f"Basic Auth: {username}:{password}")
616
617
 
617
618
  # Start the server with or without SSL
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Configuration constants for the CLI tools
4
+ """
5
+
6
+ # Default values for call configuration
7
+ DEFAULT_CALL_TYPE = "webrtc"
8
+ DEFAULT_CALL_DIRECTION = "inbound"
9
+ DEFAULT_CALL_STATE = "created"
10
+ DEFAULT_HTTP_METHOD = "POST"
11
+ DEFAULT_TOKEN_EXPIRY = 3600
12
+
13
+ # Default fake data values
14
+ DEFAULT_PROJECT_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
15
+ DEFAULT_SPACE_ID = "zzzzzzzz-yyyy-xxxx-wwww-vvvvvvvvvvvv"
16
+ DEFAULT_SPACE_NAME = "example-space"
17
+ DEFAULT_ENVIRONMENT = "production"
18
+
19
+ # Request timeouts
20
+ HTTP_REQUEST_TIMEOUT = 30
21
+
22
+ # Output formatting
23
+ RESULT_LINE_SEP = "-" * 60
24
+
25
+ # Platform-specific constants
26
+ SERVERLESS_PLATFORMS = ["lambda", "cgi", "cloud_function", "azure_function"]
27
+
28
+ # AWS Lambda defaults
29
+ AWS_DEFAULT_REGION = "us-east-1"
30
+ AWS_DEFAULT_STAGE = "prod"
31
+
32
+ # Google Cloud defaults
33
+ GCP_DEFAULT_REGION = "us-central1"
34
+
35
+ # Error messages
36
+ ERROR_MISSING_AGENT = "Error: Missing required argument."
37
+ ERROR_MULTIPLE_AGENTS = "Multiple agents found in file. Please specify --agent-class"
38
+ ERROR_NO_AGENTS = "No agents found in file: {file_path}"
39
+ ERROR_AGENT_NOT_FOUND = "Agent class '{class_name}' not found in file: {file_path}"
40
+ ERROR_FUNCTION_NOT_FOUND = "Function '{function_name}' not found in agent"
41
+ ERROR_CGI_HOST_REQUIRED = "CGI simulation requires --cgi-host"
42
+
43
+ # Help messages
44
+ HELP_DESCRIPTION = "Test SWAIG functions and generate SWML documents for SignalWire AI agents"
45
+ HELP_EPILOG_SHORT = """
46
+ examples:
47
+ # Execute a function
48
+ %(prog)s agent.py --exec search --query "test" --limit 5
49
+
50
+ # Generate SWML
51
+ %(prog)s agent.py --dump-swml --raw | jq '.'
52
+
53
+ # Test with specific agent
54
+ %(prog)s multi_agent.py --agent-class MattiAgent --list-tools
55
+
56
+ # Simulate serverless
57
+ %(prog)s agent.py --simulate-serverless lambda --exec my_function
58
+
59
+ For platform-specific options: %(prog)s --help-platforms
60
+ For more examples: %(prog)s --help-examples
61
+ """
@@ -0,0 +1 @@
1
+ """Core functionality for CLI tools"""
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Agent discovery and loading functionality
4
+ """
5
+
6
+ import importlib.util
7
+ from pathlib import Path
8
+ from typing import List, Dict, Any, Optional
9
+
10
+ # Import after checking if available
11
+ try:
12
+ from signalwire_agents.core.agent_base import AgentBase
13
+ AGENT_BASE_AVAILABLE = True
14
+ except ImportError:
15
+ AgentBase = None
16
+ AGENT_BASE_AVAILABLE = False
17
+
18
+
19
+ def discover_agents_in_file(agent_path: str) -> List[Dict[str, Any]]:
20
+ """
21
+ Discover all available agents in a Python file without instantiating them
22
+
23
+ Args:
24
+ agent_path: Path to the Python file containing agents
25
+
26
+ Returns:
27
+ List of dictionaries with agent information
28
+
29
+ Raises:
30
+ ImportError: If the file cannot be imported
31
+ FileNotFoundError: If the file doesn't exist
32
+ """
33
+ if not AGENT_BASE_AVAILABLE:
34
+ raise ImportError("AgentBase not available. Please install signalwire-agents package.")
35
+
36
+ agent_path = Path(agent_path).resolve()
37
+
38
+ if not agent_path.exists():
39
+ raise FileNotFoundError(f"Agent file not found: {agent_path}")
40
+
41
+ if not agent_path.suffix == '.py':
42
+ raise ValueError(f"Agent file must be a Python file (.py): {agent_path}")
43
+
44
+ # Load the module, but prevent main() execution by setting __name__ to something other than "__main__"
45
+ spec = importlib.util.spec_from_file_location("agent_module", agent_path)
46
+ module = importlib.util.module_from_spec(spec)
47
+
48
+ try:
49
+ # Set __name__ to prevent if __name__ == "__main__": blocks from running
50
+ module.__name__ = "agent_module"
51
+ spec.loader.exec_module(module)
52
+ except Exception as e:
53
+ raise ImportError(f"Failed to load agent module: {e}")
54
+
55
+ agents_found = []
56
+
57
+ # Look for AgentBase instances
58
+ for name, obj in vars(module).items():
59
+ if isinstance(obj, AgentBase):
60
+ agents_found.append({
61
+ 'name': name,
62
+ 'class_name': obj.__class__.__name__,
63
+ 'type': 'instance',
64
+ 'agent_name': getattr(obj, 'name', 'Unknown'),
65
+ 'route': getattr(obj, 'route', 'Unknown'),
66
+ 'description': obj.__class__.__doc__,
67
+ 'object': obj
68
+ })
69
+
70
+ # Look for AgentBase subclasses (that could be instantiated)
71
+ for name, obj in vars(module).items():
72
+ if (isinstance(obj, type) and
73
+ issubclass(obj, AgentBase) and
74
+ obj != AgentBase):
75
+ # Check if we already found an instance of this class
76
+ instance_found = any(agent['class_name'] == name for agent in agents_found)
77
+ if not instance_found:
78
+ try:
79
+ # Try to get class information without instantiating
80
+ agent_info = {
81
+ 'name': name,
82
+ 'class_name': name,
83
+ 'type': 'class',
84
+ 'agent_name': 'Unknown (not instantiated)',
85
+ 'route': 'Unknown (not instantiated)',
86
+ 'description': obj.__doc__,
87
+ 'object': obj
88
+ }
89
+ agents_found.append(agent_info)
90
+ except Exception:
91
+ # If we can't get info, still record that the class exists
92
+ agents_found.append({
93
+ 'name': name,
94
+ 'class_name': name,
95
+ 'type': 'class',
96
+ 'agent_name': 'Unknown (not instantiated)',
97
+ 'route': 'Unknown (not instantiated)',
98
+ 'description': obj.__doc__ or 'No description available',
99
+ 'object': obj
100
+ })
101
+
102
+ return agents_found
103
+
104
+
105
+ def load_agent_from_file(agent_path: str, agent_class_name: Optional[str] = None) -> 'AgentBase':
106
+ """
107
+ Load an agent from a Python file
108
+
109
+ Args:
110
+ agent_path: Path to the Python file containing the agent
111
+ agent_class_name: Optional name of the agent class to instantiate
112
+
113
+ Returns:
114
+ AgentBase instance
115
+
116
+ Raises:
117
+ ImportError: If the file cannot be imported
118
+ ValueError: If no agent is found in the file
119
+ """
120
+ if not AGENT_BASE_AVAILABLE:
121
+ raise ImportError("AgentBase not available. Please install signalwire-agents package.")
122
+
123
+ agent_path = Path(agent_path).resolve()
124
+
125
+ if not agent_path.exists():
126
+ raise FileNotFoundError(f"Agent file not found: {agent_path}")
127
+
128
+ if not agent_path.suffix == '.py':
129
+ raise ValueError(f"Agent file must be a Python file (.py): {agent_path}")
130
+
131
+ # Load the module, but prevent main() execution by setting __name__ to something other than "__main__"
132
+ spec = importlib.util.spec_from_file_location("agent_module", agent_path)
133
+ module = importlib.util.module_from_spec(spec)
134
+
135
+ try:
136
+ # Set __name__ to prevent if __name__ == "__main__": blocks from running
137
+ module.__name__ = "agent_module"
138
+ spec.loader.exec_module(module)
139
+ except Exception as e:
140
+ raise ImportError(f"Failed to load agent module: {e}")
141
+
142
+ # Find the agent instance
143
+ agent = None
144
+
145
+ # If agent_class_name is specified, try to instantiate that specific class first
146
+ if agent_class_name:
147
+ if hasattr(module, agent_class_name):
148
+ obj = getattr(module, agent_class_name)
149
+ if isinstance(obj, type) and issubclass(obj, AgentBase) and obj != AgentBase:
150
+ try:
151
+ agent = obj()
152
+ if agent and not agent.route.endswith('dummy'): # Avoid test agents with dummy routes
153
+ pass # Successfully created specific agent
154
+ else:
155
+ agent = obj() # Create anyway if requested specifically
156
+ except Exception as e:
157
+ raise ValueError(f"Failed to instantiate agent class '{agent_class_name}': {e}")
158
+ else:
159
+ raise ValueError(f"'{agent_class_name}' is not a valid AgentBase subclass")
160
+ else:
161
+ raise ValueError(f"Agent class '{agent_class_name}' not found in {agent_path}")
162
+
163
+ # Strategy 1: Look for 'agent' variable (most common pattern)
164
+ if agent is None and hasattr(module, 'agent') and isinstance(module.agent, AgentBase):
165
+ agent = module.agent
166
+
167
+ # Strategy 2: Look for any AgentBase instance in module globals
168
+ if agent is None:
169
+ agents_found = []
170
+ for name, obj in vars(module).items():
171
+ if isinstance(obj, AgentBase):
172
+ agents_found.append((name, obj))
173
+
174
+ if len(agents_found) == 1:
175
+ agent = agents_found[0][1]
176
+ elif len(agents_found) > 1:
177
+ # Multiple agents found, prefer one named 'agent'
178
+ for name, obj in agents_found:
179
+ if name == 'agent':
180
+ agent = obj
181
+ break
182
+ # If no 'agent' variable, use the first one
183
+ if agent is None:
184
+ agent = agents_found[0][1]
185
+ print(f"Warning: Multiple agents found, using '{agents_found[0][0]}'")
186
+ print(f"Hint: Use --agent-class parameter to choose specific agent")
187
+
188
+ # Strategy 3: Look for AgentBase subclass and try to instantiate it
189
+ if agent is None:
190
+ agent_classes_found = []
191
+ for name, obj in vars(module).items():
192
+ if (isinstance(obj, type) and
193
+ issubclass(obj, AgentBase) and
194
+ obj != AgentBase):
195
+ agent_classes_found.append((name, obj))
196
+
197
+ if len(agent_classes_found) == 1:
198
+ try:
199
+ agent = agent_classes_found[0][1]()
200
+ except Exception as e:
201
+ print(f"Warning: Failed to instantiate {agent_classes_found[0][0]}: {e}")
202
+ elif len(agent_classes_found) > 1:
203
+ # Multiple agent classes found
204
+ class_names = [name for name, _ in agent_classes_found]
205
+ raise ValueError(f"Multiple agent classes found: {', '.join(class_names)}. "
206
+ f"Please specify which agent class to use with --agent-class parameter. "
207
+ f"Usage: swaig-test {agent_path} [tool_name] [args] --agent-class <AgentClassName>")
208
+ else:
209
+ # Try instantiating any AgentBase class we can find
210
+ for name, obj in vars(module).items():
211
+ if (isinstance(obj, type) and
212
+ issubclass(obj, AgentBase) and
213
+ obj != AgentBase):
214
+ try:
215
+ agent = obj()
216
+ break
217
+ except Exception as e:
218
+ print(f"Warning: Failed to instantiate {name}: {e}")
219
+
220
+ # Strategy 4: Try calling a modified main() function that doesn't start the server
221
+ if agent is None and hasattr(module, 'main'):
222
+ print("Warning: No agent instance found, attempting to call main() without server startup")
223
+ try:
224
+ # Temporarily patch AgentBase.serve to prevent server startup
225
+ original_serve = AgentBase.serve
226
+ captured_agent = []
227
+
228
+ def mock_serve(self, *args, **kwargs):
229
+ captured_agent.append(self)
230
+ print(f" (Intercepted serve() call, agent captured for testing)")
231
+ return self
232
+
233
+ AgentBase.serve = mock_serve
234
+
235
+ try:
236
+ result = module.main()
237
+ if isinstance(result, AgentBase):
238
+ agent = result
239
+ elif captured_agent:
240
+ agent = captured_agent[0]
241
+ finally:
242
+ # Restore original serve method
243
+ AgentBase.serve = original_serve
244
+
245
+ except Exception as e:
246
+ print(f"Warning: Failed to call main() function: {e}")
247
+
248
+ if agent is None:
249
+ raise ValueError(f"No agent found in {agent_path}. The file must contain either:\n"
250
+ f"- An AgentBase instance (e.g., agent = MyAgent())\n"
251
+ f"- An AgentBase subclass that can be instantiated\n"
252
+ f"- A main() function that creates and returns an agent")
253
+
254
+ return agent