aixtools 0.2.8__tar.gz → 0.2.10__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 (95) hide show
  1. {aixtools-0.2.8 → aixtools-0.2.10}/PKG-INFO +2 -2
  2. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/_version.py +3 -3
  3. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/a2a/google_sdk/pydantic_ai_adapter/agent_executor.py +74 -5
  4. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/a2a/google_sdk/remote_agent_connection.py +16 -0
  5. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/a2a/google_sdk/utils.py +1 -1
  6. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/auth/auth.py +29 -0
  7. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/utils/config.py +3 -0
  8. {aixtools-0.2.8 → aixtools-0.2.10}/pyproject.toml +1 -1
  9. {aixtools-0.2.8 → aixtools-0.2.10}/README.md +0 -0
  10. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/.chainlit/config.toml +0 -0
  11. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/.chainlit/translations/bn.json +0 -0
  12. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/.chainlit/translations/en-US.json +0 -0
  13. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/.chainlit/translations/gu.json +0 -0
  14. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/.chainlit/translations/he-IL.json +0 -0
  15. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/.chainlit/translations/hi.json +0 -0
  16. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/.chainlit/translations/ja.json +0 -0
  17. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/.chainlit/translations/kn.json +0 -0
  18. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/.chainlit/translations/ml.json +0 -0
  19. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/.chainlit/translations/mr.json +0 -0
  20. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/.chainlit/translations/nl.json +0 -0
  21. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/.chainlit/translations/ta.json +0 -0
  22. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/.chainlit/translations/te.json +0 -0
  23. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/.chainlit/translations/zh-CN.json +0 -0
  24. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/__init__.py +0 -0
  25. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/a2a/app.py +0 -0
  26. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/a2a/google_sdk/__init__.py +0 -0
  27. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/a2a/google_sdk/pydantic_ai_adapter/storage.py +0 -0
  28. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/a2a/utils.py +0 -0
  29. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/agents/__init__.py +0 -0
  30. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/agents/agent.py +0 -0
  31. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/agents/agent_batch.py +0 -0
  32. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/agents/print_nodes.py +0 -0
  33. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/agents/prompt.py +0 -0
  34. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/app.py +0 -0
  35. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/auth/__init__.py +0 -0
  36. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/chainlit.md +0 -0
  37. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/compliance/__init__.py +0 -0
  38. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/compliance/private_data.py +0 -0
  39. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/context.py +0 -0
  40. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/db/__init__.py +0 -0
  41. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/db/database.py +0 -0
  42. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/db/vector_db.py +0 -0
  43. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/evals/__init__.py +0 -0
  44. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/evals/__main__.py +0 -0
  45. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/evals/dataset.py +0 -0
  46. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/evals/discovery.py +0 -0
  47. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/evals/run_evals.py +0 -0
  48. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/google/client.py +0 -0
  49. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/log_view/__init__.py +0 -0
  50. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/log_view/app.py +0 -0
  51. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/log_view/display.py +0 -0
  52. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/log_view/export.py +0 -0
  53. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/log_view/filters.py +0 -0
  54. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/log_view/log_utils.py +0 -0
  55. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/log_view/node_summary.py +0 -0
  56. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/logfilters/__init__.py +0 -0
  57. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/logfilters/context_filter.py +0 -0
  58. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/logging/__init__.py +0 -0
  59. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/logging/log_objects.py +0 -0
  60. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/logging/logging_config.py +0 -0
  61. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/logging/mcp_log_models.py +0 -0
  62. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/logging/mcp_logger.py +0 -0
  63. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/logging/model_patch_logging.py +0 -0
  64. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/logging/open_telemetry.py +0 -0
  65. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/mcp/__init__.py +0 -0
  66. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/mcp/client.py +0 -0
  67. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/mcp/example_client.py +0 -0
  68. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/mcp/example_server.py +0 -0
  69. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/mcp/fast_mcp_log.py +0 -0
  70. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/mcp/faulty_mcp.py +0 -0
  71. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/model_patch/model_patch.py +0 -0
  72. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/server/__init__.py +0 -0
  73. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/server/app_mounter.py +0 -0
  74. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/server/path.py +0 -0
  75. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/server/utils.py +0 -0
  76. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/testing/__init__.py +0 -0
  77. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/testing/aix_test_model.py +0 -0
  78. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/testing/mock_tool.py +0 -0
  79. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/testing/model_patch_cache.py +0 -0
  80. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/tools/doctor/__init__.py +0 -0
  81. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/tools/doctor/mcp_tool_doctor.py +0 -0
  82. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/tools/doctor/tool_doctor.py +0 -0
  83. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/tools/doctor/tool_recommendation.py +0 -0
  84. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/utils/__init__.py +0 -0
  85. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/utils/chainlit/cl_agent_show.py +0 -0
  86. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/utils/chainlit/cl_utils.py +0 -0
  87. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/utils/config_util.py +0 -0
  88. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/utils/enum_with_description.py +0 -0
  89. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/utils/files.py +0 -0
  90. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/utils/persisted_dict.py +0 -0
  91. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/utils/utils.py +0 -0
  92. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/vault/__init__.py +0 -0
  93. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools/vault/vault.py +0 -0
  94. {aixtools-0.2.8 → aixtools-0.2.10}/aixtools.egg-info/SOURCES.txt +0 -0
  95. {aixtools-0.2.8 → aixtools-0.2.10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aixtools
3
- Version: 0.2.8
3
+ Version: 0.2.10
4
4
  Summary: Tools for AI exploration and debugging
5
5
  Requires-Python: >=3.11.2
6
6
  Description-Content-Type: text/markdown
@@ -9,7 +9,7 @@ Requires-Dist: cachebox>=5.0.1
9
9
  Requires-Dist: chainlit>=2.5.5
10
10
  Requires-Dist: colorlog>=6.9.0
11
11
  Requires-Dist: fasta2a>=0.5.0
12
- Requires-Dist: fastmcp>=2.10.2
12
+ Requires-Dist: fastmcp>=2.11.0
13
13
  Requires-Dist: hvac>=2.3.0
14
14
  Requires-Dist: ipykernel>=6.29.5
15
15
  Requires-Dist: langchain-chroma>=0.2.3
@@ -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.8'
32
- __version_tuple__ = version_tuple = (0, 2, 8)
31
+ __version__ = version = '0.2.10'
32
+ __version_tuple__ = version_tuple = (0, 2, 10)
33
33
 
34
- __commit_id__ = commit_id = 'gac214deb6'
34
+ __commit_id__ = commit_id = 'ge6b757983'
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  from pathlib import Path
2
3
 
3
4
  from a2a.server.agent_execution import AgentExecutor, RequestContext
@@ -54,10 +55,23 @@ def _task_failed_event(text: str, context_id: str | None, task_id: str | None) -
54
55
  )
55
56
 
56
57
 
58
+ def _task_cancelled_event(text: str, context_id: str | None, task_id: str | None) -> TaskStatusUpdateEvent:
59
+ """Creates a TaskStatusUpdateEvent indicating task cancellation."""
60
+ return TaskStatusUpdateEvent(
61
+ status=TaskStatus(
62
+ state=TaskState.canceled, message=new_agent_text_message(text=text, context_id=context_id, task_id=task_id)
63
+ ),
64
+ final=True,
65
+ context_id=context_id,
66
+ task_id=task_id,
67
+ )
68
+
69
+
57
70
  class PydanticAgentExecutor(AgentExecutor):
58
71
  def __init__(self, agent_parameters: AgentParameters):
59
72
  self._agent_parameters = agent_parameters
60
73
  self.history_storage = InMemoryHistoryStorage()
74
+ self._running_tasks: dict[str, asyncio.Task] = {} # Track running agent tasks for cancellation
61
75
 
62
76
  def _convert_message_to_pydantic_parts(
63
77
  self,
@@ -102,11 +116,27 @@ class PydanticAgentExecutor(AgentExecutor):
102
116
  prompt = self._convert_message_to_pydantic_parts(session_tuple, message)
103
117
  history_message = self.history_storage.get(task.id)
104
118
 
105
- try:
106
- result = await agent.run(
119
+ # Create and track the agent run task for cancellation
120
+ agent_task = asyncio.create_task(
121
+ agent.run(
107
122
  user_prompt=prompt,
108
123
  message_history=history_message,
109
124
  )
125
+ )
126
+ self._running_tasks[task.id] = agent_task
127
+
128
+ try:
129
+ result = await agent_task
130
+ except asyncio.CancelledError:
131
+ # Task was cancelled, send cancellation event
132
+ await event_queue.enqueue_event(
133
+ _task_cancelled_event(
134
+ text="Task was cancelled",
135
+ context_id=context.context_id,
136
+ task_id=task.id,
137
+ )
138
+ )
139
+ return
110
140
  except Exception as e:
111
141
  await event_queue.enqueue_event(
112
142
  _task_failed_event(
@@ -116,6 +146,9 @@ class PydanticAgentExecutor(AgentExecutor):
116
146
  )
117
147
  )
118
148
  return
149
+ finally:
150
+ # Clean up the task from tracking
151
+ self._running_tasks.pop(task.id, None)
119
152
 
120
153
  self.history_storage.store(task.id, result.all_messages())
121
154
 
@@ -185,9 +218,45 @@ class PydanticAgentExecutor(AgentExecutor):
185
218
  )
186
219
  )
