atlas-chat 0.1.0__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 (250) hide show
  1. atlas/__init__.py +40 -0
  2. atlas/application/__init__.py +7 -0
  3. atlas/application/chat/__init__.py +7 -0
  4. atlas/application/chat/agent/__init__.py +10 -0
  5. atlas/application/chat/agent/act_loop.py +179 -0
  6. atlas/application/chat/agent/factory.py +142 -0
  7. atlas/application/chat/agent/protocols.py +46 -0
  8. atlas/application/chat/agent/react_loop.py +338 -0
  9. atlas/application/chat/agent/think_act_loop.py +171 -0
  10. atlas/application/chat/approval_manager.py +151 -0
  11. atlas/application/chat/elicitation_manager.py +191 -0
  12. atlas/application/chat/events/__init__.py +1 -0
  13. atlas/application/chat/events/agent_event_relay.py +112 -0
  14. atlas/application/chat/modes/__init__.py +1 -0
  15. atlas/application/chat/modes/agent.py +125 -0
  16. atlas/application/chat/modes/plain.py +74 -0
  17. atlas/application/chat/modes/rag.py +81 -0
  18. atlas/application/chat/modes/tools.py +179 -0
  19. atlas/application/chat/orchestrator.py +213 -0
  20. atlas/application/chat/policies/__init__.py +1 -0
  21. atlas/application/chat/policies/tool_authorization.py +99 -0
  22. atlas/application/chat/preprocessors/__init__.py +1 -0
  23. atlas/application/chat/preprocessors/message_builder.py +92 -0
  24. atlas/application/chat/preprocessors/prompt_override_service.py +104 -0
  25. atlas/application/chat/service.py +454 -0
  26. atlas/application/chat/utilities/__init__.py +6 -0
  27. atlas/application/chat/utilities/error_handler.py +367 -0
  28. atlas/application/chat/utilities/event_notifier.py +546 -0
  29. atlas/application/chat/utilities/file_processor.py +613 -0
  30. atlas/application/chat/utilities/tool_executor.py +789 -0
  31. atlas/atlas_chat_cli.py +347 -0
  32. atlas/atlas_client.py +238 -0
  33. atlas/core/__init__.py +0 -0
  34. atlas/core/auth.py +205 -0
  35. atlas/core/authorization_manager.py +27 -0
  36. atlas/core/capabilities.py +123 -0
  37. atlas/core/compliance.py +215 -0
  38. atlas/core/domain_whitelist.py +147 -0
  39. atlas/core/domain_whitelist_middleware.py +82 -0
  40. atlas/core/http_client.py +28 -0
  41. atlas/core/log_sanitizer.py +102 -0
  42. atlas/core/metrics_logger.py +59 -0
  43. atlas/core/middleware.py +131 -0
  44. atlas/core/otel_config.py +242 -0
  45. atlas/core/prompt_risk.py +200 -0
  46. atlas/core/rate_limit.py +0 -0
  47. atlas/core/rate_limit_middleware.py +64 -0
  48. atlas/core/security_headers_middleware.py +51 -0
  49. atlas/domain/__init__.py +37 -0
  50. atlas/domain/chat/__init__.py +1 -0
  51. atlas/domain/chat/dtos.py +85 -0
  52. atlas/domain/errors.py +96 -0
  53. atlas/domain/messages/__init__.py +12 -0
  54. atlas/domain/messages/models.py +160 -0
  55. atlas/domain/rag_mcp_service.py +664 -0
  56. atlas/domain/sessions/__init__.py +7 -0
  57. atlas/domain/sessions/models.py +36 -0
  58. atlas/domain/unified_rag_service.py +371 -0
  59. atlas/infrastructure/__init__.py +10 -0
  60. atlas/infrastructure/app_factory.py +135 -0
  61. atlas/infrastructure/events/__init__.py +1 -0
  62. atlas/infrastructure/events/cli_event_publisher.py +140 -0
  63. atlas/infrastructure/events/websocket_publisher.py +140 -0
  64. atlas/infrastructure/sessions/in_memory_repository.py +56 -0
  65. atlas/infrastructure/transport/__init__.py +7 -0
  66. atlas/infrastructure/transport/websocket_connection_adapter.py +33 -0
  67. atlas/init_cli.py +226 -0
  68. atlas/interfaces/__init__.py +15 -0
  69. atlas/interfaces/events.py +134 -0
  70. atlas/interfaces/llm.py +54 -0
  71. atlas/interfaces/rag.py +40 -0
  72. atlas/interfaces/sessions.py +75 -0
  73. atlas/interfaces/tools.py +57 -0
  74. atlas/interfaces/transport.py +24 -0
  75. atlas/main.py +564 -0
  76. atlas/mcp/api_key_demo/README.md +76 -0
  77. atlas/mcp/api_key_demo/main.py +172 -0
  78. atlas/mcp/api_key_demo/run.sh +56 -0
  79. atlas/mcp/basictable/main.py +147 -0
  80. atlas/mcp/calculator/main.py +149 -0
  81. atlas/mcp/code-executor/execution_engine.py +98 -0
  82. atlas/mcp/code-executor/execution_environment.py +95 -0
  83. atlas/mcp/code-executor/main.py +528 -0
  84. atlas/mcp/code-executor/result_processing.py +276 -0
  85. atlas/mcp/code-executor/script_generation.py +195 -0
  86. atlas/mcp/code-executor/security_checker.py +140 -0
  87. atlas/mcp/corporate_cars/main.py +437 -0
  88. atlas/mcp/csv_reporter/main.py +545 -0
  89. atlas/mcp/duckduckgo/main.py +182 -0
  90. atlas/mcp/elicitation_demo/README.md +171 -0
  91. atlas/mcp/elicitation_demo/main.py +262 -0
  92. atlas/mcp/env-demo/README.md +158 -0
  93. atlas/mcp/env-demo/main.py +199 -0
  94. atlas/mcp/file_size_test/main.py +284 -0
  95. atlas/mcp/filesystem/main.py +348 -0
  96. atlas/mcp/image_demo/main.py +113 -0
  97. atlas/mcp/image_demo/requirements.txt +4 -0
  98. atlas/mcp/logging_demo/README.md +72 -0
  99. atlas/mcp/logging_demo/main.py +103 -0
  100. atlas/mcp/many_tools_demo/main.py +50 -0
  101. atlas/mcp/order_database/__init__.py +0 -0
  102. atlas/mcp/order_database/main.py +369 -0
  103. atlas/mcp/order_database/signal_data.csv +1001 -0
  104. atlas/mcp/pdfbasic/main.py +394 -0
  105. atlas/mcp/pptx_generator/main.py +760 -0
  106. atlas/mcp/pptx_generator/requirements.txt +13 -0
  107. atlas/mcp/pptx_generator/run_test.sh +1 -0
  108. atlas/mcp/pptx_generator/test_pptx_generator_security.py +169 -0
  109. atlas/mcp/progress_demo/main.py +167 -0
  110. atlas/mcp/progress_updates_demo/QUICKSTART.md +273 -0
  111. atlas/mcp/progress_updates_demo/README.md +120 -0
  112. atlas/mcp/progress_updates_demo/main.py +497 -0
  113. atlas/mcp/prompts/main.py +222 -0
  114. atlas/mcp/public_demo/main.py +189 -0
  115. atlas/mcp/sampling_demo/README.md +169 -0
  116. atlas/mcp/sampling_demo/main.py +234 -0
  117. atlas/mcp/thinking/main.py +77 -0
  118. atlas/mcp/tool_planner/main.py +240 -0
  119. atlas/mcp/ui-demo/badmesh.png +0 -0
  120. atlas/mcp/ui-demo/main.py +383 -0
  121. atlas/mcp/ui-demo/templates/button_demo.html +32 -0
  122. atlas/mcp/ui-demo/templates/data_visualization.html +32 -0
  123. atlas/mcp/ui-demo/templates/form_demo.html +28 -0
  124. atlas/mcp/username-override-demo/README.md +320 -0
  125. atlas/mcp/username-override-demo/main.py +308 -0
  126. atlas/modules/__init__.py +0 -0
  127. atlas/modules/config/__init__.py +34 -0
  128. atlas/modules/config/cli.py +231 -0
  129. atlas/modules/config/config_manager.py +1096 -0
  130. atlas/modules/file_storage/__init__.py +22 -0
  131. atlas/modules/file_storage/cli.py +330 -0
  132. atlas/modules/file_storage/content_extractor.py +290 -0
  133. atlas/modules/file_storage/manager.py +295 -0
  134. atlas/modules/file_storage/mock_s3_client.py +402 -0
  135. atlas/modules/file_storage/s3_client.py +417 -0
  136. atlas/modules/llm/__init__.py +19 -0
  137. atlas/modules/llm/caller.py +287 -0
  138. atlas/modules/llm/litellm_caller.py +675 -0
  139. atlas/modules/llm/models.py +19 -0
  140. atlas/modules/mcp_tools/__init__.py +17 -0
  141. atlas/modules/mcp_tools/client.py +2123 -0
  142. atlas/modules/mcp_tools/token_storage.py +556 -0
  143. atlas/modules/prompts/prompt_provider.py +130 -0
  144. atlas/modules/rag/__init__.py +24 -0
  145. atlas/modules/rag/atlas_rag_client.py +336 -0
  146. atlas/modules/rag/client.py +129 -0
  147. atlas/routes/admin_routes.py +865 -0
  148. atlas/routes/config_routes.py +484 -0
  149. atlas/routes/feedback_routes.py +361 -0
  150. atlas/routes/files_routes.py +274 -0
  151. atlas/routes/health_routes.py +40 -0
  152. atlas/routes/mcp_auth_routes.py +223 -0
  153. atlas/server_cli.py +164 -0
  154. atlas/tests/conftest.py +20 -0
  155. atlas/tests/integration/test_mcp_auth_integration.py +152 -0
  156. atlas/tests/manual_test_sampling.py +87 -0
  157. atlas/tests/modules/mcp_tools/test_client_auth.py +226 -0
  158. atlas/tests/modules/mcp_tools/test_client_env.py +191 -0
  159. atlas/tests/test_admin_mcp_server_management_routes.py +141 -0
  160. atlas/tests/test_agent_roa.py +135 -0
  161. atlas/tests/test_app_factory_smoke.py +47 -0
  162. atlas/tests/test_approval_manager.py +439 -0
  163. atlas/tests/test_atlas_client.py +188 -0
  164. atlas/tests/test_atlas_rag_client.py +447 -0
  165. atlas/tests/test_atlas_rag_integration.py +224 -0
  166. atlas/tests/test_attach_file_flow.py +287 -0
  167. atlas/tests/test_auth_utils.py +165 -0
  168. atlas/tests/test_backend_public_url.py +185 -0
  169. atlas/tests/test_banner_logging.py +287 -0
  170. atlas/tests/test_capability_tokens_and_injection.py +203 -0
  171. atlas/tests/test_compliance_level.py +54 -0
  172. atlas/tests/test_compliance_manager.py +253 -0
  173. atlas/tests/test_config_manager.py +617 -0
  174. atlas/tests/test_config_manager_paths.py +12 -0
  175. atlas/tests/test_core_auth.py +18 -0
  176. atlas/tests/test_core_utils.py +190 -0
  177. atlas/tests/test_docker_env_sync.py +202 -0
  178. atlas/tests/test_domain_errors.py +329 -0
  179. atlas/tests/test_domain_whitelist.py +359 -0
  180. atlas/tests/test_elicitation_manager.py +408 -0
  181. atlas/tests/test_elicitation_routing.py +296 -0
  182. atlas/tests/test_env_demo_server.py +88 -0
  183. atlas/tests/test_error_classification.py +113 -0
  184. atlas/tests/test_error_flow_integration.py +116 -0
  185. atlas/tests/test_feedback_routes.py +333 -0
  186. atlas/tests/test_file_content_extraction.py +1134 -0
  187. atlas/tests/test_file_extraction_routes.py +158 -0
  188. atlas/tests/test_file_library.py +107 -0
  189. atlas/tests/test_file_manager_unit.py +18 -0
  190. atlas/tests/test_health_route.py +49 -0
  191. atlas/tests/test_http_client_stub.py +8 -0
  192. atlas/tests/test_imports_smoke.py +30 -0
  193. atlas/tests/test_interfaces_llm_response.py +9 -0
  194. atlas/tests/test_issue_access_denied_fix.py +136 -0
  195. atlas/tests/test_llm_env_expansion.py +836 -0
  196. atlas/tests/test_log_level_sensitive_data.py +285 -0
  197. atlas/tests/test_mcp_auth_routes.py +341 -0
  198. atlas/tests/test_mcp_client_auth.py +331 -0
  199. atlas/tests/test_mcp_data_injection.py +270 -0
  200. atlas/tests/test_mcp_get_authorized_servers.py +95 -0
  201. atlas/tests/test_mcp_hot_reload.py +512 -0
  202. atlas/tests/test_mcp_image_content.py +424 -0
  203. atlas/tests/test_mcp_logging.py +172 -0
  204. atlas/tests/test_mcp_progress_updates.py +313 -0
  205. atlas/tests/test_mcp_prompt_override_system_prompt.py +102 -0
  206. atlas/tests/test_mcp_prompts_server.py +39 -0
  207. atlas/tests/test_mcp_tool_result_parsing.py +296 -0
  208. atlas/tests/test_metrics_logger.py +56 -0
  209. atlas/tests/test_middleware_auth.py +379 -0
  210. atlas/tests/test_prompt_risk_and_acl.py +141 -0
  211. atlas/tests/test_rag_mcp_aggregator.py +204 -0
  212. atlas/tests/test_rag_mcp_service.py +224 -0
  213. atlas/tests/test_rate_limit_middleware.py +45 -0
  214. atlas/tests/test_routes_config_smoke.py +60 -0
  215. atlas/tests/test_routes_files_download_token.py +41 -0
  216. atlas/tests/test_routes_files_health.py +18 -0
  217. atlas/tests/test_runtime_imports.py +53 -0
  218. atlas/tests/test_sampling_integration.py +482 -0
  219. atlas/tests/test_security_admin_routes.py +61 -0
  220. atlas/tests/test_security_capability_tokens.py +65 -0
  221. atlas/tests/test_security_file_stats_scope.py +21 -0
  222. atlas/tests/test_security_header_injection.py +191 -0
  223. atlas/tests/test_security_headers_and_filename.py +63 -0
  224. atlas/tests/test_shared_session_repository.py +101 -0
  225. atlas/tests/test_system_prompt_loading.py +181 -0
  226. atlas/tests/test_token_storage.py +505 -0
  227. atlas/tests/test_tool_approval_config.py +93 -0
  228. atlas/tests/test_tool_approval_utils.py +356 -0
  229. atlas/tests/test_tool_authorization_group_filtering.py +223 -0
  230. atlas/tests/test_tool_details_in_config.py +108 -0
  231. atlas/tests/test_tool_planner.py +300 -0
  232. atlas/tests/test_unified_rag_service.py +398 -0
  233. atlas/tests/test_username_override_in_approval.py +258 -0
  234. atlas/tests/test_websocket_auth_header.py +168 -0
  235. atlas/version.py +6 -0
  236. atlas_chat-0.1.0.data/data/.env.example +253 -0
  237. atlas_chat-0.1.0.data/data/config/defaults/compliance-levels.json +44 -0
  238. atlas_chat-0.1.0.data/data/config/defaults/domain-whitelist.json +123 -0
  239. atlas_chat-0.1.0.data/data/config/defaults/file-extractors.json +74 -0
  240. atlas_chat-0.1.0.data/data/config/defaults/help-config.json +198 -0
  241. atlas_chat-0.1.0.data/data/config/defaults/llmconfig-buggy.yml +11 -0
  242. atlas_chat-0.1.0.data/data/config/defaults/llmconfig.yml +19 -0
  243. atlas_chat-0.1.0.data/data/config/defaults/mcp.json +138 -0
  244. atlas_chat-0.1.0.data/data/config/defaults/rag-sources.json +17 -0
  245. atlas_chat-0.1.0.data/data/config/defaults/splash-config.json +16 -0
  246. atlas_chat-0.1.0.dist-info/METADATA +236 -0
  247. atlas_chat-0.1.0.dist-info/RECORD +250 -0
  248. atlas_chat-0.1.0.dist-info/WHEEL +5 -0
  249. atlas_chat-0.1.0.dist-info/entry_points.txt +4 -0
  250. atlas_chat-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,484 @@
1
+ """Configuration API routes."""
2
+
3
+ import logging
4
+ from typing import Optional
5
+
6
+ from fastapi import APIRouter, Depends
7
+
8
+ from atlas.core.auth import is_user_in_group
9
+ from atlas.core.log_sanitizer import get_current_user, sanitize_for_logging
10
+ from atlas.infrastructure.app_factory import app_factory
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ router = APIRouter(prefix="/api", tags=["config"])
15
+
16
+ # Canvas tool description constant
17
+ CANVAS_TOOL_DESCRIPTION = (
18
+ "Display final rendered content in a visual canvas panel. "
19
+ "Use this for: 1) Complete code (not code discussions), "
20
+ "2) Final reports/documents (not report discussions), "
21
+ "3) Data visualizations, 4) Any polished content that should be "
22
+ "viewed separately from the conversation."
23
+ )
24
+
25
+
26
+ @router.get("/banners")
27
+ async def get_banners(current_user: str = Depends(get_current_user)):
28
+ """Get banners for the user."""
29
+ config_manager = app_factory.get_config_manager()
30
+ app_settings = config_manager.app_settings
31
+
32
+ # Check if banners are enabled
33
+ if not app_settings.banner_enabled:
34
+ return {"messages": []}
35
+
36
+ # Read messages from messages.txt file
37
+ try:
38
+ from pathlib import Path
39
+
40
+ # Use app settings for config path
41
+ base = Path(app_settings.app_config_overrides)
42
+
43
+ # If relative path, resolve from project root
44
+ if not base.is_absolute():
45
+ project_root = Path(__file__).parent.parent.parent
46
+ base = project_root / base
47
+
48
+ messages_file = base / app_settings.messages_config_file
49
+
50
+ if messages_file.exists():
51
+ with open(messages_file, "r", encoding="utf-8") as f:
52
+ content = f.read()
53
+ messages = [line.strip() for line in content.splitlines() if line.strip()]
54
+ return {"messages": messages}
55
+ else:
56
+ return {"messages": []}
57
+ except Exception as e:
58
+ logger.error(f"Error reading banner messages: {e}")
59
+ return {"messages": []}
60
+
61
+
62
+ @router.get("/config")
63
+ async def get_config(
64
+ current_user: str = Depends(get_current_user),
65
+ compliance_level: Optional[str] = None,
66
+ ):
67
+ """Get available models, tools, and data sources for the user.
68
+ Only returns MCP servers and tools that the user is authorized to access.
69
+ """
70
+ config_manager = app_factory.get_config_manager()
71
+ llm_config = config_manager.llm_config
72
+ app_settings = config_manager.app_settings
73
+
74
+ # Get RAG data sources for the user from unified RAG service
75
+ rag_data_sources = []
76
+ rag_servers = []
77
+ # Only attempt RAG discovery if RAG feature is enabled
78
+ if app_settings.feature_rag_enabled:
79
+ # Discover HTTP and MCP RAG sources independently (best-effort)
80
+ # so a failure in one type does not prevent discovery of the other
81
+ try:
82
+ unified_rag = app_factory.get_unified_rag_service()
83
+ if unified_rag:
84
+ http_rag_servers = await unified_rag.discover_data_sources(
85
+ current_user, user_compliance_level=compliance_level
86
+ )
87
+ rag_servers.extend(http_rag_servers)
88
+ except Exception as e:
89
+ logger.warning("Error discovering HTTP RAG sources: %s", e)
90
+
91
+ try:
92
+ rag_mcp = app_factory.get_rag_mcp_service()
93
+ if rag_mcp:
94
+ mcp_rag_servers = await rag_mcp.discover_servers(
95
+ current_user, user_compliance_level=compliance_level
96
+ )
97
+ rag_servers.extend(mcp_rag_servers)
98
+ except Exception as e:
99
+ logger.warning("Error discovering MCP RAG sources: %s", e)
100
+
101
+ # Build flat list of data sources for backward compatibility
102
+ # Format: "server:source_id" for qualified references
103
+ for server in rag_servers:
104
+ server_name = server.get("server", "")
105
+ for source in server.get("sources", []):
106
+ source_id = source.get("id", "")
107
+ if server_name and source_id:
108
+ rag_data_sources.append(f"{server_name}:{source_id}")
109
+
110
+ # Check if tools are enabled
111
+ tools_info = []
112
+ prompts_info = []
113
+ authorized_servers = []
114
+
115
+ if app_settings.feature_tools_enabled:
116
+ # Get MCP manager
117
+ mcp_manager = app_factory.get_mcp_manager()
118
+
119
+ # Get authorized servers for the user - this filters out unauthorized servers completely
120
+ authorized_servers = await mcp_manager.get_authorized_servers(current_user, is_user_in_group)
121
+
122
+ # Add canvas pseudo-tool to authorized servers (available to all users)
123
+ authorized_servers.append("canvas")
124
+
125
+ # Only build tool information for servers the user is authorized to access
126
+ for server_name in authorized_servers:
127
+ # Handle canvas pseudo-tool
128
+ if server_name == "canvas":
129
+ tools_info.append({
130
+ 'server': 'canvas',
131
+ 'tools': ['canvas'],
132
+ 'tools_detailed': [{
133
+ 'name': 'canvas',
134
+ 'description': CANVAS_TOOL_DESCRIPTION,
135
+ 'inputSchema': {
136
+ 'type': 'object',
137
+ 'properties': {
138
+ 'content': {
139
+ 'type': 'string',
140
+ 'description': 'The content to display in the canvas. Can be markdown, code, or plain text.'
141
+ }
142
+ },
143
+ 'required': ['content']
144
+ }
145
+ }],
146
+ 'tool_count': 1,
147
+ 'description': 'Canvas for showing final rendered content: complete code, reports, and polished documents. Use this to finalize your work. Most code and reports will be shown here.',
148
+ 'author': 'Chat UI Team',
149
+ 'short_description': 'Visual content display',
150
+ 'help_email': 'support@chatui.example.com',
151
+ 'compliance_level': 'Public'
152
+ })
153
+ elif server_name in mcp_manager.available_tools:
154
+ server_tools = mcp_manager.available_tools[server_name]['tools']
155
+ server_config = mcp_manager.available_tools[server_name]['config']
156
+
157
+ # Only include servers that have tools and user has access to
158
+ if server_tools: # Only show servers with actual tools
159
+ # Build detailed tool information including descriptions and input schemas
160
+ tools_detailed = []
161
+ for tool in server_tools:
162
+ tool_detail = {
163
+ 'name': tool.name,
164
+ 'description': tool.description or '',
165
+ 'inputSchema': getattr(tool, 'inputSchema', {}) or {}
166
+ }
167
+ tools_detailed.append(tool_detail)
168
+
169
+ # Determine auth_type from server config
170
+ auth_type = server_config.get('auth_type', 'none')
171
+ auth_required = auth_type in ('jwt', 'bearer', 'oauth', 'api_key')
172
+
173
+ tools_info.append({
174
+ 'server': server_name,
175
+ 'tools': [tool.name for tool in server_tools],
176
+ 'tools_detailed': tools_detailed,
177
+ 'tool_count': len(server_tools),
178
+ 'description': server_config.get('description', f'{server_name} tools'),
179
+ 'author': server_config.get('author', 'Unknown'),
180
+ 'short_description': server_config.get('short_description', server_config.get('description', f'{server_name} tools')),
181
+ 'help_email': server_config.get('help_email', ''),
182
+ 'compliance_level': server_config.get('compliance_level'),
183
+ 'auth_type': auth_type,
184
+ 'auth_required': auth_required
185
+ })
186
+
187
+ # Collect prompts from this server if available
188
+ if server_name in mcp_manager.available_prompts:
189
+ server_prompts = mcp_manager.available_prompts[server_name]['prompts']
190
+ server_config = mcp_manager.available_prompts[server_name]['config']
191
+ if server_prompts: # Only show servers with actual prompts
192
+ prompts_info.append({
193
+ 'server': server_name,
194
+ 'prompts': [{'name': prompt.name, 'description': prompt.description} for prompt in server_prompts],
195
+ 'prompt_count': len(server_prompts),
196
+ 'description': f'{server_name} custom prompts',
197
+ 'author': server_config.get('author', 'Unknown'),
198
+ 'short_description': server_config.get('short_description', f'{server_name} custom prompts'),
199
+ 'help_email': server_config.get('help_email', ''),
200
+ 'compliance_level': server_config.get('compliance_level')
201
+ })
202
+
203
+ # Read help page configuration (supports new config directory layout + legacy paths)
204
+ help_config = {}
205
+ import json
206
+ help_config_filename = config_manager.app_settings.help_config_file
207
+ help_paths = []
208
+ try:
209
+ # Reuse config manager search logic (private but acceptable for now)
210
+ try:
211
+ help_paths = config_manager._search_paths(help_config_filename) # type: ignore[attr-defined]
212
+ except AttributeError:
213
+ # Fallback minimal search if method renamed/removed
214
+ from pathlib import Path
215
+ backend_root = Path(__file__).parent.parent
216
+ project_root = backend_root.parent
217
+ help_paths = [
218
+ project_root / "config" / "overrides" / help_config_filename,
219
+ project_root / "config" / "defaults" / help_config_filename,
220
+ backend_root / "configfilesadmin" / help_config_filename,
221
+ backend_root / "configfiles" / help_config_filename,
222
+ backend_root / help_config_filename,
223
+ project_root / help_config_filename,
224
+ ]
225
+
226
+ found_path = None
227
+ for p in help_paths:
228
+ if p.exists():
229
+ found_path = p
230
+ break
231
+ if found_path:
232
+ with open(found_path, "r", encoding="utf-8") as f:
233
+ help_config = json.load(f)
234
+ logger.info(f"Loaded help config from {found_path}")
235
+ else:
236
+ logger.warning(
237
+ "Help config not found in any of these locations: %s",
238
+ [str(p) for p in help_paths]
239
+ )
240
+ help_config = {"title": "Help & Documentation", "sections": []}
241
+ except Exception as e:
242
+ logger.warning(f"Error loading help config: {e}")
243
+ help_config = {"title": "Help & Documentation", "sections": []}
244
+
245
+ # Keep INFO logging concise; server lists can be very long.
246
+ logger.info(
247
+ "Config for user %s: %d authorized servers, %d tool groups",
248
+ sanitize_for_logging(current_user),
249
+ len(authorized_servers),
250
+ len(tools_info),
251
+ )
252
+ logger.debug(
253
+ "Authorized servers for user %s: %s",
254
+ sanitize_for_logging(current_user),
255
+ authorized_servers,
256
+ )
257
+ # Build models list with compliance levels
258
+ models_list = []
259
+ for model_name, model_config in llm_config.models.items():
260
+ model_info = {
261
+ "name": model_name,
262
+ "description": model_config.description,
263
+ }
264
+ # Include compliance_level if feature is enabled
265
+ if app_settings.feature_compliance_levels_enabled and model_config.compliance_level:
266
+ model_info["compliance_level"] = model_config.compliance_level
267
+ models_list.append(model_info)
268
+
269
+ # Build tool approval settings - only include tools from authorized servers
270
+ tool_approvals_config = config_manager.tool_approvals_config
271
+ filtered_tool_approvals = {}
272
+
273
+ # Get all tool names from authorized servers
274
+ authorized_tool_names = set()
275
+ for tool_group in tools_info:
276
+ server_name = tool_group.get('server')
277
+ if server_name in authorized_servers:
278
+ # tools is a list of strings (tool names), not dicts
279
+ for tool_name in tool_group.get('tools', []):
280
+ if isinstance(tool_name, str):
281
+ authorized_tool_names.add(tool_name)
282
+
283
+ # Only include approval settings for tools the user has access to
284
+ for tool_name, approval_config in tool_approvals_config.tools.items():
285
+ if tool_name in authorized_tool_names:
286
+ filtered_tool_approvals[tool_name] = {
287
+ "require_approval": approval_config.require_approval,
288
+ "allow_edit": approval_config.allow_edit
289
+ }
290
+
291
+ return {
292
+ "app_name": app_settings.app_name,
293
+ "models": models_list,
294
+ "tools": tools_info, # Only authorized servers are included
295
+ "prompts": prompts_info, # Available prompts from authorized servers
296
+ "data_sources": rag_data_sources, # RAG data sources for the user
297
+ "rag_servers": rag_servers, # Optional richer structure for RAG UI
298
+ "user": current_user,
299
+ "is_in_admin_group": await is_user_in_group(current_user, app_settings.admin_group),
300
+ "active_sessions": 0, # TODO: Implement session counting in ChatService
301
+ "authorized_servers": authorized_servers, # Optional: expose for debugging
302
+ "agent_mode_available": app_settings.agent_mode_available, # Whether agent mode UI should be shown
303
+ "banner_enabled": app_settings.banner_enabled, # Whether banner system is enabled
304
+ "help_config": help_config, # Help page configuration from help-config.json
305
+ "tool_approvals": {
306
+ "require_approval_by_default": tool_approvals_config.require_approval_by_default,
307
+ "tools": filtered_tool_approvals
308
+ },
309
+ "features": {
310
+ "workspaces": app_settings.feature_workspaces_enabled,
311
+ "rag": app_settings.feature_rag_enabled,
312
+ "tools": app_settings.feature_tools_enabled,
313
+ "marketplace": app_settings.feature_marketplace_enabled,
314
+ "files_panel": app_settings.feature_files_panel_enabled,
315
+ "chat_history": app_settings.feature_chat_history_enabled,
316
+ "compliance_levels": app_settings.feature_compliance_levels_enabled,
317
+ "splash_screen": app_settings.feature_splash_screen_enabled,
318
+ "file_content_extraction": app_settings.feature_file_content_extraction_enabled
319
+ },
320
+ "file_extraction": _get_file_extraction_config(config_manager)
321
+ }
322
+
323
+
324
+ def _get_file_extraction_config(config_manager) -> dict:
325
+ """Build file extraction config for frontend."""
326
+ app_settings = config_manager.app_settings
327
+
328
+ if not app_settings.feature_file_content_extraction_enabled:
329
+ return {
330
+ "enabled": False,
331
+ "default_behavior": "none",
332
+ "supported_extensions": []
333
+ }
334
+
335
+ try:
336
+ extractors_config = config_manager.file_extractors_config
337
+
338
+ # Get list of extensions with enabled extractors
339
+ supported_extensions = []
340
+ for ext, extractor_name in extractors_config.extension_mapping.items():
341
+ extractor = extractors_config.extractors.get(extractor_name)
342
+ if extractor and extractor.enabled:
343
+ supported_extensions.append(ext)
344
+
345
+ return {
346
+ "enabled": extractors_config.enabled,
347
+ "default_behavior": extractors_config.default_behavior,
348
+ "supported_extensions": sorted(supported_extensions)
349
+ }
350
+ except Exception as e:
351
+ logger.warning(f"Error building file extraction config: {e}")
352
+ return {
353
+ "enabled": False,
354
+ "default_behavior": "none",
355
+ "supported_extensions": []
356
+ }
357
+
358
+
359
+ @router.get("/compliance-levels")
360
+ async def get_compliance_levels(current_user: str = Depends(get_current_user)):
361
+ """Get compliance level definitions and allowlist."""
362
+ try:
363
+ from atlas.core.compliance import get_compliance_manager
364
+ compliance_mgr = get_compliance_manager()
365
+
366
+ # Return level definitions for frontend use
367
+ levels = []
368
+ for name, level_obj in compliance_mgr.levels.items():
369
+ levels.append({
370
+ "name": name,
371
+ "description": level_obj.description,
372
+ "aliases": level_obj.aliases,
373
+ "allowed_with": level_obj.allowed_with
374
+ })
375
+
376
+ return {
377
+ "levels": levels,
378
+ "mode": compliance_mgr.mode,
379
+ "all_level_names": compliance_mgr.get_all_levels()
380
+ }
381
+ except Exception as e:
382
+ logger.error(f"Error getting compliance levels: {e}", exc_info=True)
383
+ return {
384
+ "levels": [],
385
+ "mode": "explicit_allowlist",
386
+ "all_level_names": []
387
+ }
388
+
389
+
390
+ @router.get("/splash")
391
+ async def get_splash_config(current_user: str = Depends(get_current_user)):
392
+ """Get splash screen configuration."""
393
+ config_manager = app_factory.get_config_manager()
394
+ app_settings = config_manager.app_settings
395
+
396
+ # Check if splash screen feature is enabled
397
+ if not app_settings.feature_splash_screen_enabled:
398
+ return {
399
+ "enabled": False,
400
+ "title": "",
401
+ "messages": [],
402
+ "dismissible": True,
403
+ "require_accept": False,
404
+ "dismiss_duration_days": 30,
405
+ "accept_button_text": "Accept",
406
+ "dismiss_button_text": "Dismiss",
407
+ "show_on_every_visit": False
408
+ }
409
+
410
+ # Read splash screen configuration
411
+ splash_config = {}
412
+ import json
413
+ splash_config_filename = app_settings.splash_config_file
414
+ splash_paths = []
415
+ try:
416
+ # Reuse config manager search logic
417
+ try:
418
+ splash_paths = config_manager._search_paths(splash_config_filename) # type: ignore[attr-defined]
419
+ except AttributeError:
420
+ # Fallback minimal search if method renamed/removed
421
+ from pathlib import Path
422
+ backend_root = Path(__file__).parent.parent
423
+ project_root = backend_root.parent
424
+ splash_paths = [
425
+ project_root / "config" / "overrides" / splash_config_filename,
426
+ project_root / "config" / "defaults" / splash_config_filename,
427
+ backend_root / "configfilesadmin" / splash_config_filename,
428
+ backend_root / "configfiles" / splash_config_filename,
429
+ backend_root / splash_config_filename,
430
+ project_root / splash_config_filename,
431
+ ]
432
+
433
+ found_path = None
434
+ for p in splash_paths:
435
+ if p.exists():
436
+ found_path = p
437
+ break
438
+ if found_path:
439
+ with open(found_path, "r", encoding="utf-8") as f:
440
+ splash_config = json.load(f)
441
+ logger.info(f"Loaded splash config from {found_path}")
442
+ else:
443
+ logger.info(
444
+ "Splash config not found in any of these locations: %s",
445
+ [str(p) for p in splash_paths]
446
+ )
447
+ # Return default disabled config
448
+ splash_config = {
449
+ "enabled": False,
450
+ "title": "",
451
+ "messages": [],
452
+ "dismissible": True,
453
+ "require_accept": False,
454
+ "dismiss_duration_days": 30,
455
+ "accept_button_text": "Accept",
456
+ "dismiss_button_text": "Dismiss",
457
+ "show_on_every_visit": False
458
+ }
459
+ except Exception as e:
460
+ logger.warning(f"Error loading splash config: {e}")
461
+ splash_config = {
462
+ "enabled": False,
463
+ "title": "",
464
+ "messages": [],
465
+ "dismissible": True,
466
+ "require_accept": False,
467
+ "dismiss_duration_days": 30,
468
+ "accept_button_text": "Accept",
469
+ "dismiss_button_text": "Dismiss",
470
+ "show_on_every_visit": False
471
+ }
472
+
473
+ return splash_config
474
+
475
+
476
+ # @router.get("/sessions")
477
+ # async def get_session_info(current_user: str = Depends(get_current_user)):
478
+ # """Get session information for the current user."""
479
+ # # TODO: Implement session info retrieval from ChatService
480
+ # return {
481
+ # "total_sessions": 0,
482
+ # "user_sessions": 0,
483
+ # "sessions": []
484
+ # }