aixtools 0.2.4__tar.gz → 0.2.6__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 (97) hide show
  1. {aixtools-0.2.4 → aixtools-0.2.6}/PKG-INFO +1 -1
  2. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/_version.py +3 -3
  3. aixtools-0.2.6/aixtools/auth/auth.py +149 -0
  4. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/utils/config.py +3 -2
  5. aixtools-0.2.4/aixtools/auth/auth.py +0 -70
  6. {aixtools-0.2.4 → aixtools-0.2.6}/README.md +0 -0
  7. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/.chainlit/config.toml +0 -0
  8. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/.chainlit/translations/bn.json +0 -0
  9. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/.chainlit/translations/en-US.json +0 -0
  10. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/.chainlit/translations/gu.json +0 -0
  11. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/.chainlit/translations/he-IL.json +0 -0
  12. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/.chainlit/translations/hi.json +0 -0
  13. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/.chainlit/translations/ja.json +0 -0
  14. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/.chainlit/translations/kn.json +0 -0
  15. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/.chainlit/translations/ml.json +0 -0
  16. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/.chainlit/translations/mr.json +0 -0
  17. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/.chainlit/translations/nl.json +0 -0
  18. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/.chainlit/translations/ta.json +0 -0
  19. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/.chainlit/translations/te.json +0 -0
  20. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/.chainlit/translations/zh-CN.json +0 -0
  21. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/__init__.py +0 -0
  22. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/a2a/app.py +0 -0
  23. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/a2a/google_sdk/__init__.py +0 -0
  24. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/a2a/google_sdk/card.py +0 -0
  25. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/a2a/google_sdk/pydantic_ai_adapter/agent_executor.py +0 -0
  26. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/a2a/google_sdk/pydantic_ai_adapter/storage.py +0 -0
  27. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/a2a/google_sdk/remote_agent_connection.py +0 -0
  28. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/a2a/google_sdk/utils.py +0 -0
  29. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/a2a/utils.py +0 -0
  30. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/agents/__init__.py +0 -0
  31. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/agents/agent.py +0 -0
  32. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/agents/agent_batch.py +0 -0
  33. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/agents/print_nodes.py +0 -0
  34. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/agents/prompt.py +0 -0
  35. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/app.py +0 -0
  36. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/auth/__init__.py +0 -0
  37. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/chainlit.md +0 -0
  38. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/compliance/__init__.py +0 -0
  39. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/compliance/private_data.py +0 -0
  40. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/context.py +0 -0
  41. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/db/__init__.py +0 -0
  42. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/db/database.py +0 -0
  43. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/db/vector_db.py +0 -0
  44. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/evals/__init__.py +0 -0
  45. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/evals/__main__.py +0 -0
  46. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/evals/dataset.py +0 -0
  47. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/evals/discovery.py +0 -0
  48. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/evals/run_evals.py +0 -0
  49. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/google/client.py +0 -0
  50. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/log_view/__init__.py +0 -0
  51. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/log_view/app.py +0 -0
  52. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/log_view/display.py +0 -0
  53. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/log_view/export.py +0 -0
  54. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/log_view/filters.py +0 -0
  55. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/log_view/log_utils.py +0 -0
  56. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/log_view/node_summary.py +0 -0
  57. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/logfilters/__init__.py +0 -0
  58. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/logfilters/context_filter.py +0 -0
  59. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/logging/__init__.py +0 -0
  60. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/logging/log_objects.py +0 -0
  61. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/logging/logging_config.py +0 -0
  62. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/logging/mcp_log_models.py +0 -0
  63. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/logging/mcp_logger.py +0 -0
  64. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/logging/model_patch_logging.py +0 -0
  65. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/logging/open_telemetry.py +0 -0
  66. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/mcp/__init__.py +0 -0
  67. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/mcp/client.py +0 -0
  68. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/mcp/example_client.py +0 -0
  69. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/mcp/example_server.py +0 -0
  70. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/mcp/fast_mcp_log.py +0 -0
  71. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/mcp/faulty_mcp.py +0 -0
  72. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/model_patch/model_patch.py +0 -0
  73. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/server/__init__.py +0 -0
  74. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/server/app_mounter.py +0 -0
  75. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/server/path.py +0 -0
  76. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/server/utils.py +0 -0
  77. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/testing/__init__.py +0 -0
  78. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/testing/aix_test_model.py +0 -0
  79. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/testing/mock_tool.py +0 -0
  80. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/testing/model_patch_cache.py +0 -0
  81. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/tools/doctor/__init__.py +0 -0
  82. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/tools/doctor/mcp_tool_doctor.py +0 -0
  83. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/tools/doctor/tool_doctor.py +0 -0
  84. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/tools/doctor/tool_recommendation.py +0 -0
  85. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/utils/__init__.py +0 -0
  86. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/utils/chainlit/cl_agent_show.py +0 -0
  87. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/utils/chainlit/cl_utils.py +0 -0
  88. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/utils/config_util.py +0 -0
  89. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/utils/enum_with_description.py +0 -0
  90. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/utils/files.py +0 -0
  91. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/utils/persisted_dict.py +0 -0
  92. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/utils/utils.py +0 -0
  93. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/vault/__init__.py +0 -0
  94. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools/vault/vault.py +0 -0
  95. {aixtools-0.2.4 → aixtools-0.2.6}/aixtools.egg-info/SOURCES.txt +0 -0
  96. {aixtools-0.2.4 → aixtools-0.2.6}/pyproject.toml +0 -0
  97. {aixtools-0.2.4 → aixtools-0.2.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aixtools
3
- Version: 0.2.4
3
+ Version: 0.2.6
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.2.4'
32
- __version_tuple__ = version_tuple = (0, 2, 4)
31
+ __version__ = version = '0.2.6'
32
+ __version_tuple__ = version_tuple = (0, 2, 6)
33
33
 
34
- __commit_id__ = commit_id = 'g3a46ce295'
34
+ __commit_id__ = commit_id = 'g9b369b8bb'
@@ -0,0 +1,149 @@
1
+ """
2
+ Module that manages OAuth2 functions for authentication
3
+ """
4
+
5
+ import enum
6
+ import logging
7
+
8
+ import jwt
9
+ from fastapi import HTTPException
10
+ from jwt import ExpiredSignatureError, InvalidAudienceError, InvalidIssuerError, InvalidSignatureError, PyJWKClient
11
+
12
+ from aixtools.utils import config
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class AuthTokenErrorCode(str, enum.Enum):
18
+ """Enum for error codes returned by the AuthTokenError exception."""
19
+
20
+ TOKEN_EXPIRED = "Token expired"
21
+ INVALID_AUDIENCE = "Token not for expected audience"
22
+ INVALID_ISSUER = "Token not for expected issuer"
23
+ INVALID_SIGNATURE = "Token signature error"
24
+ INVALID_TOKEN = "Invalid token"
25
+ JWT_ERROR = "Generic JWT error"
26
+ MISSING_GROUPS_ERROR = "Missing authorized groups"
27
+ INVALID_TOKEN_SCOPE = "Token scope does not match configured scope"
28
+
29
+
30
+ class AuthTokenError(Exception):
31
+ """Exception raised for authentication token errors."""
32
+
33
+ def __init__(self, error_code: AuthTokenErrorCode, msg: str = None):
34
+ self.error_code = error_code
35
+ error_msg = error_code.value if msg is None else msg
36
+ super().__init__(error_msg)
37
+
38
+ def to_http_exception(self, required_scope: str = None, realm: str = "MCP") -> HTTPException:
39
+ """
40
+ Returns an HTTPException with 401 status for all AuthTokenErrorCode,
41
+ including MCP JSON body and WWW-Authenticate header.
42
+ """
43
+ status_code = 401
44
+ www_error = (
45
+ "insufficient_scope" if self.error_code == AuthTokenErrorCode.INVALID_TOKEN_SCOPE else "invalid_token"
46
+ )
47
+
48
+ header_value = f'Bearer realm="{realm}", error="{www_error}", error_description="{self.error_code.value}"'
49
+ if self.error_code == AuthTokenErrorCode.INVALID_TOKEN_SCOPE and required_scope:
50
+ header_value += f', scope="{required_scope}"'
51
+
52
+ detail = {"error": {"code": self.error_code.name, "message": self.error_code.value}}
53
+ if self.error_code == AuthTokenErrorCode.INVALID_TOKEN_SCOPE and required_scope:
54
+ detail["error"]["required_scope"] = required_scope
55
+
56
+ return HTTPException(status_code=status_code, detail=detail, headers={"WWW-Authenticate": header_value})
57
+
58
+
59
+ class AccessTokenVerifier:
60
+ """
61
+ Verifies Microsoft SSO JWT token against the configured Tenant ID, Audience, API ID and Issuer URL.
62
+ """
63
+
64
+ def __init__(self):
65
+ tenant_id = config.APP_TENANT_ID
66
+ self.api_id = config.APP_API_ID
67
+ self.issuer_url = f"https://sts.windows.net/{tenant_id}/"
68
+
69
+ self.authorized_groups = set(config.APP_AUTHORIZED_GROUPS.split(",")) if config.APP_AUTHORIZED_GROUPS else set()
70
+ if not self.authorized_groups:
71
+ logger.warning("No authorized groups configured")
72
+
73
+ # Azure AD endpoints
74
+ jwks_url = f"https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys"
75
+ self.jwks_client = PyJWKClient(
76
+ uri=jwks_url,
77
+ # cache keys url response to reduce SSO server network calls,
78
+ # as public keys are not expected to change frequently
79
+ cache_jwk_set=True,
80
+ # cache resolved public keys
81
+ cache_keys=True,
82
+ # cache url response for 10 hours
83
+ lifespan=36000,
84
+ )
85
+
86
+ logger.info("Using JWKS: %s", jwks_url)
87
+
88
+ def verify(self, token: str) -> dict:
89
+ """
90
+ Verifies The JWT access token and returns decoded claims as a dictionary if the token is
91
+ valid, otherwise raises an AuthTokenError
92
+ """
93
+ try:
94
+ signing_key = self.jwks_client.get_signing_key_from_jwt(token)
95
+ logger.info("Verifying JWT token")
96
+ claims = jwt.decode(
97
+ token,
98
+ signing_key.key,
99
+ algorithms=["RS256"],
100
+ audience=self.api_id,
101
+ issuer=self.issuer_url,
102
+ # ensure audience verification is carried out
103
+ options={"verify_aud": True},
104
+ )
105
+ logger.info("Verified JWT token")
106
+ return claims
107
+
108
+ except ExpiredSignatureError as e:
109
+ raise AuthTokenError(AuthTokenErrorCode.TOKEN_EXPIRED) from e
110
+ except InvalidAudienceError as e:
111
+ raise AuthTokenError(AuthTokenErrorCode.INVALID_AUDIENCE) from e
112
+ except InvalidIssuerError as e:
113
+ raise AuthTokenError(AuthTokenErrorCode.INVALID_ISSUER) from e
114
+ except InvalidSignatureError as e:
115
+ raise AuthTokenError(AuthTokenErrorCode.INVALID_SIGNATURE) from e
116
+ except jwt.exceptions.PyJWTError as e:
117
+ raise AuthTokenError(AuthTokenErrorCode.JWT_ERROR) from e
118
+
119
+ def authorize_claims(self, claims: dict, expected_scope: str):
120
+ """
121
+ Authorize claims based on token scope, expected scope and authorized groups
122
+ claims: decoded JWT claims
123
+ expected_scope: expected scope for the token
124
+ Raises AuthTokenError if authorization fails.
125
+ """
126
+ logger.info("Checking JWT token claims")
127
+ if expected_scope:
128
+ token_scopes = claims.get("scp", "").split()
129
+ if expected_scope not in token_scopes:
130
+ logger.error("Expected token scope: %s, got: %s", expected_scope, token_scopes)
131
+ raise AuthTokenError(
132
+ AuthTokenErrorCode.INVALID_TOKEN_SCOPE,
133
+ f"Expected token scope: {expected_scope}, got: {token_scopes}",
134
+ )
135
+
136
+ if not self.authorized_groups:
137
+ logger.info("Authorized JWT token, no authorized groups configured")
138
+ return
139
+
140
+ groups = claims.get("groups", [])
141
+ if self.authorized_groups & set(groups):
142
+ logger.info("Authorized JWT token, against %s", groups)
143
+ return
144
+
145
+ logger.error("Could not find any group in JWT token, matching: %s", self.authorized_groups)
146
+ raise AuthTokenError(
147
+ AuthTokenErrorCode.MISSING_GROUPS_ERROR,
148
+ f"Could not find any group in JWT token, matching: {self.authorized_groups}",
149
+ )
@@ -135,5 +135,6 @@ APP_CLIENT_ID = get_variable_env("APP_CLIENT_ID")
135
135
  # used for token audience check
136
136
  APP_API_ID = get_variable_env("APP_API_ID")
137
137
  APP_TENANT_ID = get_variable_env("APP_TENANT_ID")
138
- # used for token issuer check
139
- APP_ISSUER_URL = get_variable_env("APP_ISSUER_URL")
138
+
139
+ # used for token authorization check
140
+ APP_AUTHORIZED_GROUPS = get_variable_env("APP_AUTHORIZED_GROUPS", allow_empty=True)
@@ -1,70 +0,0 @@
1
- """
2
- Module that manages OAuth2 functions for authentication
3
- """
4
-
5
- import logging
6
-
7
- import jwt
8
- from jwt import ExpiredSignatureError, InvalidAudienceError, InvalidIssuerError, PyJWKClient
9
-
10
- from aixtools.utils import config
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
-
15
- class AuthTokenError(Exception):
16
- """Exception raised for authentication token errors."""
17
-
18
-
19
- # pylint: disable=too-few-public-methods
20
- class AccessTokenVerifier:
21
- """
22
- Verifies Microsoft SSO JWT token against the configured Tenant ID, Audience, API ID and Issuer URL.
23
- """
24
-
25
- def __init__(self):
26
- tenant_id = config.APP_TENANT_ID
27
- self.api_id = config.APP_API_ID
28
- self.issuer_url = config.APP_ISSUER_URL
29
- # Azure AD endpoints
30
- jwks_url = f"https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys"
31
- self.jwks_client = PyJWKClient(
32
- uri=jwks_url,
33
- # cache keys url response to reduce SSO server network calls,
34
- # as public keys are not expected to change frequently
35
- cache_jwk_set=True,
36
- # cache resolved public keys
37
- cache_keys=True,
38
- # cache url response for 10 hours
39
- lifespan=36000,
40
- )
41
-
42
- logger.info("Using JWKS: %s", jwks_url)
43
-
44
- def verify(self, token: str) -> dict:
45
- """
46
- Verifies The JWT access token and returns decoded claims as a dictionary if the token is
47
- valid, otherwise raises an AuthTokenError
48
- """
49
- try:
50
- signing_key = self.jwks_client.get_signing_key_from_jwt(token)
51
-
52
- claims = jwt.decode(
53
- token,
54
- signing_key.key,
55
- algorithms=["RS256"],
56
- audience=self.api_id,
57
- issuer=self.issuer_url,
58
- # ensure audience verification is carried out
59
- options={"verify_aud": True},
60
- )
61
- return claims
62
-
63
- except ExpiredSignatureError as e:
64
- raise AuthTokenError("Token expired") from e
65
- except InvalidAudienceError as e:
66
- raise AuthTokenError(f"Token not for expected audience: {e}") from e
67
- except InvalidIssuerError as e:
68
- raise AuthTokenError(f"Token not for expected issuer: {e}") from e
69
- except jwt.exceptions.PyJWTError as e:
70
- raise AuthTokenError(f"Invalid token: {e}") from e
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes