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,300 @@
1
+ """Tests for the tool_planner MCP server."""
2
+
3
+ import base64
4
+ import importlib.util
5
+ from pathlib import Path
6
+ from unittest.mock import AsyncMock, MagicMock
7
+
8
+ import pytest
9
+
10
+ # Load tool_planner/main.py as a uniquely-named module to avoid colliding
11
+ # with backend/main.py which is already on sys.path.
12
+ _tool_planner_path = Path(__file__).parent.parent / "mcp" / "tool_planner" / "main.py"
13
+ _spec = importlib.util.spec_from_file_location("tool_planner_main", _tool_planner_path)
14
+ _mod = importlib.util.module_from_spec(_spec)
15
+ _spec.loader.exec_module(_mod)
16
+
17
+ format_tools_for_llm = _mod.format_tools_for_llm
18
+ build_planning_prompt = _mod.build_planning_prompt
19
+ _build_artifact_response = _mod._build_artifact_response
20
+ # plan_with_tools is wrapped by @mcp.tool into a FunctionTool; get the raw fn
21
+ _plan_with_tools_fn = _mod.plan_with_tools.fn
22
+
23
+
24
+ def _decode_artifact(result: dict) -> str:
25
+ """Decode the base64 script content from an artifact response."""
26
+ return base64.b64decode(result["artifacts"][0]["b64"]).decode("utf-8")
27
+
28
+
29
+ async def _call_plan_with_tools(**kwargs):
30
+ """Call the unwrapped plan_with_tools async function."""
31
+ return await _plan_with_tools_fn(**kwargs)
32
+
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # format_tools_for_llm tests
36
+ # ---------------------------------------------------------------------------
37
+
38
+ class TestFormatToolsForLlm:
39
+ """Tests for the format_tools_for_llm helper."""
40
+
41
+ def test_empty_data_returns_no_tools_message(self):
42
+ result = format_tools_for_llm({})
43
+ assert result == "(No tools available)"
44
+
45
+ def test_empty_servers_returns_no_tools_message(self):
46
+ result = format_tools_for_llm({"available_servers": []})
47
+ assert result == "(No tools available)"
48
+
49
+ def test_single_server_single_tool(self):
50
+ data = {
51
+ "available_servers": [
52
+ {
53
+ "server_name": "calculator",
54
+ "description": "Math calculator",
55
+ "tools": [
56
+ {
57
+ "name": "calculator_evaluate",
58
+ "description": "Evaluate math expressions",
59
+ "parameters": {
60
+ "type": "object",
61
+ "properties": {
62
+ "expression": {
63
+ "type": "string",
64
+ "description": "Math expression",
65
+ }
66
+ },
67
+ "required": ["expression"],
68
+ },
69
+ }
70
+ ],
71
+ }
72
+ ]
73
+ }
74
+ result = format_tools_for_llm(data)
75
+ assert "Server: calculator (Math calculator)" in result
76
+ assert "Tool: calculator_evaluate" in result
77
+ assert "Description: Evaluate math expressions" in result
78
+ assert "expression (string, required): Math expression" in result
79
+
80
+ def test_multiple_servers_and_tools(self):
81
+ data = {
82
+ "available_servers": [
83
+ {
84
+ "server_name": "calc",
85
+ "description": "",
86
+ "tools": [
87
+ {"name": "calc_add", "description": "Add numbers", "parameters": {}},
88
+ ],
89
+ },
90
+ {
91
+ "server_name": "pptx",
92
+ "description": "Slides",
93
+ "tools": [
94
+ {"name": "pptx_create", "description": "Create slides", "parameters": {}},
95
+ {"name": "pptx_export", "description": "Export slides", "parameters": {}},
96
+ ],
97
+ },
98
+ ]
99
+ }
100
+ result = format_tools_for_llm(data)
101
+ assert "Server: calc" in result
102
+ assert "Server: pptx (Slides)" in result
103
+ assert "Tool: calc_add" in result
104
+ assert "Tool: pptx_create" in result
105
+ assert "Tool: pptx_export" in result
106
+
107
+ def test_optional_and_required_params(self):
108
+ data = {
109
+ "available_servers": [
110
+ {
111
+ "server_name": "srv",
112
+ "description": "",
113
+ "tools": [
114
+ {
115
+ "name": "srv_tool",
116
+ "description": "",
117
+ "parameters": {
118
+ "type": "object",
119
+ "properties": {
120
+ "required_param": {"type": "string"},
121
+ "optional_param": {"type": "number"},
122
+ },
123
+ "required": ["required_param"],
124
+ },
125
+ }
126
+ ],
127
+ }
128
+ ]
129
+ }
130
+ result = format_tools_for_llm(data)
131
+ assert "required_param (string, required)" in result
132
+ assert "optional_param (number, optional)" in result
133
+
134
+ def test_underscore_prefixed_params_excluded(self):
135
+ data = {
136
+ "available_servers": [
137
+ {
138
+ "server_name": "srv",
139
+ "description": "",
140
+ "tools": [
141
+ {
142
+ "name": "srv_tool",
143
+ "description": "",
144
+ "parameters": {
145
+ "type": "object",
146
+ "properties": {
147
+ "task": {"type": "string"},
148
+ "_mcp_data": {"type": "object"},
149
+ },
150
+ },
151
+ }
152
+ ],
153
+ }
154
+ ]
155
+ }
156
+ result = format_tools_for_llm(data)
157
+ assert "_mcp_data" not in result
158
+ assert "task (string" in result
159
+
160
+
161
+ # ---------------------------------------------------------------------------
162
+ # build_planning_prompt tests
163
+ # ---------------------------------------------------------------------------
164
+
165
+ class TestBuildPlanningPrompt:
166
+ """Tests for the build_planning_prompt helper."""
167
+
168
+ def test_includes_task(self):
169
+ result = build_planning_prompt("create a presentation", "some tools")
170
+ assert "Task: create a presentation" in result
171
+
172
+ def test_includes_tools_reference(self):
173
+ result = build_planning_prompt("task", "Server: calc\n Tool: calc_add")
174
+ assert "Server: calc" in result
175
+ assert "Tool: calc_add" in result
176
+
177
+ def test_includes_cli_usage_instructions(self):
178
+ result = build_planning_prompt("task", "tools")
179
+ assert "python atlas_chat_cli.py" in result
180
+ assert "--tools tool_name" in result
181
+ assert "-o result.txt" in result
182
+
183
+ def test_includes_loop_example(self):
184
+ result = build_planning_prompt("task", "tools")
185
+ assert "for item" in result
186
+
187
+
188
+ # ---------------------------------------------------------------------------
189
+ # plan_with_tools tests
190
+ # ---------------------------------------------------------------------------
191
+
192
+ class TestBuildArtifactResponse:
193
+ """Tests for the _build_artifact_response helper."""
194
+
195
+ def test_returns_required_keys(self):
196
+ result = _build_artifact_response("#!/bin/bash\necho hi", "greet user")
197
+ assert "results" in result
198
+ assert "artifacts" in result
199
+ assert "display" in result
200
+
201
+ def test_artifact_contains_base64_script(self):
202
+ script = "#!/bin/bash\nset -e\necho hello"
203
+ result = _build_artifact_response(script, "say hello")
204
+ artifact = result["artifacts"][0]
205
+ decoded = base64.b64decode(artifact["b64"]).decode("utf-8")
206
+ assert decoded == script
207
+
208
+ def test_artifact_mime_and_viewer(self):
209
+ result = _build_artifact_response("echo x", "task")
210
+ artifact = result["artifacts"][0]
211
+ assert artifact["mime"] == "application/x-sh"
212
+ assert artifact["viewer"] == "code"
213
+
214
+ def test_filename_derived_from_task(self):
215
+ result = _build_artifact_response("echo x", "Create a PowerPoint about dogs")
216
+ filename = result["artifacts"][0]["name"]
217
+ assert filename.endswith(".sh")
218
+ assert "create" in filename
219
+ assert " " not in filename
220
+
221
+ def test_display_opens_canvas_with_code_hint(self):
222
+ result = _build_artifact_response("echo x", "task")
223
+ display = result["display"]
224
+ assert display["open_canvas"] is True
225
+ assert display["viewer_hint"] == "code"
226
+ assert display["primary_file"].endswith(".sh")
227
+
228
+
229
+ class TestPlanWithTools:
230
+ """Tests for the plan_with_tools tool function."""
231
+
232
+ @pytest.mark.asyncio
233
+ async def test_without_ctx_returns_artifact(self):
234
+ result = await _call_plan_with_tools(
235
+ task="test task", _mcp_data={"available_servers": []}
236
+ )
237
+ assert result["results"]["operation"] == "plan_with_tools"
238
+ script = _decode_artifact(result)
239
+ assert "Sampling unavailable" in script
240
+ assert "test task" in script
241
+
242
+ @pytest.mark.asyncio
243
+ async def test_without_mcp_data_still_works(self):
244
+ result = await _call_plan_with_tools(task="do something")
245
+ script = _decode_artifact(result)
246
+ assert "Sampling unavailable" in script
247
+ assert "No tools available" in script
248
+
249
+ @pytest.mark.asyncio
250
+ async def test_with_mocked_ctx_sample(self):
251
+ mock_ctx = MagicMock()
252
+ mock_result = MagicMock()
253
+ mock_result.text = "#!/bin/bash\nset -e\npython atlas_chat_cli.py 'hello' --tools calc_add"
254
+ mock_ctx.sample = AsyncMock(return_value=mock_result)
255
+ mock_ctx.report_progress = AsyncMock()
256
+
257
+ mcp_data = {
258
+ "available_servers": [
259
+ {
260
+ "server_name": "calc",
261
+ "description": "Calculator",
262
+ "tools": [
263
+ {
264
+ "name": "calc_add",
265
+ "description": "Add numbers",
266
+ "parameters": {
267
+ "type": "object",
268
+ "properties": {"a": {"type": "number"}},
269
+ },
270
+ }
271
+ ],
272
+ }
273
+ ]
274
+ }
275
+
276
+ result = await _call_plan_with_tools(
277
+ task="add two numbers", _mcp_data=mcp_data, ctx=mock_ctx
278
+ )
279
+
280
+ script = _decode_artifact(result)
281
+ assert "atlas_chat_cli.py" in script
282
+ assert result["artifacts"][0]["name"].endswith(".sh")
283
+ mock_ctx.sample.assert_awaited_once()
284
+
285
+ call_kwargs = mock_ctx.sample.call_args
286
+ assert call_kwargs.kwargs["temperature"] == 0.3
287
+ assert call_kwargs.kwargs["max_tokens"] == 10000
288
+ assert "task planner" in call_kwargs.kwargs["system_prompt"].lower()
289
+
290
+ @pytest.mark.asyncio
291
+ async def test_sample_returns_none_text(self):
292
+ mock_ctx = MagicMock()
293
+ mock_result = MagicMock()
294
+ mock_result.text = None
295
+ mock_ctx.sample = AsyncMock(return_value=mock_result)
296
+ mock_ctx.report_progress = AsyncMock()
297
+
298
+ result = await _call_plan_with_tools(task="test", _mcp_data={}, ctx=mock_ctx)
299
+ script = _decode_artifact(result)
300
+ assert "Unable to generate plan" in script