signalwire-agents 0.1.43__tar.gz → 0.1.44__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 (142) hide show
  1. {signalwire_agents-0.1.43/signalwire_agents.egg-info → signalwire_agents-0.1.44}/PKG-INFO +1 -1
  2. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/pyproject.toml +1 -1
  3. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/__init__.py +5 -1
  4. signalwire_agents-0.1.44/signalwire_agents/web/__init__.py +17 -0
  5. signalwire_agents-0.1.44/signalwire_agents/web/web_service.py +559 -0
  6. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44/signalwire_agents.egg-info}/PKG-INFO +1 -1
  7. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents.egg-info/SOURCES.txt +3 -1
  8. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/LICENSE +0 -0
  9. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/README.md +0 -0
  10. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/setup.cfg +0 -0
  11. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/setup.py +0 -0
  12. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/agent_server.py +0 -0
  13. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/agents/bedrock.py +0 -0
  14. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/__init__.py +0 -0
  15. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/build_search.py +0 -0
  16. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/config.py +0 -0
  17. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/core/__init__.py +0 -0
  18. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/core/agent_loader.py +0 -0
  19. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/core/argparse_helpers.py +0 -0
  20. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/core/dynamic_config.py +0 -0
  21. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/core/service_loader.py +0 -0
  22. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/execution/__init__.py +0 -0
  23. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/execution/datamap_exec.py +0 -0
  24. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/execution/webhook_exec.py +0 -0
  25. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/output/__init__.py +0 -0
  26. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/output/output_formatter.py +0 -0
  27. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/output/swml_dump.py +0 -0
  28. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/simulation/__init__.py +0 -0
  29. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/simulation/data_generation.py +0 -0
  30. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/simulation/data_overrides.py +0 -0
  31. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/simulation/mock_env.py +0 -0
  32. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/swaig_test_wrapper.py +0 -0
  33. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/test_swaig.py +0 -0
  34. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/cli/types.py +0 -0
  35. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/__init__.py +0 -0
  36. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/agent/__init__.py +0 -0
  37. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/agent/config/__init__.py +0 -0
  38. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/agent/deployment/__init__.py +0 -0
  39. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/agent/deployment/handlers/__init__.py +0 -0
  40. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/agent/prompt/__init__.py +0 -0
  41. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/agent/prompt/manager.py +0 -0
  42. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/agent/routing/__init__.py +0 -0
  43. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/agent/security/__init__.py +0 -0
  44. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/agent/swml/__init__.py +0 -0
  45. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/agent/tools/__init__.py +0 -0
  46. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/agent/tools/decorator.py +0 -0
  47. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/agent/tools/registry.py +0 -0
  48. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/agent_base.py +0 -0
  49. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/auth_handler.py +0 -0
  50. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/config_loader.py +0 -0
  51. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/contexts.py +0 -0
  52. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/data_map.py +0 -0
  53. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/function_result.py +0 -0
  54. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/logging_config.py +0 -0
  55. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/mixins/__init__.py +0 -0
  56. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/mixins/ai_config_mixin.py +0 -0
  57. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/mixins/auth_mixin.py +0 -0
  58. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/mixins/prompt_mixin.py +0 -0
  59. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/mixins/serverless_mixin.py +0 -0
  60. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/mixins/skill_mixin.py +0 -0
  61. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/mixins/state_mixin.py +0 -0
  62. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/mixins/tool_mixin.py +0 -0
  63. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/mixins/web_mixin.py +0 -0
  64. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/pom_builder.py +0 -0
  65. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/security/__init__.py +0 -0
  66. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/security/session_manager.py +0 -0
  67. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/security_config.py +0 -0
  68. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/skill_base.py +0 -0
  69. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/skill_manager.py +0 -0
  70. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/swaig_function.py +0 -0
  71. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/swml_builder.py +0 -0
  72. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/swml_handler.py +0 -0
  73. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/swml_renderer.py +0 -0
  74. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/core/swml_service.py +0 -0
  75. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/prefabs/__init__.py +0 -0
  76. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/prefabs/concierge.py +0 -0
  77. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/prefabs/faq_bot.py +0 -0
  78. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/prefabs/info_gatherer.py +0 -0
  79. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/prefabs/receptionist.py +0 -0
  80. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/prefabs/survey.py +0 -0
  81. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/schema.json +0 -0
  82. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/search/__init__.py +0 -0
  83. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/search/document_processor.py +0 -0
  84. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/search/index_builder.py +0 -0
  85. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/search/pgvector_backend.py +0 -0
  86. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/search/query_processor.py +0 -0
  87. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/search/search_engine.py +0 -0
  88. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/search/search_service.py +0 -0
  89. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/README.md +0 -0
  90. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/__init__.py +0 -0
  91. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/api_ninjas_trivia/README.md +0 -0
  92. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/api_ninjas_trivia/__init__.py +0 -0
  93. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/api_ninjas_trivia/skill.py +0 -0
  94. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/datasphere/README.md +0 -0
  95. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/datasphere/__init__.py +0 -0
  96. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/datasphere/skill.py +0 -0
  97. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/datasphere_serverless/README.md +0 -0
  98. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/datasphere_serverless/__init__.py +0 -0
  99. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/datasphere_serverless/skill.py +0 -0
  100. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/datetime/README.md +0 -0
  101. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/datetime/__init__.py +0 -0
  102. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/datetime/skill.py +0 -0
  103. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/joke/README.md +0 -0
  104. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/joke/__init__.py +0 -0
  105. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/joke/skill.py +0 -0
  106. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/math/README.md +0 -0
  107. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/math/__init__.py +0 -0
  108. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/math/skill.py +0 -0
  109. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/mcp_gateway/README.md +0 -0
  110. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/mcp_gateway/__init__.py +0 -0
  111. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/mcp_gateway/skill.py +0 -0
  112. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/native_vector_search/README.md +0 -0
  113. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/native_vector_search/__init__.py +0 -0
  114. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/native_vector_search/skill.py +0 -0
  115. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/play_background_file/README.md +0 -0
  116. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/play_background_file/__init__.py +0 -0
  117. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/play_background_file/skill.py +0 -0
  118. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/registry.py +0 -0
  119. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/spider/README.md +0 -0
  120. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/spider/__init__.py +0 -0
  121. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/spider/skill.py +0 -0
  122. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/swml_transfer/README.md +0 -0
  123. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/swml_transfer/__init__.py +0 -0
  124. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/swml_transfer/skill.py +0 -0
  125. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/weather_api/README.md +0 -0
  126. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/weather_api/__init__.py +0 -0
  127. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/weather_api/skill.py +0 -0
  128. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/web_search/README.md +0 -0
  129. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/web_search/__init__.py +0 -0
  130. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/web_search/skill.py +0 -0
  131. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/wikipedia_search/README.md +0 -0
  132. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/wikipedia_search/__init__.py +0 -0
  133. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/skills/wikipedia_search/skill.py +0 -0
  134. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/utils/__init__.py +0 -0
  135. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/utils/pom_utils.py +0 -0
  136. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/utils/schema_utils.py +0 -0
  137. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/utils/token_generators.py +0 -0
  138. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents/utils/validators.py +0 -0
  139. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents.egg-info/dependency_links.txt +0 -0
  140. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents.egg-info/entry_points.txt +0 -0
  141. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents.egg-info/requires.txt +0 -0
  142. {signalwire_agents-0.1.43 → signalwire_agents-0.1.44}/signalwire_agents.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: signalwire_agents
3
- Version: 0.1.43
3
+ Version: 0.1.44
4
4
  Summary: SignalWire AI Agents SDK
5
5
  Author-email: SignalWire Team <info@signalwire.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "signalwire_agents"
7
- version = "0.1.43"
7
+ version = "0.1.44"
8
8
  description = "SignalWire AI Agents SDK"
9
9
  authors = [
10
10
  {name = "SignalWire Team", email = "info@signalwire.com"}
@@ -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.43"
21
+ __version__ = "0.1.44"
22
22
 
23
23
  # Import core classes for easier access
24
24
  from .core.agent_base import AgentBase
@@ -31,6 +31,9 @@ from signalwire_agents.core.function_result import SwaigFunctionResult
31
31
  from signalwire_agents.core.swaig_function import SWAIGFunction
32
32
  from signalwire_agents.agents.bedrock import BedrockAgent
33
33
 
34
+ # Import WebService for static file serving
35
+ from signalwire_agents.web import WebService
36
+
34
37
  # Lazy import skills to avoid slow startup for CLI tools
35
38
  # Skills are now loaded on-demand when requested
36
39
  def _get_skill_registry():
@@ -138,6 +141,7 @@ __all__ = [
138
141
  "Context",
139
142
  "Step",
140
143
  "create_simple_context",
144
+ "WebService",
141
145
  "start_agent",
142
146
  "run_agent",
143
147
  "list_skills",
@@ -0,0 +1,17 @@
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
+ """SignalWire Agents Web Service Module
11
+
12
+ This module provides static file serving capabilities for the SignalWire Agents SDK.
13
+ """
14
+
15
+ from .web_service import WebService
16
+
17
+ __all__ = ['WebService']
@@ -0,0 +1,559 @@
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
+ import os
11
+ import mimetypes
12
+ from pathlib import Path
13
+ from typing import Dict, Optional, Tuple, Any
14
+ import logging
15
+
16
+ try:
17
+ from fastapi import FastAPI, HTTPException, Request, Response, Depends
18
+ from fastapi.middleware.cors import CORSMiddleware
19
+ from fastapi.security import HTTPBasic, HTTPBasicCredentials
20
+ from fastapi.staticfiles import StaticFiles
21
+ from fastapi.responses import FileResponse, HTMLResponse
22
+ except ImportError:
23
+ FastAPI = None
24
+ HTTPException = None
25
+ Request = None
26
+ Response = None
27
+ Depends = None
28
+ CORSMiddleware = None
29
+ HTTPBasic = None
30
+ HTTPBasicCredentials = None
31
+ StaticFiles = None
32
+ FileResponse = None
33
+ HTMLResponse = None
34
+
35
+ from signalwire_agents.core.security_config import SecurityConfig
36
+ from signalwire_agents.core.config_loader import ConfigLoader
37
+ from signalwire_agents.core.logging_config import get_logger
38
+
39
+ logger = get_logger("web_service")
40
+
41
+ class WebService:
42
+ """Static file serving service with HTTP API"""
43
+
44
+ def __init__(self,
45
+ port: int = 8002,
46
+ directories: Dict[str, str] = None,
47
+ basic_auth: Optional[Tuple[str, str]] = None,
48
+ config_file: Optional[str] = None,
49
+ enable_directory_browsing: bool = False,
50
+ allowed_extensions: Optional[list] = None,
51
+ blocked_extensions: Optional[list] = None,
52
+ max_file_size: int = 100 * 1024 * 1024, # 100MB default
53
+ enable_cors: bool = True):
54
+ """
55
+ Initialize WebService
56
+
57
+ Args:
58
+ port: Port to bind to (default: 8002)
59
+ directories: Dict mapping URL paths to local directories
60
+ basic_auth: Optional tuple of (username, password)
61
+ config_file: Optional configuration file path
62
+ enable_directory_browsing: Allow directory listing
63
+ allowed_extensions: List of allowed file extensions (e.g., ['.html', '.css'])
64
+ blocked_extensions: List of blocked extensions (e.g., ['.env', '.git'])
65
+ max_file_size: Maximum file size in bytes to serve
66
+ enable_cors: Enable CORS support
67
+ """
68
+ # Load configuration first
69
+ self._load_config(config_file)
70
+
71
+ # Override with constructor params if provided
72
+ self.port = port
73
+ self.enable_directory_browsing = enable_directory_browsing
74
+ self.max_file_size = max_file_size
75
+ self.enable_cors = enable_cors
76
+
77
+ if directories is not None:
78
+ self.directories = directories
79
+
80
+ # Set up file extension filters
81
+ self.allowed_extensions = allowed_extensions
82
+ self.blocked_extensions = blocked_extensions or [
83
+ '.env', '.git', '.gitignore', '.key', '.pem', '.crt',
84
+ '.pyc', '__pycache__', '.DS_Store', '.swp'
85
+ ]
86
+
87
+ # Initialize mimetypes
88
+ mimetypes.init()
89
+ # Add custom MIME types if needed
90
+ mimetypes.add_type('application/javascript', '.js')
91
+ mimetypes.add_type('text/css', '.css')
92
+ mimetypes.add_type('application/json', '.json')
93
+
94
+ # Load security configuration
95
+ self.security = SecurityConfig(config_file=config_file, service_name="web")
96
+ self.security.log_config("WebService")
97
+
98
+ # Set up authentication
99
+ self._basic_auth = basic_auth or self.security.get_basic_auth()
100
+
101
+ if FastAPI:
102
+ self.app = FastAPI(
103
+ title="SignalWire Web Service",
104
+ description="Static file serving for SignalWire Agents"
105
+ )
106
+ self._setup_security()
107
+ self._setup_routes()
108
+ self._mount_directories()
109
+ else:
110
+ self.app = None
111
+ logger.warning("FastAPI not available. HTTP service will not be available.")
112
+
113
+ def _load_config(self, config_file: Optional[str]):
114
+ """Load configuration from file if available"""
115
+ # Initialize defaults
116
+ self.directories = {}
117
+ self.port = 8002
118
+
119
+ # Find config file
120
+ if not config_file:
121
+ config_file = ConfigLoader.find_config_file("web")
122
+
123
+ if not config_file:
124
+ return
125
+
126
+ # Load config
127
+ config_loader = ConfigLoader([config_file])
128
+ if not config_loader.has_config():
129
+ return
130
+
131
+ logger.info("loading_config_from_file", file=config_file)
132
+
133
+ # Get service section
134
+ service_config = config_loader.get_section('service')
135
+ if service_config:
136
+ if 'port' in service_config:
137
+ self.port = int(service_config['port'])
138
+
139
+ if 'directories' in service_config and isinstance(service_config['directories'], dict):
140
+ self.directories = service_config['directories']
141
+
142
+ if 'enable_directory_browsing' in service_config:
143
+ self.enable_directory_browsing = bool(service_config['enable_directory_browsing'])
144
+
145
+ if 'max_file_size' in service_config:
146
+ self.max_file_size = int(service_config['max_file_size'])
147
+
148
+ if 'allowed_extensions' in service_config:
149
+ self.allowed_extensions = service_config['allowed_extensions']
150
+
151
+ if 'blocked_extensions' in service_config:
152
+ self.blocked_extensions = service_config['blocked_extensions']
153
+
154
+ def _setup_security(self):
155
+ """Setup security middleware and authentication"""
156
+ if not self.app:
157
+ return
158
+
159
+ # Add CORS middleware if enabled
160
+ if self.enable_cors and CORSMiddleware:
161
+ self.app.add_middleware(
162
+ CORSMiddleware,
163
+ **self.security.get_cors_config()
164
+ )
165
+
166
+ # Add security headers middleware
167
+ @self.app.middleware("http")
168
+ async def add_security_headers(request: Request, call_next):
169
+ response = await call_next(request)
170
+
171
+ # Add security headers
172
+ is_https = request.url.scheme == "https"
173
+ headers = self.security.get_security_headers(is_https)
174
+ for header, value in headers.items():
175
+ response.headers[header] = value
176
+
177
+ # Add cache headers for static files
178
+ if request.url.path.startswith(tuple(self.directories.keys())):
179
+ # Cache static files for 1 hour
180
+ response.headers["Cache-Control"] = "public, max-age=3600"
181
+
182
+ return response
183
+
184
+ # Add host validation middleware
185
+ @self.app.middleware("http")
186
+ async def validate_host(request: Request, call_next):
187
+ host = request.headers.get("host", "").split(":")[0]
188
+ if host and not self.security.should_allow_host(host):
189
+ return Response(content="Invalid host", status_code=400)
190
+
191
+ return await call_next(request)
192
+
193
+ def _get_current_username(self, credentials: HTTPBasicCredentials = None) -> str:
194
+ """Validate basic auth credentials"""
195
+ if not credentials:
196
+ return None
197
+
198
+ correct_username, correct_password = self._basic_auth
199
+
200
+ # Compare credentials
201
+ import secrets
202
+ username_correct = secrets.compare_digest(credentials.username, correct_username)
203
+ password_correct = secrets.compare_digest(credentials.password, correct_password)
204
+
205
+ if not (username_correct and password_correct):
206
+ raise HTTPException(
207
+ status_code=401,
208
+ detail="Invalid authentication credentials",
209
+ headers={"WWW-Authenticate": "Basic"},
210
+ )
211
+
212
+ return credentials.username
213
+
214
+ def _is_file_allowed(self, file_path: Path) -> bool:
215
+ """Check if file is allowed to be served"""
216
+ # Check file size
217
+ if file_path.stat().st_size > self.max_file_size:
218
+ return False
219
+
220
+ # Check extension and name
221
+ ext = file_path.suffix.lower()
222
+ name = file_path.name
223
+
224
+ # Check blocked extensions and names
225
+ for blocked in self.blocked_extensions:
226
+ if blocked.startswith('.'):
227
+ # Check both as extension and as full name (for files like .env, .gitignore)
228
+ if ext == blocked or name == blocked:
229
+ return False
230
+ else:
231
+ if name == blocked or blocked in str(file_path):
232
+ return False
233
+
234
+ # If allowed_extensions is set, only allow those
235
+ if self.allowed_extensions:
236
+ return ext in self.allowed_extensions
237
+
238
+ return True
239
+
240
+ def _generate_directory_listing(self, directory: Path, url_path: str) -> str:
241
+ """Generate HTML directory listing"""
242
+ items = []
243
+
244
+ # Add parent directory link if not at root
245
+ if url_path != '/':
246
+ items.append('<li><a href="../">../</a></li>')
247
+
248
+ # List directories first
249
+ for item in sorted(directory.iterdir()):
250
+ if item.name.startswith('.'):
251
+ continue # Skip hidden files
252
+
253
+ if item.is_dir():
254
+ items.append(f'<li>📁 <a href="{item.name}/">{item.name}/</a></li>')
255
+
256
+ # Then list files
257
+ for item in sorted(directory.iterdir()):
258
+ if item.name.startswith('.'):
259
+ continue
260
+
261
+ if item.is_file() and self._is_file_allowed(item):
262
+ size = item.stat().st_size
263
+ if size < 1024:
264
+ size_str = f"{size} B"
265
+ elif size < 1024 * 1024:
266
+ size_str = f"{size / 1024:.1f} KB"
267
+ else:
268
+ size_str = f"{size / (1024 * 1024):.1f} MB"
269
+
270
+ items.append(f'<li>📄 <a href="{item.name}">{item.name}</a> ({size_str})</li>')
271
+
272
+ html = f"""
273
+ <!DOCTYPE html>
274
+ <html>
275
+ <head>
276
+ <title>Directory listing for {url_path}</title>
277
+ <style>
278
+ body {{ font-family: sans-serif; margin: 40px; }}
279
+ h1 {{ color: #333; }}
280
+ ul {{ list-style: none; padding: 0; }}
281
+ li {{ padding: 5px 0; }}
282
+ a {{ text-decoration: none; color: #0066cc; }}
283
+ a:hover {{ text-decoration: underline; }}
284
+ </style>
285
+ </head>
286
+ <body>
287
+ <h1>Directory listing for {url_path}</h1>
288
+ <ul>
289
+ {''.join(items)}
290
+ </ul>
291
+ </body>
292
+ </html>
293
+ """
294
+ return html
295
+
296
+ def _setup_routes(self):
297
+ """Setup FastAPI routes"""
298
+ if not self.app:
299
+ return
300
+
301
+ # Create security dependency if HTTPBasic is available
302
+ security = HTTPBasic() if HTTPBasic else None
303
+
304
+ @self.app.get("/health")
305
+ async def health():
306
+ return {
307
+ "status": "healthy",
308
+ "directories": list(self.directories.keys()),
309
+ "ssl_enabled": self.security.ssl_enabled,
310
+ "auth_required": bool(security),
311
+ "directory_browsing": self.enable_directory_browsing
312
+ }
313
+
314
+ @self.app.get("/")
315
+ async def root():
316
+ """Root endpoint showing available directories"""
317
+ html = """
318
+ <!DOCTYPE html>
319
+ <html>
320
+ <head>
321
+ <title>SignalWire Web Service</title>
322
+ <style>
323
+ body { font-family: sans-serif; margin: 40px; }
324
+ h1 { color: #333; }
325
+ ul { list-style: none; padding: 0; }
326
+ li { padding: 10px 0; }
327
+ a { text-decoration: none; color: #0066cc; font-size: 18px; }
328
+ a:hover { text-decoration: underline; }
329
+ .path { color: #666; font-size: 14px; }
330
+ </style>
331
+ </head>
332
+ <body>
333
+ <h1>SignalWire Web Service</h1>
334
+ <h2>Available Directories:</h2>
335
+ <ul>
336
+ """
337
+
338
+ for route, local_path in self.directories.items():
339
+ html += f'<li>📁 <a href="{route}">{route}</a> <span class="path">→ {local_path}</span></li>'
340
+
341
+ html += """
342
+ </ul>
343
+ </body>
344
+ </html>
345
+ """
346
+
347
+ if HTMLResponse:
348
+ return HTMLResponse(content=html)
349
+ else:
350
+ return {"directories": list(self.directories.keys())}
351
+
352
+ def _mount_directories(self):
353
+ """Mount static file directories"""
354
+ if not self.app or not StaticFiles:
355
+ return
356
+
357
+ # Create security dependency if HTTPBasic is available
358
+ security = HTTPBasic() if HTTPBasic else None
359
+
360
+ for route, directory in self.directories.items():
361
+ # Ensure directory exists
362
+ dir_path = Path(directory)
363
+ if not dir_path.exists():
364
+ logger.warning(f"Directory does not exist: {directory}")
365
+ continue
366
+
367
+ if not dir_path.is_dir():
368
+ logger.warning(f"Path is not a directory: {directory}")
369
+ continue
370
+
371
+ # Normalize route
372
+ if not route.startswith('/'):
373
+ route = '/' + route
374
+
375
+ logger.info(f"Mounting directory {directory} at route {route}")
376
+
377
+ # Create custom static file handler with our security checks
378
+ @self.app.get(f"{route}/{{file_path:path}}")
379
+ async def serve_file(
380
+ file_path: str,
381
+ request: Request,
382
+ credentials: HTTPBasicCredentials = None if not security else Depends(security),
383
+ route=route,
384
+ directory=directory
385
+ ):
386
+ """Serve files with security checks"""
387
+ if security:
388
+ self._get_current_username(credentials)
389
+
390
+ # Build full file path
391
+ full_path = Path(directory) / file_path
392
+
393
+ # Security: Prevent path traversal
394
+ try:
395
+ full_path = full_path.resolve()
396
+ dir_path = Path(directory).resolve()
397
+ if not str(full_path).startswith(str(dir_path)):
398
+ raise HTTPException(status_code=403, detail="Access denied")
399
+ except Exception:
400
+ raise HTTPException(status_code=403, detail="Invalid path")
401
+
402
+ # Check if path exists
403
+ if not full_path.exists():
404
+ raise HTTPException(status_code=404, detail="File not found")
405
+
406
+ # Handle directory requests
407
+ if full_path.is_dir():
408
+ if not self.enable_directory_browsing:
409
+ # Try to serve index.html if it exists
410
+ index_path = full_path / "index.html"
411
+ if index_path.exists() and self._is_file_allowed(index_path):
412
+ return FileResponse(index_path)
413
+ else:
414
+ raise HTTPException(status_code=403, detail="Directory browsing disabled")
415
+ else:
416
+ # Generate directory listing
417
+ html = self._generate_directory_listing(full_path, request.url.path)
418
+ if HTMLResponse:
419
+ return HTMLResponse(content=html)
420
+ else:
421
+ raise HTTPException(status_code=403, detail="Directory browsing not available")
422
+
423
+ # Check if file is allowed
424
+ if not self._is_file_allowed(full_path):
425
+ raise HTTPException(status_code=403, detail="File type not allowed")
426
+
427
+ # Serve the file
428
+ mime_type = mimetypes.guess_type(str(full_path))[0] or 'application/octet-stream'
429
+
430
+ if FileResponse:
431
+ return FileResponse(
432
+ full_path,
433
+ media_type=mime_type,
434
+ headers={
435
+ "Cache-Control": "public, max-age=3600",
436
+ "X-Content-Type-Options": "nosniff"
437
+ }
438
+ )
439
+ else:
440
+ # Fallback if FileResponse not available
441
+ with open(full_path, 'rb') as f:
442
+ content = f.read()
443
+ return Response(
444
+ content=content,
445
+ media_type=mime_type,
446
+ headers={
447
+ "Cache-Control": "public, max-age=3600",
448
+ "X-Content-Type-Options": "nosniff"
449
+ }
450
+ )
451
+
452
+ def add_directory(self, route: str, directory: str) -> None:
453
+ """
454
+ Add a new directory to serve
455
+
456
+ Args:
457
+ route: URL path to mount at (e.g., "/docs")
458
+ directory: Local directory path to serve
459
+ """
460
+ # Normalize route
461
+ if not route.startswith('/'):
462
+ route = '/' + route
463
+
464
+ # Verify directory exists
465
+ dir_path = Path(directory)
466
+ if not dir_path.exists():
467
+ raise ValueError(f"Directory does not exist: {directory}")
468
+
469
+ if not dir_path.is_dir():
470
+ raise ValueError(f"Path is not a directory: {directory}")
471
+
472
+ self.directories[route] = directory
473
+
474
+ # If app is already running, mount the new directory
475
+ if self.app:
476
+ self._mount_directories()
477
+
478
+ def remove_directory(self, route: str) -> None:
479
+ """
480
+ Remove a directory from being served
481
+
482
+ Args:
483
+ route: URL path to remove
484
+ """
485
+ # Normalize route
486
+ if not route.startswith('/'):
487
+ route = '/' + route
488
+
489
+ if route in self.directories:
490
+ del self.directories[route]
491
+
492
+ def start(self, host: str = "0.0.0.0", port: Optional[int] = None,
493
+ ssl_cert: Optional[str] = None, ssl_key: Optional[str] = None):
494
+ """
495
+ Start the service with optional HTTPS support
496
+
497
+ Args:
498
+ host: Host to bind to (default: "0.0.0.0")
499
+ port: Port to bind to (default: self.port)
500
+ ssl_cert: Path to SSL certificate file (overrides environment)
501
+ ssl_key: Path to SSL key file (overrides environment)
502
+ """
503
+ if not self.app:
504
+ raise RuntimeError("FastAPI not available. Cannot start HTTP service.")
505
+
506
+ port = port or self.port
507
+
508
+ # Get SSL configuration
509
+ ssl_kwargs = {}
510
+ if ssl_cert and ssl_key:
511
+ # Use provided SSL files
512
+ ssl_kwargs = {
513
+ 'ssl_certfile': ssl_cert,
514
+ 'ssl_keyfile': ssl_key
515
+ }
516
+ else:
517
+ # Use security config SSL settings
518
+ ssl_kwargs = self.security.get_ssl_context_kwargs()
519
+
520
+ # Build startup URL
521
+ scheme = "https" if ssl_kwargs else "http"
522
+ startup_url = f"{scheme}://{host}:{port}"
523
+
524
+ # Get auth credentials
525
+ username, password = self._basic_auth
526
+
527
+ # Log startup information
528
+ logger.info(
529
+ "starting_web_service",
530
+ url=startup_url,
531
+ ssl_enabled=bool(ssl_kwargs),
532
+ directories=list(self.directories.keys()),
533
+ username=username
534
+ )
535
+
536
+ # Print user-friendly startup message
537
+ print(f"\nSignalWire Web Service starting...")
538
+ print(f"URL: {startup_url}")
539
+ print(f"Directories: {', '.join(self.directories.keys()) if self.directories else 'None'}")
540
+ print(f"Basic Auth: {username}:{password}")
541
+ print(f"Directory Browsing: {'Enabled' if self.enable_directory_browsing else 'Disabled'}")
542
+ if ssl_kwargs:
543
+ print(f"SSL: Enabled")
544
+ print("")
545
+
546
+ try:
547
+ import uvicorn
548
+ uvicorn.run(
549
+ self.app,
550
+ host=host,
551
+ port=port,
552
+ **ssl_kwargs
553
+ )
554
+ except ImportError:
555
+ raise RuntimeError("uvicorn not available. Cannot start HTTP service.")
556
+
557
+ def stop(self):
558
+ """Stop the service (placeholder for cleanup)"""
559
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: signalwire_agents
3
- Version: 0.1.43
3
+ Version: 0.1.44
4
4
  Summary: SignalWire AI Agents SDK
5
5
  Author-email: SignalWire Team <info@signalwire.com>
6
6
  License: MIT
@@ -135,4 +135,6 @@ signalwire_agents/utils/__init__.py
135
135
  signalwire_agents/utils/pom_utils.py
136
136
  signalwire_agents/utils/schema_utils.py
137
137
  signalwire_agents/utils/token_generators.py
138
- signalwire_agents/utils/validators.py
138
+ signalwire_agents/utils/validators.py
139
+ signalwire_agents/web/__init__.py
140
+ signalwire_agents/web/web_service.py