187
220
 
188
- async def cancel(self, ctx: RequestContext, event_queue: EventQueue) -> None:
189
- """Cancel"""
190
- raise Exception("cancel not supported")
221
+ async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
222
+ """Cancel the ongoing task identified by the task_id in the context.
223
+
224
+ Attempts to stop the running agent task and publishes a TaskStatusUpdateEvent
225
+ with state TaskState.canceled to the event_queue as per A2A SDK specification.
226
+
227
+ Args:
228
+ context: The request context containing the task ID to cancel.
229
+ event_queue: The queue to publish the cancellation status update to.
230
+ """
231
+ task = context.current_task
232
+ if not task:
233
+ logger.warning("No task to cancel in context")
234
+ return
235
+
236
+ task_id = task.id
237
+
238
+ # Check if we have a running task to cancel
239
+ if task_id in self._running_tasks:
240
+ agent_task = self._running_tasks[task_id]
241
+ if not agent_task.done():
242
+ logger.info("Cancelling running agent task: %s", task_id)
243
+ agent_task.cancel()
244
+ # The cancellation event will be sent by the execute method's except block
245
+ return
246
+
247
+ # If no running task found, check if task is already in terminal state
248
+ if is_in_terminal_state(task):
249
+ logger.info("Task %s is already in terminal state: %s", task_id, task.status.state)
250
+ return
251
+
252
+ # Send cancellation event for tasks that aren't currently running
253
+ await event_queue.enqueue_event(
254
+ _task_cancelled_event(
255
+ text="Task cancelled",
256
+ context_id=context.context_id,
257
+ task_id=task_id,
258
+ )
259
+ )
191
260
 
192
261
  def _build_agent(self, session_tuple: SessionIdTuple) -> Agent:
193
262
  params = self._agent_parameters
@@ -1,10 +1,12 @@
1
1
  import asyncio
2
+ from typing import Callable
2
3
 
3
4
  from a2a.client import Client
4
5
  from a2a.types import (
5
6
  AgentCard,
6
7
  Message,
7
8
  Task,
9
+ TaskIdParams,
8
10
  TaskQueryParams,
9
11
  TaskState,
10
12
  )
@@ -63,6 +65,7 @@ class RemoteAgentConnection:
63
65
  *,
64
66
  sleep_time: float = 0.2,
65
67
  max_iter=1000,
68
+ on_task_submitted: Callable[[str], None] | None = None,
66
69
  ) -> Task | Message:
67
70
  """
68
71
  Sends a message to the remote agent and polls for the task status at regular intervals.
@@ -75,6 +78,9 @@ class RemoteAgentConnection:
75
78
  if isinstance(last_task, Message):
76
79
  return last_task
77
80
 
81
+ if on_task_submitted:
82
+ on_task_submitted(last_task.id)
83
+
78
84
  if is_in_terminal_or_interrupted_state(last_task):
79
85
  return last_task
80
86
  task_id = last_task.id
@@ -86,3 +92,13 @@ class RemoteAgentConnection:
86
92
 
87
93
  timeout_seconds = max_iter * sleep_time
88
94
  raise Exception(f"Task did not complete in {timeout_seconds} seconds") # pylint: disable=broad-exception-raised
95
+
96
+ async def cancel_task(self, task_id: str) -> Task:
97
+ """
98
+ Cancels a task by its ID.
99
+ """
100
+ try:
101
+ return await self._client.cancel_task(TaskIdParams(id=task_id))
102
+ except Exception as e:
103
+ logger.error("Exception found in cancel_task: %s", str(e))
104
+ raise e
@@ -40,7 +40,7 @@ class _AgentCardResolver:
40
40
 
41
41
  def __init__(self, client: httpx.AsyncClient):
42
42
  self._httpx_client = client
43
- self._a2a_client_factory = ClientFactory(ClientConfig(httpx_client=self._httpx_client))
43
+ self._a2a_client_factory = ClientFactory(ClientConfig(httpx_client=self._httpx_client, polling=True))
44
44
  self.clients: dict[str, RemoteAgentConnection] = {}
45
45
 
46
46
  def register_agent_card(self, card: AgentCard):
@@ -7,7 +7,11 @@ import logging
7
7
 
8
8
  import jwt
9
9
  from fastapi import HTTPException
10
+ from fastmcp.server.auth.auth import AuthProvider
10
11
  from jwt import ExpiredSignatureError, InvalidAudienceError, InvalidIssuerError, InvalidSignatureError, PyJWKClient
12
+ from mcp.server.auth.provider import (
13
+ AccessToken,
14
+ )
11
15
 
12
16
  from aixtools.utils import config
13
17
 
@@ -147,3 +151,28 @@ class AccessTokenVerifier:
147
151
  AuthTokenErrorCode.MISSING_GROUPS_ERROR,
148
152
  f"Could not find any group in JWT token, matching: {self.authorized_groups}",
149
153
  )
154
+
155
+
156
+ class AccessTokenAuthProvider(AuthProvider):
157
+ """Authentication provider for MCP servers for validating, authorizing and extracting access tokens."""
158
+
159
+ def __init__(self) -> None:
160
+ super().__init__()
161
+ self.token_verifier = AccessTokenVerifier()
162
+
163
+ async def verify_token(self, token: str) -> AccessToken:
164
+ """Verify the access token and return an AccessToken object."""
165
+
166
+ logger.info("Received verify token request")
167
+ claims = self.token_verifier.verify(token)
168
+ scopes = claims.get("scp", "")
169
+ self.token_verifier.authorize_claims(claims, scopes)
170
+
171
+ scopes_arr = []
172
+ if scopes:
173
+ scopes_arr = scopes.split(" ")
174
+
175
+ logger.info("Authorized the token")
176
+ return AccessToken(
177
+ token=token, client_id=self.token_verifier.api_id, scopes=scopes_arr, expires_at=claims.get("exp", None)
178
+ )
@@ -138,3 +138,6 @@ APP_TENANT_ID = get_variable_env("APP_TENANT_ID")
138
138
 
139
139
  # used for token authorization check
140
140
  APP_AUTHORIZED_GROUPS = get_variable_env("APP_AUTHORIZED_GROUPS", allow_empty=True)
141
+
142
+ # used to skip authorization in local tests if required.
143
+ SKIP_MCP_AUTHORIZATION = str2bool(get_variable_env("SKIP_MCP_AUTHORIZATION", True, False))
@@ -17,7 +17,7 @@ dependencies = [
17
17
  "chainlit>=2.5.5",
18
18
  "colorlog>=6.9.0",
19
19
  "fasta2a>=0.5.0",
20
- "fastmcp>=2.10.2",
20
+ "fastmcp>=2.11.0",
21
21
  "hvac>=2.3.0",
22
22
  "ipykernel>=6.29.5",
23
23
  "langchain-chroma>=0.2.3",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes