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.

Files changed (139) hide show
  1. {aixtools-0.1.8.post1 → aixtools-0.1.9}/PKG-INFO +1 -1
  2. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/_version.py +3 -3
  3. aixtools-0.1.9/aixtools/compliance/private_data.py +136 -0
  4. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/server/__init__.py +6 -0
  5. aixtools-0.1.9/aixtools/server/workspace_privacy.py +65 -0
  6. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/vault/vault.py +40 -3
  7. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools.egg-info/PKG-INFO +1 -1
  8. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools.egg-info/SOURCES.txt +4 -0
  9. {aixtools-0.1.8.post1 → aixtools-0.1.9}/docker/mcp-base/Dockerfile +7 -10
  10. aixtools-0.1.9/docker/mcp-base/zscaler.crt +28 -0
  11. {aixtools-0.1.8.post1 → aixtools-0.1.9}/scripts/test.sh +13 -6
  12. aixtools-0.1.9/tests/unit/compliance/test_private_data.py +329 -0
  13. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/vault/test_vault.py +75 -0
  14. {aixtools-0.1.8.post1 → aixtools-0.1.9}/.env_template +0 -0
  15. {aixtools-0.1.8.post1 → aixtools-0.1.9}/.github/workflows/lint-and-test.yml +0 -0
  16. {aixtools-0.1.8.post1 → aixtools-0.1.9}/.github/workflows/release.yml +0 -0
  17. {aixtools-0.1.8.post1 → aixtools-0.1.9}/.gitignore +0 -0
  18. {aixtools-0.1.8.post1 → aixtools-0.1.9}/.python-version +0 -0
  19. {aixtools-0.1.8.post1 → aixtools-0.1.9}/.roo/rules/rules-mcp.md +0 -0
  20. {aixtools-0.1.8.post1 → aixtools-0.1.9}/.roo/rules/rules.md +0 -0
  21. {aixtools-0.1.8.post1 → aixtools-0.1.9}/.vscode/settings.json +0 -0
  22. {aixtools-0.1.8.post1 → aixtools-0.1.9}/README.md +0 -0
  23. {aixtools-0.1.8.post1 → aixtools-0.1.9}/README.ori.md +0 -0
  24. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/config.toml +0 -0
  25. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/bn.json +0 -0
  26. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/en-US.json +0 -0
  27. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/gu.json +0 -0
  28. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/he-IL.json +0 -0
  29. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/hi.json +0 -0
  30. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/ja.json +0 -0
  31. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/kn.json +0 -0
  32. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/ml.json +0 -0
  33. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/mr.json +0 -0
  34. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/nl.json +0 -0
  35. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/ta.json +0 -0
  36. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/te.json +0 -0
  37. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/.chainlit/translations/zh-CN.json +0 -0
  38. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/__init__.py +0 -0
  39. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/app.py +0 -0
  40. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/google_sdk/__init__.py +0 -0
  41. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/google_sdk/card.py +0 -0
  42. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/google_sdk/pydantic_ai_adapter/agent_executor.py +0 -0
  43. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/google_sdk/pydantic_ai_adapter/storage.py +0 -0
  44. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/google_sdk/remote_agent_connection.py +0 -0
  45. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/google_sdk/utils.py +0 -0
  46. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/a2a/utils.py +0 -0
  47. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/agents/__init__.py +0 -0
  48. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/agents/agent.py +0 -0
  49. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/agents/agent_batch.py +0 -0
  50. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/agents/prompt.py +0 -0
  51. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/app.py +0 -0
  52. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/chainlit.md +0 -0
  53. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/context.py +0 -0
  54. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/db/__init__.py +0 -0
  55. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/db/database.py +0 -0
  56. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/db/vector_db.py +0 -0
  57. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/google/client.py +0 -0
  58. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/log_view/__init__.py +0 -0
  59. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/log_view/app.py +0 -0
  60. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/log_view/display.py +0 -0
  61. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/log_view/export.py +0 -0
  62. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/log_view/filters.py +0 -0
  63. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/log_view/log_utils.py +0 -0
  64. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/log_view/node_summary.py +0 -0
  65. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logfilters/__init__.py +0 -0
  66. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logfilters/context_filter.py +0 -0
  67. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logging/__init__.py +0 -0
  68. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logging/log_objects.py +0 -0
  69. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logging/logging_config.py +0 -0
  70. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logging/mcp_log_models.py +0 -0
  71. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logging/mcp_logger.py +0 -0
  72. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logging/model_patch_logging.py +0 -0
  73. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/logging/open_telemetry.py +0 -0
  74. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/mcp/__init__.py +0 -0
  75. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/mcp/client.py +0 -0
  76. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/mcp/example_client.py +0 -0
  77. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/mcp/example_server.py +0 -0
  78. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/mcp/fast_mcp_log.py +0 -0
  79. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/mcp/faulty_mcp.py +0 -0
  80. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/model_patch/model_patch.py +0 -0
  81. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/server/app_mounter.py +0 -0
  82. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/server/path.py +0 -0
  83. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/server/utils.py +0 -0
  84. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/testing/__init__.py +0 -0
  85. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/testing/aix_test_model.py +0 -0
  86. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/testing/mock_tool.py +0 -0
  87. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/testing/model_patch_cache.py +0 -0
  88. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/tools/doctor/__init__.py +0 -0
  89. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/tools/doctor/tool_doctor.py +0 -0
  90. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/tools/doctor/tool_recommendation.py +0 -0
  91. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/__init__.py +0 -0
  92. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/chainlit/cl_agent_show.py +0 -0
  93. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/chainlit/cl_utils.py +0 -0
  94. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/config.py +0 -0
  95. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/config_util.py +0 -0
  96. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/enum_with_description.py +0 -0
  97. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/files.py +0 -0
  98. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/persisted_dict.py +0 -0
  99. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/utils/utils.py +0 -0
  100. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools/vault/__init__.py +0 -0
  101. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools.egg-info/dependency_links.txt +0 -0
  102. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools.egg-info/entry_points.txt +0 -0
  103. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools.egg-info/requires.txt +0 -0
  104. {aixtools-0.1.8.post1 → aixtools-0.1.9}/aixtools.egg-info/top_level.txt +0 -0
  105. {aixtools-0.1.8.post1 → aixtools-0.1.9}/notebooks/example_faulty_mcp_server.ipynb +0 -0
  106. {aixtools-0.1.8.post1 → aixtools-0.1.9}/notebooks/example_mcp_server_stdio.ipynb +0 -0
  107. {aixtools-0.1.8.post1 → aixtools-0.1.9}/notebooks/example_raw_mcp_client.ipynb +0 -0
  108. {aixtools-0.1.8.post1 → aixtools-0.1.9}/notebooks/example_tool_doctor.ipynb +0 -0
  109. {aixtools-0.1.8.post1 → aixtools-0.1.9}/pyproject.toml +0 -0
  110. {aixtools-0.1.8.post1 → aixtools-0.1.9}/scripts/config.sh +0 -0
  111. {aixtools-0.1.8.post1 → aixtools-0.1.9}/scripts/lint.sh +0 -0
  112. {aixtools-0.1.8.post1 → aixtools-0.1.9}/scripts/log_view.sh +0 -0
  113. {aixtools-0.1.8.post1 → aixtools-0.1.9}/scripts/run_example_mcp_server.sh +0 -0
  114. {aixtools-0.1.8.post1 → aixtools-0.1.9}/scripts/run_faulty_mcp_server.sh +0 -0
  115. {aixtools-0.1.8.post1 → aixtools-0.1.9}/scripts/run_server.sh +0 -0
  116. {aixtools-0.1.8.post1 → aixtools-0.1.9}/setup.cfg +0 -0
  117. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/__init__.py +0 -0
  118. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/__init__.py +0 -0
  119. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/__init__.py +0 -0
  120. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/google_sdk/__init__.py +0 -0
  121. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/google_sdk/pydantic_ai_adapter/__init__.py +0 -0
  122. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_agent_executor.py +0 -0
  123. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_storage.py +0 -0
  124. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/google_sdk/test_card.py +0 -0
  125. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/google_sdk/test_remote_agent_connection.py +0 -0
  126. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/a2a/google_sdk/test_utils.py +0 -0
  127. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/agents/__init__.py +0 -0
  128. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/agents/test_prompt.py +0 -0
  129. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/google/__init__.py +0 -0
  130. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/google/test_client.py +0 -0
  131. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/mcp/__init__.py +0 -0
  132. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/mcp/test_client.py +0 -0
  133. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/server/__init__.py +0 -0
  134. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/server/test_path.py +0 -0
  135. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/server/test_utils.py +0 -0
  136. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/utils/__init__.py +0 -0
  137. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/utils/test_files.py +0 -0
  138. {aixtools-0.1.8.post1 → aixtools-0.1.9}/tests/unit/vault/__init__.py +0 -0
  139. {aixtools-0.1.8.post1 → aixtools-0.1.9}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aixtools
