aixtools 0.1.8.post1__tar.gz → 0.1.9__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.
Potentially problematic release.
This version of aixtools might be problematic. Click here for more details.
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/PKG-INFO +1 -1
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/_version.py +3 -3
- aixtools-0.1.9/aixtools/compliance/private_data.py +136 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/server/__init__.py +6 -0
- aixtools-0.1.9/aixtools/server/workspace_privacy.py +65 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/vault/vault.py +40 -3
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools.egg-info/PKG-INFO +1 -1
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools.egg-info/SOURCES.txt +4 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/docker/mcp-base/Dockerfile +7 -10
- aixtools-0.1.9/docker/mcp-base/zscaler.crt +28 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/scripts/test.sh +13 -6
- aixtools-0.1.9/tests/unit/compliance/test_private_data.py +329 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/vault/test_vault.py +75 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/.env_template +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/.github/workflows/lint-and-test.yml +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/.github/workflows/release.yml +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/.gitignore +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/.python-version +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/.roo/rules/rules-mcp.md +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/.roo/rules/rules.md +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/.vscode/settings.json +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/README.md +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/README.ori.md +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/config.toml +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/bn.json +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/en-US.json +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/gu.json +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/he-IL.json +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/hi.json +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/ja.json +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/kn.json +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/ml.json +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/mr.json +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/nl.json +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/ta.json +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/te.json +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/zh-CN.json +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/app.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/google_sdk/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/google_sdk/card.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/google_sdk/pydantic_ai_adapter/agent_executor.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/google_sdk/pydantic_ai_adapter/storage.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/google_sdk/remote_agent_connection.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/google_sdk/utils.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/utils.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/agents/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/agents/agent.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/agents/agent_batch.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/agents/prompt.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/app.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/chainlit.md +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/context.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/db/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/db/database.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/db/vector_db.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/google/client.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/log_view/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/log_view/app.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/log_view/display.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/log_view/export.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/log_view/filters.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/log_view/log_utils.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/log_view/node_summary.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logfilters/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logfilters/context_filter.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logging/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logging/log_objects.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logging/logging_config.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logging/mcp_log_models.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logging/mcp_logger.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logging/model_patch_logging.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logging/open_telemetry.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/mcp/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/mcp/client.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/mcp/example_client.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/mcp/example_server.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/mcp/fast_mcp_log.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/mcp/faulty_mcp.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/model_patch/model_patch.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/server/app_mounter.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/server/path.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/server/utils.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/testing/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/testing/aix_test_model.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/testing/mock_tool.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/testing/model_patch_cache.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/tools/doctor/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/tools/doctor/tool_doctor.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/tools/doctor/tool_recommendation.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/chainlit/cl_agent_show.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/chainlit/cl_utils.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/config.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/config_util.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/enum_with_description.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/files.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/persisted_dict.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/utils.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/vault/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools.egg-info/dependency_links.txt +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools.egg-info/entry_points.txt +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools.egg-info/requires.txt +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools.egg-info/top_level.txt +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/notebooks/example_faulty_mcp_server.ipynb +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/notebooks/example_mcp_server_stdio.ipynb +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/notebooks/example_raw_mcp_client.ipynb +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/notebooks/example_tool_doctor.ipynb +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/pyproject.toml +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/scripts/config.sh +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/scripts/lint.sh +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/scripts/log_view.sh +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/scripts/run_example_mcp_server.sh +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/scripts/run_faulty_mcp_server.sh +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/scripts/run_server.sh +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/setup.cfg +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/google_sdk/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/google_sdk/pydantic_ai_adapter/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_agent_executor.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_storage.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/google_sdk/test_card.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/google_sdk/test_remote_agent_connection.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/google_sdk/test_utils.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/agents/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/agents/test_prompt.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/google/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/google/test_client.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/mcp/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/mcp/test_client.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/server/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/server/test_path.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/server/test_utils.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/utils/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/utils/test_files.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/vault/__init__.py +0 -0
- {aixtools-0.1.8.post1 → aixtools-0.1.9}/uv.lock +0 -0
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.1.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
31
|
+
__version__ = version = '0.1.9'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 9)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g3e272f9d5'
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from fastmcp import Context
|
|
5
|
+
|
|
6
|
+
from aixtools.server.path import get_workspace_path
|
|
7
|
+
|
|
8
|
+
PRIVATE_DATA_FILE = ".private_data"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PrivateData:
|
|
12
|
+
"""
|
|
13
|
+
Class to manage private data file in the workspace.
|
|
14
|
+
|
|
15
|
+
The information is stored in a JSON file named `.private_data` within the workspace directory.
|
|
16
|
+
If the file does not exist, it indicates that there is no private data.
|
|
17
|
+
|
|
18
|
+
IMPORTANT: All modifications save the data to the file immediately.
|
|
19
|
+
|
|
20
|
+
FIXME: We should add some level of mutex/locking to prevent concurrent writes.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, ctx: Context | None = None):
|
|
24
|
+
self.ctx: Context | None = ctx
|
|
25
|
+
self._has_private_data: bool = False # Flag indicating if private data exists
|
|
26
|
+
self._private_datasets: list[str] = [] # List of private datasets
|
|
27
|
+
self._idap_datasets: list[str] = [] # List of dataset with IDAP
|
|
28
|
+
self.load()
|
|
29
|
+
|
|
30
|
+
def add_private_dataset(self, dataset_name: str) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Add a private dataset to the list.
|
|
33
|
+
Save the state after modification.
|
|
34
|
+
"""
|
|
35
|
+
if dataset_name not in self._private_datasets:
|
|
36
|
+
self._private_datasets.append(dataset_name)
|
|
37
|
+
self._has_private_data = True
|
|
38
|
+
self.save()
|
|
39
|
+
|
|
40
|
+
def add_idap_dataset(self, dataset_name: str) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Add a dataset with IDAP to the list.
|
|
43
|
+
This also adds it to the private datasets if not already present.
|
|
44
|
+
Save the state after modification.
|
|
45
|
+
"""
|
|
46
|
+
if not self.has_idap_dataset(dataset_name):
|
|
47
|
+
self._idap_datasets.append(dataset_name)
|
|
48
|
+
self._has_private_data = True
|
|
49
|
+
# An IDAP dataset is also a private dataset
|
|
50
|
+
if not self.has_private_dataset(dataset_name):
|
|
51
|
+
self._private_datasets.append(dataset_name)
|
|
52
|
+
self.save()
|
|
53
|
+
|
|
54
|
+
def get_private_datasets(self) -> list[str]:
|
|
55
|
+
"""Get the list of private datasets as a copy (to avoid modification)."""
|
|
56
|
+
return list(self._private_datasets)
|
|
57
|
+
|
|
58
|
+
def get_idap_datasets(self) -> list[str]:
|
|
59
|
+
"""Get the list of datasets with IDAP as a copy (to avoid modification)."""
|
|
60
|
+
return list(self._idap_datasets)
|
|
61
|
+
|
|
62
|
+
def has_private_dataset(self, dataset_name: str) -> bool:
|
|
63
|
+
"""Check if a specific private dataset exists."""
|
|
64
|
+
return dataset_name in self._private_datasets
|
|
65
|
+
|
|
66
|
+
def has_idap_dataset(self, dataset_name: str) -> bool:
|
|
67
|
+
"""Check if a specific dataset with IDAP exists."""
|
|
68
|
+
return dataset_name in self._idap_datasets
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def has_private_data(self) -> bool:
|
|
72
|
+
"""Check if private data exists."""
|
|
73
|
+
return self._has_private_data
|
|
74
|
+
|
|
75
|
+
@has_private_data.setter
|
|
76
|
+
def has_private_data(self, value: bool) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Set the flag indicating if private data exists.
|
|
79
|
+
Save the state after modification.
|
|
80
|
+
"""
|
|
81
|
+
self._has_private_data = value
|
|
82
|
+
if not value:
|
|
83
|
+
self._private_datasets = []
|
|
84
|
+
self._idap_datasets = []
|
|
85
|
+
self.save()
|
|
86
|
+
|
|
87
|
+
def _get_private_data_path(self) -> Path:
|
|
88
|
+
"""Get the path to the private data file in the workspace."""
|
|
89
|
+
return get_workspace_path(service_name=None, ctx=self.ctx) / PRIVATE_DATA_FILE
|
|
90
|
+
|
|
91
|
+
def _has_private_data_file(self) -> bool:
|
|
92
|
+
"""Check if the private data file exists in the workspace."""
|
|
93
|
+
private_data_path = self.get_private_data_path() # type: ignore
|
|
94
|
+
return private_data_path.exists()
|
|
95
|
+
|
|
96
|
+
def save(self) -> None:
|
|
97
|
+
"""Save content to the private data file in the workspace."""
|
|
98
|
+
private_data_path = self._get_private_data_path()
|
|
99
|
+
# No private data? Delete the file if it exists
|
|
100
|
+
if not self.has_private_data:
|
|
101
|
+
private_data_path.unlink(missing_ok=True)
|
|
102
|
+
return
|
|
103
|
+
# If there is private data, serialize this object as JSON
|
|
104
|
+
private_data_path.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
with open(private_data_path, "w") as f:
|
|
106
|
+
# Dump class as JSON, excluding the context
|
|
107
|
+
data_dict = self.__dict__.copy()
|
|
108
|
+
data_dict["ctx"] = None
|
|
109
|
+
json_data = json.dumps(data_dict, indent=4)
|
|
110
|
+
f.write(json_data)
|
|
111
|
+
|
|
112
|
+
def load(self) -> None:
|
|
113
|
+
"""Load content from the private data file in the workspace."""
|
|
114
|
+
private_data_path = self._get_private_data_path()
|
|
115
|
+
if not private_data_path.exists():
|
|
116
|
+
# No private data file
|
|
117
|
+
self.has_private_data = False
|
|
118
|
+
self._private_datasets = []
|
|
119
|
+
self._idap_datasets = []
|
|
120
|
+
return
|
|
121
|
+
with open(private_data_path, "r") as f:
|
|
122
|
+
data = json.load(f)
|
|
123
|
+
self.has_private_data = data.get("_has_private_data", False)
|
|
124
|
+
self._private_datasets = data.get("_private_datasets", [])
|
|
125
|
+
self._idap_datasets = data.get("_idap_datasets", [])
|
|
126
|
+
|
|
127
|
+
def __repr__(self) -> str:
|
|
128
|
+
return (
|
|
129
|
+
f"PrivateData(has_private_data={self.has_private_data}, "
|
|
130
|
+
f"private_datasets={self._private_datasets}, "
|
|
131
|
+
f"idap_datasets={self._idap_datasets}), "
|
|
132
|
+
f"file_path={self._get_private_data_path()})"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def __str__(self) -> str:
|
|
136
|
+
return self.__repr__()
|
|
@@ -13,6 +13,10 @@ from .utils import (
|
|
|
13
13
|
get_session_id_tuple,
|
|
14
14
|
run_in_thread,
|
|
15
15
|
)
|
|
16
|
+
from .workspace_privacy import (
|
|
17
|
+
is_session_private,
|
|
18
|
+
set_session_private,
|
|
19
|
+
)
|
|
16
20
|
|
|
17
21
|
__all__ = [
|
|
18
22
|
"get_workspace_path",
|
|
@@ -20,4 +24,6 @@ __all__ = [
|
|
|
20
24
|
"container_to_host_path",
|
|
21
25
|
"host_to_container_path",
|
|
22
26
|
"run_in_thread",
|
|
27
|
+
"is_session_private",
|
|
28
|
+
"set_session_private",
|
|
23
29
|
]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Workspace privacy utilities for managing session-level privacy flags.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from fastmcp import Context
|
|
8
|
+
|
|
9
|
+
from aixtools.logging.logging_config import get_logger
|
|
10
|
+
from aixtools.server.path import get_workspace_path
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
PRIVACY_FLAG_FILENAME = ".private_data_indicator"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def set_session_private(ctx: Context | tuple[str, str] | None = None) -> bool:
|
|
18
|
+
"""
|
|
19
|
+
Set the current session as private by creating a privacy flag file.
|
|
20
|
+
|
|
21
|
+
Creates an empty file in the session workspace directory
|
|
22
|
+
and sets it as read-only to prevent accidental removal
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
ctx: FastMCP context for user/session identification.
|
|
26
|
+
If None, uses current FastMCP request context from HTTP headers.
|
|
27
|
+
If tuple, first part is a user id (username), second part is session id (aka conversation id)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
bool: True if privacy flag was successfully created, False otherwise.
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
workspace_path = Path(get_workspace_path(ctx=ctx))
|
|
34
|
+
privacy_file = workspace_path / PRIVACY_FLAG_FILENAME
|
|
35
|
+
workspace_path.mkdir(parents=True, exist_ok=True)
|
|
36
|
+
privacy_file.touch(exist_ok=True)
|
|
37
|
+
privacy_file.chmod(0o444)
|
|
38
|
+
logger.warning("Session marked as private")
|
|
39
|
+
return True
|
|
40
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
41
|
+
logger.error("Set current session as private: %s", str(e))
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def is_session_private(ctx: Context | tuple[str, str] | None = None) -> bool:
|
|
46
|
+
"""
|
|
47
|
+
Check if the current session is marked as private.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
ctx: FastMCP context for user/session identification.
|
|
51
|
+
If None, uses current FastMCP request context from HTTP headers.
|
|
52
|
+
If tuple, first part is a user id (username), second part is session id (aka conversation id)
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
bool: True if workspace is private (flag file exists), False otherwise.
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
workspace_path = Path(get_workspace_path(ctx=ctx))
|
|
59
|
+
privacy_file = workspace_path / PRIVACY_FLAG_FILENAME
|
|
60
|
+
is_private = privacy_file.exists()
|
|
61
|
+
logger.info("Session privacy check, is private: %s", str(is_private))
|
|
62
|
+
return is_private
|
|
63
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
64
|
+
logger.error("Check session privacy: %s, assuming not private!", str(e))
|
|
65
|
+
return False
|
|
@@ -27,9 +27,11 @@ class VaultClient:
|
|
|
27
27
|
if not self.client.is_authenticated():
|
|
28
28
|
raise VaultAuthError("Vault client authentication failed. Check vault_token.")
|
|
29
29
|
|
|
30
|
-
def _get_secret_path(self, user_id: str, service_name: str) -> str:
|
|
31
|
-
"""Generate the vault secret path for a user and service."""
|
|
32
|
-
|
|
30
|
+
def _get_secret_path(self, user_id: str, service_name: Optional[str] = None) -> str:
|
|
31
|
+
"""Generate the vault secret path for a user and optionally a service."""
|
|
32
|
+
if service_name:
|
|
33
|
+
return f"{config.VAULT_PATH_PREFIX}/{config.VAULT_ENV}/{user_id}/{service_name}"
|
|
34
|
+
return f"{config.VAULT_PATH_PREFIX}/{config.VAULT_ENV}/{user_id}"
|
|
33
35
|
|
|
34
36
|
def store_user_service_api_key(self, *, user_id: str, service_name: str, user_api_key: str):
|
|
35
37
|
"""
|
|
@@ -98,3 +100,38 @@ class VaultClient:
|
|
|
98
100
|
except Exception as e:
|
|
99
101
|
logger.error("Failed to read complete secret from path %s: %s", secret_path, str(e))
|
|
100
102
|
raise VaultAuthError(e) from e
|
|
103
|
+
|
|
104
|
+
def list_user_secret_keys(self, *, user_id: str) -> list[str]:
|
|
105
|
+
"""
|
|
106
|
+
List all secret keys (service names) for a user, optionally filtered by service name.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
user_id: The user ID to list secrets for
|
|
110
|
+
service_name: Optional service name to filter results. If provided, returns only this service if it exists.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of service names (secret keys) for the user. Empty list if no secrets exist.
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
# List all services for user
|
|
117
|
+
user_path = self._get_secret_path(user_id)
|
|
118
|
+
logger.info("Listing secret keys for user at path %s", user_path)
|
|
119
|
+
|
|
120
|
+
response = self.client.secrets.kv.v2.list_secrets(path=user_path, mount_point=config.VAULT_MOUNT_POINT)
|
|
121
|
+
|
|
122
|
+
if response and "data" in response and "keys" in response["data"]:
|
|
123
|
+
secret_keys = response["data"]["keys"]
|
|
124
|
+
# Remove trailing slashes from directory names if any
|
|
125
|
+
secret_keys = [key.rstrip("/") for key in secret_keys]
|
|
126
|
+
logger.info("Found %d secret keys for user %s", len(secret_keys), user_id)
|
|
127
|
+
return secret_keys
|
|
128
|
+
logger.info("No secret keys found for user %s", user_id)
|
|
129
|
+
return []
|
|
130
|
+
|
|
131
|
+
except InvalidPath:
|
|
132
|
+
# User path does not exist
|
|
133
|
+
logger.warning("User path does not exist for user %s", user_id)
|
|
134
|
+
return []
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.error("Failed to list secret keys for user %s: %s", user_id, str(e))
|
|
137
|
+
raise VaultAuthError(e) from e
|
|
@@ -47,6 +47,7 @@ aixtools/agents/__init__.py
|
|
|
47
47
|
aixtools/agents/agent.py
|
|
48
48
|
aixtools/agents/agent_batch.py
|
|
49
49
|
aixtools/agents/prompt.py
|
|
50
|
+
aixtools/compliance/private_data.py
|
|
50
51
|
aixtools/db/__init__.py
|
|
51
52
|
aixtools/db/database.py
|
|
52
53
|
aixtools/db/vector_db.py
|
|
@@ -78,6 +79,7 @@ aixtools/server/__init__.py
|
|
|
78
79
|
aixtools/server/app_mounter.py
|
|
79
80
|
aixtools/server/path.py
|
|
80
81
|
aixtools/server/utils.py
|
|
82
|
+
aixtools/server/workspace_privacy.py
|
|
81
83
|
aixtools/testing/__init__.py
|
|
82
84
|
aixtools/testing/aix_test_model.py
|
|
83
85
|
aixtools/testing/mock_tool.py
|
|
@@ -97,6 +99,7 @@ aixtools/utils/chainlit/cl_utils.py
|
|
|
97
99
|
aixtools/vault/__init__.py
|
|
98
100
|
aixtools/vault/vault.py
|
|
99
101
|
docker/mcp-base/Dockerfile
|
|
102
|
+
docker/mcp-base/zscaler.crt
|
|
100
103
|
notebooks/example_faulty_mcp_server.ipynb
|
|
101
104
|
notebooks/example_mcp_server_stdio.ipynb
|
|
102
105
|
notebooks/example_raw_mcp_client.ipynb
|
|
@@ -120,6 +123,7 @@ tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_agent_executor.py
|
|
|
120
123
|
tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_storage.py
|
|
121
124
|
tests/unit/agents/__init__.py
|
|
122
125
|
tests/unit/agents/test_prompt.py
|
|
126
|
+
tests/unit/compliance/test_private_data.py
|
|
123
127
|
tests/unit/google/__init__.py
|
|
124
128
|
tests/unit/google/test_client.py
|
|
125
129
|
tests/unit/mcp/__init__.py
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
FROM ubuntu:22.04
|
|
2
2
|
|
|
3
3
|
RUN apt-get -y update && \
|
|
4
|
-
apt-get -y install
|
|
4
|
+
apt-get -y install ca-certificates curl gcc git libcap2-bin sudo
|
|
5
|
+
RUN mv /usr/bin/sudo /usr/sbin
|
|
5
6
|
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
update-ca-certificates && \
|
|
12
|
-
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt && \
|
|
13
|
-
export CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt; \
|
|
14
|
-
fi
|
|
7
|
+
# Add Zscaler CA certificate
|
|
8
|
+
COPY ./zscaler.crt /usr/local/share/ca-certificates/
|
|
9
|
+
RUN update-ca-certificates
|
|
10
|
+
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
|
|
11
|
+
ENV CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
|
|
15
12
|
|
|
16
13
|
# Install `uv` Python package manager
|
|
17
14
|
RUN bash -o pipefail -c "curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/bin sh"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIE0zCCA7ugAwIBAgIJANu+mC2Jt3uTMA0GCSqGSIb3DQEBCwUAMIGhMQswCQYD
|
|
3
|
+
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8GA1UEBxMIU2FuIEpvc2Ux
|
|
4
|
+
FTATBgNVBAoTDFpzY2FsZXIgSW5jLjEVMBMGA1UECxMMWnNjYWxlciBJbmMuMRgw
|
|
5
|
+
FgYDVQQDEw9ac2NhbGVyIFJvb3QgQ0ExIjAgBgkqhkiG9w0BCQEWE3N1cHBvcnRA
|
|
6
|
+
enNjYWxlci5jb20wHhcNMTQxMjE5MDAyNzU1WhcNNDIwNTA2MDAyNzU1WjCBoTEL
|
|
7
|
+
MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAPBgNVBAcTCFNhbiBK
|
|
8
|
+
b3NlMRUwEwYDVQQKEwxac2NhbGVyIEluYy4xFTATBgNVBAsTDFpzY2FsZXIgSW5j
|
|
9
|
+
LjEYMBYGA1UEAxMPWnNjYWxlciBSb290IENBMSIwIAYJKoZIhvcNAQkBFhNzdXBw
|
|
10
|
+
b3J0QHpzY2FsZXIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
|
|
11
|
+
qT7STSxZRTgEFFf6doHajSc1vk5jmzmM6BWuOo044EsaTc9eVEV/HjH/1DWzZtcr
|
|
12
|
+
fTj+ni205apMTlKBW3UYR+lyLHQ9FoZiDXYXK8poKSV5+Tm0Vls/5Kb8mkhVVqv7
|
|
13
|
+
LgYEmvEY7HPY+i1nEGZCa46ZXCOohJ0mBEtB9JVlpDIO+nN0hUMAYYdZ1KZWCMNf
|
|
14
|
+
5J/aTZiShsorN2A38iSOhdd+mcRM4iNL3gsLu99XhKnRqKoHeH83lVdfu1XBeoQz
|
|
15
|
+
z5V6gA3kbRvhDwoIlTBeMa5l4yRdJAfdpkbFzqiwSgNdhbxTHnYYorDzKfr2rEFM
|
|
16
|
+
dsMU0DHdeAZf711+1CunuQIDAQABo4IBCjCCAQYwHQYDVR0OBBYEFLm33UrNww4M
|
|
17
|
+
hp1d3+wcBGnFTpjfMIHWBgNVHSMEgc4wgcuAFLm33UrNww4Mhp1d3+wcBGnFTpjf
|
|
18
|
+
oYGnpIGkMIGhMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8G
|
|
19
|
+
A1UEBxMIU2FuIEpvc2UxFTATBgNVBAoTDFpzY2FsZXIgSW5jLjEVMBMGA1UECxMM
|
|
20
|
+
WnNjYWxlciBJbmMuMRgwFgYDVQQDEw9ac2NhbGVyIFJvb3QgQ0ExIjAgBgkqhkiG
|
|
21
|
+
9w0BCQEWE3N1cHBvcnRAenNjYWxlci5jb22CCQDbvpgtibd7kzAMBgNVHRMEBTAD
|
|
22
|
+
AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAw0NdJh8w3NsJu4KHuVZUrmZgIohnTm0j+
|
|
23
|
+
RTmYQ9IKA/pvxAcA6K1i/LO+Bt+tCX+C0yxqB8qzuo+4vAzoY5JEBhyhBhf1uK+P
|
|
24
|
+
/WVWFZN/+hTgpSbZgzUEnWQG2gOVd24msex+0Sr7hyr9vn6OueH+jj+vCMiAm5+u
|
|
25
|
+
kd7lLvJsBu3AO3jGWVLyPkS3i6Gf+rwAp1OsRrv3WnbkYcFf9xjuaf4z0hRCrLN2
|
|
26
|
+
xFNjavxrHmsH8jPHVvgc1VD0Opja0l/BRVauTrUaoW6tE+wFG5rEcPGS80jjHK4S
|
|
27
|
+
pB5iDj2mUZH1T8lzYtuZy0ZPirxmtsk3135+CKNa2OCAhhFjE0xd
|
|
28
|
+
-----END CERTIFICATE-----
|
|
@@ -15,9 +15,16 @@ touch .env
|
|
|
15
15
|
# Add the project root to PYTHONPATH so imports work correctly
|
|
16
16
|
export PYTHONPATH="${PROJECT_DIR}:${PYTHONPATH:-}"
|
|
17
17
|
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
# Check if a specific test is provided as parameter
|
|
19
|
+
if [ $# -eq 1 ]; then
|
|
20
|
+
# Run specific test without coverage
|
|
21
|
+
echo "Running specific test: $1"
|
|
22
|
+
pytest "$1" -v
|
|
23
|
+
else
|
|
24
|
+
# Run all tests using pytest with coverage
|
|
25
|
+
echo "Running tests with coverage..."
|
|
26
|
+
pytest tests/ -v --cov=aixtools --cov-report=term-missing
|
|
27
|
+
|
|
28
|
+
# Generate HTML coverage report
|
|
29
|
+
# pytest tests/ --cov=aixtools --cov-report=html
|
|
30
|
+
fi
|