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.
- atlas/__init__.py +40 -0
- atlas/application/__init__.py +7 -0
- atlas/application/chat/__init__.py +7 -0
- atlas/application/chat/agent/__init__.py +10 -0
- atlas/application/chat/agent/act_loop.py +179 -0
- atlas/application/chat/agent/factory.py +142 -0
- atlas/application/chat/agent/protocols.py +46 -0
- atlas/application/chat/agent/react_loop.py +338 -0
- atlas/application/chat/agent/think_act_loop.py +171 -0
- atlas/application/chat/approval_manager.py +151 -0
- atlas/application/chat/elicitation_manager.py +191 -0
- atlas/application/chat/events/__init__.py +1 -0
- atlas/application/chat/events/agent_event_relay.py +112 -0
- atlas/application/chat/modes/__init__.py +1 -0
- atlas/application/chat/modes/agent.py +125 -0
- atlas/application/chat/modes/plain.py +74 -0
- atlas/application/chat/modes/rag.py +81 -0
- atlas/application/chat/modes/tools.py +179 -0
- atlas/application/chat/orchestrator.py +213 -0
- atlas/application/chat/policies/__init__.py +1 -0
- atlas/application/chat/policies/tool_authorization.py +99 -0
- atlas/application/chat/preprocessors/__init__.py +1 -0
- atlas/application/chat/preprocessors/message_builder.py +92 -0
- atlas/application/chat/preprocessors/prompt_override_service.py +104 -0
- atlas/application/chat/service.py +454 -0
- atlas/application/chat/utilities/__init__.py +6 -0
- atlas/application/chat/utilities/error_handler.py +367 -0
- atlas/application/chat/utilities/event_notifier.py +546 -0
- atlas/application/chat/utilities/file_processor.py +613 -0
- atlas/application/chat/utilities/tool_executor.py +789 -0
- atlas/atlas_chat_cli.py +347 -0
- atlas/atlas_client.py +238 -0
- atlas/core/__init__.py +0 -0
- atlas/core/auth.py +205 -0
- atlas/core/authorization_manager.py +27 -0
- atlas/core/capabilities.py +123 -0
- atlas/core/compliance.py +215 -0
- atlas/core/domain_whitelist.py +147 -0
- atlas/core/domain_whitelist_middleware.py +82 -0
- atlas/core/http_client.py +28 -0
- atlas/core/log_sanitizer.py +102 -0
- atlas/core/metrics_logger.py +59 -0
- atlas/core/middleware.py +131 -0
- atlas/core/otel_config.py +242 -0
- atlas/core/prompt_risk.py +200 -0
- atlas/core/rate_limit.py +0 -0
- atlas/core/rate_limit_middleware.py +64 -0
- atlas/core/security_headers_middleware.py +51 -0
- atlas/domain/__init__.py +37 -0
- atlas/domain/chat/__init__.py +1 -0
- atlas/domain/chat/dtos.py +85 -0
- atlas/domain/errors.py +96 -0
- atlas/domain/messages/__init__.py +12 -0
- atlas/domain/messages/models.py +160 -0
- atlas/domain/rag_mcp_service.py +664 -0
- atlas/domain/sessions/__init__.py +7 -0
- atlas/domain/sessions/models.py +36 -0
- atlas/domain/unified_rag_service.py +371 -0
- atlas/infrastructure/__init__.py +10 -0
- atlas/infrastructure/app_factory.py +135 -0
- atlas/infrastructure/events/__init__.py +1 -0
- atlas/infrastructure/events/cli_event_publisher.py +140 -0
- atlas/infrastructure/events/websocket_publisher.py +140 -0
- atlas/infrastructure/sessions/in_memory_repository.py +56 -0
- atlas/infrastructure/transport/__init__.py +7 -0
- atlas/infrastructure/transport/websocket_connection_adapter.py +33 -0
- atlas/init_cli.py +226 -0
- atlas/interfaces/__init__.py +15 -0
- atlas/interfaces/events.py +134 -0
- atlas/interfaces/llm.py +54 -0
- atlas/interfaces/rag.py +40 -0
- atlas/interfaces/sessions.py +75 -0
- atlas/interfaces/tools.py +57 -0
- atlas/interfaces/transport.py +24 -0
- atlas/main.py +564 -0
- atlas/mcp/api_key_demo/README.md +76 -0
- atlas/mcp/api_key_demo/main.py +172 -0
- atlas/mcp/api_key_demo/run.sh +56 -0
- atlas/mcp/basictable/main.py +147 -0
- atlas/mcp/calculator/main.py +149 -0
- atlas/mcp/code-executor/execution_engine.py +98 -0
- atlas/mcp/code-executor/execution_environment.py +95 -0
- atlas/mcp/code-executor/main.py +528 -0
- atlas/mcp/code-executor/result_processing.py +276 -0
- atlas/mcp/code-executor/script_generation.py +195 -0
- atlas/mcp/code-executor/security_checker.py +140 -0
- atlas/mcp/corporate_cars/main.py +437 -0
- atlas/mcp/csv_reporter/main.py +545 -0
- atlas/mcp/duckduckgo/main.py +182 -0
- atlas/mcp/elicitation_demo/README.md +171 -0
- atlas/mcp/elicitation_demo/main.py +262 -0
- atlas/mcp/env-demo/README.md +158 -0
- atlas/mcp/env-demo/main.py +199 -0
- atlas/mcp/file_size_test/main.py +284 -0
- atlas/mcp/filesystem/main.py +348 -0
- atlas/mcp/image_demo/main.py +113 -0
- atlas/mcp/image_demo/requirements.txt +4 -0
- atlas/mcp/logging_demo/README.md +72 -0
- atlas/mcp/logging_demo/main.py +103 -0
- atlas/mcp/many_tools_demo/main.py +50 -0
- atlas/mcp/order_database/__init__.py +0 -0
- atlas/mcp/order_database/main.py +369 -0
- atlas/mcp/order_database/signal_data.csv +1001 -0
- atlas/mcp/pdfbasic/main.py +394 -0
- atlas/mcp/pptx_generator/main.py +760 -0
- atlas/mcp/pptx_generator/requirements.txt +13 -0
- atlas/mcp/pptx_generator/run_test.sh +1 -0
- atlas/mcp/pptx_generator/test_pptx_generator_security.py +169 -0
- atlas/mcp/progress_demo/main.py +167 -0
- atlas/mcp/progress_updates_demo/QUICKSTART.md +273 -0
- atlas/mcp/progress_updates_demo/README.md +120 -0
- atlas/mcp/progress_updates_demo/main.py +497 -0
- atlas/mcp/prompts/main.py +222 -0
- atlas/mcp/public_demo/main.py +189 -0
- atlas/mcp/sampling_demo/README.md +169 -0
- atlas/mcp/sampling_demo/main.py +234 -0
- atlas/mcp/thinking/main.py +77 -0
- atlas/mcp/tool_planner/main.py +240 -0
- atlas/mcp/ui-demo/badmesh.png +0 -0
- atlas/mcp/ui-demo/main.py +383 -0
- atlas/mcp/ui-demo/templates/button_demo.html +32 -0
- atlas/mcp/ui-demo/templates/data_visualization.html +32 -0
- atlas/mcp/ui-demo/templates/form_demo.html +28 -0
- atlas/mcp/username-override-demo/README.md +320 -0
- atlas/mcp/username-override-demo/main.py +308 -0
- atlas/modules/__init__.py +0 -0
- atlas/modules/config/__init__.py +34 -0
- atlas/modules/config/cli.py +231 -0
- atlas/modules/config/config_manager.py +1096 -0
- atlas/modules/file_storage/__init__.py +22 -0
- atlas/modules/file_storage/cli.py +330 -0
- atlas/modules/file_storage/content_extractor.py +290 -0
- atlas/modules/file_storage/manager.py +295 -0
- atlas/modules/file_storage/mock_s3_client.py +402 -0
- atlas/modules/file_storage/s3_client.py +417 -0
- atlas/modules/llm/__init__.py +19 -0
- atlas/modules/llm/caller.py +287 -0
- atlas/modules/llm/litellm_caller.py +675 -0
- atlas/modules/llm/models.py +19 -0
- atlas/modules/mcp_tools/__init__.py +17 -0
- atlas/modules/mcp_tools/client.py +2123 -0
- atlas/modules/mcp_tools/token_storage.py +556 -0
- atlas/modules/prompts/prompt_provider.py +130 -0
- atlas/modules/rag/__init__.py +24 -0
- atlas/modules/rag/atlas_rag_client.py +336 -0
- atlas/modules/rag/client.py +129 -0
- atlas/routes/admin_routes.py +865 -0
- atlas/routes/config_routes.py +484 -0
- atlas/routes/feedback_routes.py +361 -0
- atlas/routes/files_routes.py +274 -0
- atlas/routes/health_routes.py +40 -0
- atlas/routes/mcp_auth_routes.py +223 -0
- atlas/server_cli.py +164 -0
- atlas/tests/conftest.py +20 -0
- atlas/tests/integration/test_mcp_auth_integration.py +152 -0
- atlas/tests/manual_test_sampling.py +87 -0
- atlas/tests/modules/mcp_tools/test_client_auth.py +226 -0
- atlas/tests/modules/mcp_tools/test_client_env.py +191 -0
- atlas/tests/test_admin_mcp_server_management_routes.py +141 -0
- atlas/tests/test_agent_roa.py +135 -0
- atlas/tests/test_app_factory_smoke.py +47 -0
- atlas/tests/test_approval_manager.py +439 -0
- atlas/tests/test_atlas_client.py +188 -0
- atlas/tests/test_atlas_rag_client.py +447 -0
- atlas/tests/test_atlas_rag_integration.py +224 -0
- atlas/tests/test_attach_file_flow.py +287 -0
- atlas/tests/test_auth_utils.py +165 -0
- atlas/tests/test_backend_public_url.py +185 -0
- atlas/tests/test_banner_logging.py +287 -0
- atlas/tests/test_capability_tokens_and_injection.py +203 -0
- atlas/tests/test_compliance_level.py +54 -0
- atlas/tests/test_compliance_manager.py +253 -0
- atlas/tests/test_config_manager.py +617 -0
- atlas/tests/test_config_manager_paths.py +12 -0
- atlas/tests/test_core_auth.py +18 -0
- atlas/tests/test_core_utils.py +190 -0
- atlas/tests/test_docker_env_sync.py +202 -0
- atlas/tests/test_domain_errors.py +329 -0
- atlas/tests/test_domain_whitelist.py +359 -0
- atlas/tests/test_elicitation_manager.py +408 -0
- atlas/tests/test_elicitation_routing.py +296 -0
- atlas/tests/test_env_demo_server.py +88 -0
- atlas/tests/test_error_classification.py +113 -0
- atlas/tests/test_error_flow_integration.py +116 -0
- atlas/tests/test_feedback_routes.py +333 -0
- atlas/tests/test_file_content_extraction.py +1134 -0
- atlas/tests/test_file_extraction_routes.py +158 -0
- atlas/tests/test_file_library.py +107 -0
- atlas/tests/test_file_manager_unit.py +18 -0
- atlas/tests/test_health_route.py +49 -0
- atlas/tests/test_http_client_stub.py +8 -0
- atlas/tests/test_imports_smoke.py +30 -0
- atlas/tests/test_interfaces_llm_response.py +9 -0
- atlas/tests/test_issue_access_denied_fix.py +136 -0
- atlas/tests/test_llm_env_expansion.py +836 -0
- atlas/tests/test_log_level_sensitive_data.py +285 -0
- atlas/tests/test_mcp_auth_routes.py +341 -0
- atlas/tests/test_mcp_client_auth.py +331 -0
- atlas/tests/test_mcp_data_injection.py +270 -0
- atlas/tests/test_mcp_get_authorized_servers.py +95 -0
- atlas/tests/test_mcp_hot_reload.py +512 -0
- atlas/tests/test_mcp_image_content.py +424 -0
- atlas/tests/test_mcp_logging.py +172 -0
- atlas/tests/test_mcp_progress_updates.py +313 -0
- atlas/tests/test_mcp_prompt_override_system_prompt.py +102 -0
- atlas/tests/test_mcp_prompts_server.py +39 -0
- atlas/tests/test_mcp_tool_result_parsing.py +296 -0
- atlas/tests/test_metrics_logger.py +56 -0
- atlas/tests/test_middleware_auth.py +379 -0
- atlas/tests/test_prompt_risk_and_acl.py +141 -0
- atlas/tests/test_rag_mcp_aggregator.py +204 -0
- atlas/tests/test_rag_mcp_service.py +224 -0
- atlas/tests/test_rate_limit_middleware.py +45 -0
- atlas/tests/test_routes_config_smoke.py +60 -0
- atlas/tests/test_routes_files_download_token.py +41 -0
- atlas/tests/test_routes_files_health.py +18 -0
- atlas/tests/test_runtime_imports.py +53 -0
- atlas/tests/test_sampling_integration.py +482 -0
- atlas/tests/test_security_admin_routes.py +61 -0
- atlas/tests/test_security_capability_tokens.py +65 -0
- atlas/tests/test_security_file_stats_scope.py +21 -0
- atlas/tests/test_security_header_injection.py +191 -0
- atlas/tests/test_security_headers_and_filename.py +63 -0
- atlas/tests/test_shared_session_repository.py +101 -0
- atlas/tests/test_system_prompt_loading.py +181 -0
- atlas/tests/test_token_storage.py +505 -0
- atlas/tests/test_tool_approval_config.py +93 -0
- atlas/tests/test_tool_approval_utils.py +356 -0
- atlas/tests/test_tool_authorization_group_filtering.py +223 -0
- atlas/tests/test_tool_details_in_config.py +108 -0
- atlas/tests/test_tool_planner.py +300 -0
- atlas/tests/test_unified_rag_service.py +398 -0
- atlas/tests/test_username_override_in_approval.py +258 -0
- atlas/tests/test_websocket_auth_header.py +168 -0
- atlas/version.py +6 -0
- atlas_chat-0.1.0.data/data/.env.example +253 -0
- atlas_chat-0.1.0.data/data/config/defaults/compliance-levels.json +44 -0
- atlas_chat-0.1.0.data/data/config/defaults/domain-whitelist.json +123 -0
- atlas_chat-0.1.0.data/data/config/defaults/file-extractors.json +74 -0
- atlas_chat-0.1.0.data/data/config/defaults/help-config.json +198 -0
- atlas_chat-0.1.0.data/data/config/defaults/llmconfig-buggy.yml +11 -0
- atlas_chat-0.1.0.data/data/config/defaults/llmconfig.yml +19 -0
- atlas_chat-0.1.0.data/data/config/defaults/mcp.json +138 -0
- atlas_chat-0.1.0.data/data/config/defaults/rag-sources.json +17 -0
- atlas_chat-0.1.0.data/data/config/defaults/splash-config.json +16 -0
- atlas_chat-0.1.0.dist-info/METADATA +236 -0
- atlas_chat-0.1.0.dist-info/RECORD +250 -0
- atlas_chat-0.1.0.dist-info/WHEEL +5 -0
- atlas_chat-0.1.0.dist-info/entry_points.txt +4 -0
- 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
|