3
- Version: 0.1.8.post1
3
+ Version: 0.1.9
4
4
  Summary: Tools for AI exploration and debugging
5
5
  Requires-Python: >=3.11.2
6
6
  Description-Content-Type: text/markdown
@@ -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.8.post1'
32
- __version_tuple__ = version_tuple = (0, 1, 8, 'post1')
31
+ __version__ = version = '0.1.9'
32
+ __version_tuple__ = version_tuple = (0, 1, 9)
33
33
 
34
- __commit_id__ = commit_id = 'g6ff4d2790'
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
- return f"{config.VAULT_PATH_PREFIX}/{config.VAULT_ENV}/{user_id}/{service_name}"
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aixtools
3
- Version: 0.1.8.post1
3
+ Version: 0.1.9
4
4
  Summary: Tools for AI exploration and debugging
5
5
  Requires-Python: >=3.11.2
6
6
  Description-Content-Type: text/markdown
@@ -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 git curl sudo ca-certificates
4
+ apt-get -y install ca-certificates curl gcc git libcap2-bin sudo
5
+ RUN mv /usr/bin/sudo /usr/sbin
5
6
 
6
- # Build argument for custom certificate file (optional)
7
- ARG CUSTOM_CERT_FILE
8
- # Copy custom certificate if specified
9
- RUN if [ -n "$CUSTOM_CERT_FILE" ] && [ -f "$CUSTOM_CERT_FILE" ]; then \
10
- cp "$CUSTOM_CERT_FILE" /usr/local/share/ca-certificates/ && \
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
- # Run all tests using pytest with coverage
19
- echo "Running tests with coverage..."
20
- pytest tests/ -v --cov=aixtools --cov-report=term-missing
21
-
22
- # Generate HTML coverage report
23
- # pytest tests/ --cov=aixtools --cov-report=html
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