smarta2a 0.4.11__py3-none-any.whl → 0.4.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,6 @@ from typing import Optional
7
7
 
8
8
  # Local imports
9
9
  from smarta2a.server import SmartA2A
10
- from smarta2a.model_providers.base_llm_provider import BaseLLMProvider
11
10
  from smarta2a.server.state_manager import StateManager
12
11
  from smarta2a.utils.types import StateData, SendTaskRequest, AgentCard, WebhookRequest, WebhookResponse, TextPart, DataPart, FilePart
13
12
  from smarta2a.client.a2a_client import A2AClient
@@ -16,11 +15,9 @@ class A2AHuman:
16
15
  def __init__(
17
16
  self,
18
17
  name: str,
19
- model_provider: BaseLLMProvider,
20
18
  agent_card: AgentCard = None,
21
19
  state_manager: StateManager = None,
22
20
  ):
23
- self.model_provider = model_provider
24
21
  self.state_manager = state_manager
25
22
  self.app = SmartA2A(
26
23
  name=name,
@@ -32,7 +29,7 @@ class A2AHuman:
32
29
  def __register_handlers(self):
33
30
  @self.app.on_event("startup")
34
31
  async def on_startup():
35
- await self.model_provider.load()
32
+ pass
36
33
 
37
34
  @self.app.app.get("/tasks")
38
35
  async def get_tasks(fields: Optional[str] = Query(None)):
@@ -42,7 +39,7 @@ class A2AHuman:
42
39
 
43
40
  @self.app.on_send_task(forward_to_webhook=True)
44
41
  async def on_send_task(request: SendTaskRequest, state: StateData):
45
- return "Give me some time to respond"
42
+ return "I am human and take some time to respond, but I will definitely respond to your request"
46
43
 
47
44
  @self.app.webhook()
48
45
  async def on_webhook(request: WebhookRequest, state: StateData):
@@ -54,6 +54,15 @@ class A2AClient:
54
54
  metadata: dict[str, Any] | None = None,
55
55
  ):
56
56
  """Send a task to another Agent."""
57
+
58
+ # Auto-create PushNotificationConfig if not provided and we have a URL
59
+ if push_notification is None and self.url:
60
+ push_notification = PushNotificationConfig(
61
+ url=f"{self.url}/webhook",
62
+ token=None,
63
+ authentication=None
64
+ )
65
+
57
66
  params = TaskRequestBuilder.build_send_task_request(
58
67
  id=id,
59
68
  role=role,
@@ -84,6 +93,15 @@ class A2AClient:
84
93
  metadata: dict[str, Any] | None = None,
85
94
  ):
86
95
  """Send to another Agent and receive a stream of responses."""
96
+
97
+ # Auto-create PushNotificationConfig if not provided and we have a URL
98
+ if push_notification is None and self.url:
99
+ push_notification = PushNotificationConfig(
100
+ url=f"{self.url}/webhook",
101
+ token=None,
102
+ authentication=None
103
+ )
104
+
87
105
  params = TaskRequestBuilder.build_send_task_request(
88
106
  id=id,
89
107
  role=role,
@@ -0,0 +1,27 @@
1
+ # Library imports
2
+ from abc import ABC, abstractmethod
3
+ from typing import Optional
4
+
5
+ class BaseFileStore(ABC):
6
+ """Separate interface for file operations"""
7
+
8
+ @abstractmethod
9
+ async def upload(
10
+ self,
11
+ content: bytes,
12
+ task_id: str,
13
+ filename: Optional[str] = None
14
+ ) -> str:
15
+ pass
16
+
17
+ @abstractmethod
18
+ async def download(self, uri: str) -> bytes:
19
+ pass
20
+
21
+ @abstractmethod
22
+ async def delete_for_task(self, task_id: str):
23
+ pass
24
+
25
+ @abstractmethod
26
+ async def list_files(self, task_id: str) -> list[str]:
27
+ pass
@@ -0,0 +1,75 @@
1
+ # Library imports
2
+ from pathlib import Path
3
+ from typing import Optional
4
+ import hashlib
5
+ import aiofiles
6
+ import aiofiles.os
7
+ import shutil
8
+
9
+ # Local imports
10
+ from smarta2a.file_stores.base_file_store import BaseFileStore
11
+
12
+ class LocalFileStore(BaseFileStore):
13
+ """
14
+ Local filesystem implementation that mimics cloud storage patterns
15
+ - Stores files in task-specific directories
16
+ - Uses content-addressable storage for deduplication
17
+ - Generates file:// URIs for compatibility
18
+ """
19
+
20
+ def __init__(self, base_path: str = "./filestore"):
21
+ self.base_path = Path(base_path).resolve()
22
+ self.base_path.mkdir(parents=True, exist_ok=True)
23
+
24
+ async def upload(self, content: bytes, task_id: str, filename: Optional[str] = None) -> str:
25
+ # Create task directory if not exists
26
+ task_dir = self.base_path / task_id
27
+ await aiofiles.os.makedirs(task_dir, exist_ok=True)
28
+
29
+ # Ensure content is bytes for hashing
30
+ if isinstance(content, str):
31
+ content = content.encode('utf-8')
32
+
33
+ # Generate content hash for deduplication
34
+ content_hash = hashlib.sha256(content).hexdigest()
35
+ file_ext = Path(filename).suffix if filename else ""
36
+ unique_name = f"{content_hash}{file_ext}"
37
+
38
+ # Write file
39
+ file_path = task_dir / unique_name
40
+ async with aiofiles.open(file_path, "wb") as f:
41
+ await f.write(content)
42
+
43
+ return f"file://{file_path}"
44
+
45
+
46
+ async def download(self, uri: str) -> bytes:
47
+ # Parse file:// URI
48
+ path = Path(uri.replace("file://", ""))
49
+ if not path.exists():
50
+ raise FileNotFoundError(f"File not found at {uri}")
51
+
52
+ async with aiofiles.open(path, "rb") as f:
53
+ return await f.read()
54
+
55
+ async def delete_for_task(self, task_id: str) -> None:
56
+ task_dir = self.base_path / task_id
57
+ if await aiofiles.os.path.exists(task_dir):
58
+ await aiofiles.os.rmdir(task_dir)
59
+
60
+ async def list_files(self, task_id: str) -> list[str]:
61
+ task_dir = self.base_path / task_id
62
+ if not await aiofiles.os.path.exists(task_dir):
63
+ return []
64
+
65
+ return [
66
+ f"file://{file_path}"
67
+ for file_path in task_dir.iterdir()
68
+ if file_path.is_file()
69
+ ]
70
+
71
+ async def clear_all(self) -> None:
72
+ """Clear entire storage (useful for testing)"""
73
+ if await aiofiles.os.path.exists(self.base_path):
74
+ shutil.rmtree(self.base_path)
75
+ await aiofiles.os.makedirs(self.base_path, exist_ok=True)
@@ -69,35 +69,33 @@ class OpenAIProvider(BaseLLMProvider):
69
69
  self.agent_cards
70
70
  )
71
71
 
72
+
72
73
  def _convert_part(self, part: Union[TextPart, FilePart, DataPart]) -> dict:
73
74
  """Convert a single part to OpenAI-compatible format"""
74
75
  if isinstance(part, TextPart):
75
76
  return {"type": "text", "text": part.text}
76
77
 
77
78
  elif isinstance(part, FilePart):
78
- if part.file.mimeType not in self.supported_media_types:
79
- raise ValueError(f"Unsupported media type: {part.file.mimeType}")
80
-
81
- if part.file.uri:
82
- return {
83
- "type": "image_url",
84
- "image_url": {"url": part.file.uri}
85
- }
86
- elif part.file.bytes:
87
- return {
88
- "type": "image_url",
89
- "image_url": {
90
- "url": f"data:{part.file.mimeType};base64,{part.file.bytes}"
91
- }
92
- }
93
-
94
- elif isinstance(part, DataPart):
79
+ # Treat all files as attachments with placeholder metadata
80
+ fc = part.file
81
+ # Determine URI (either direct or data URL)
82
+ if fc.uri:
83
+ uri = fc.uri
84
+ else:
85
+ uri = f"data:{fc.mimeType};base64,{fc.bytes}"
86
+
87
+ placeholder = (
88
+ f'[FileAttachment name="{fc.name or ""}" '
89
+ f'mimeType="{fc.mimeType or ""}" '
90
+ f'uri="{uri}"]'
91
+ )
92
+ return {"type": "text", "text": placeholder}
93
+
94
+ else:
95
95
  return {
96
96
  "type": "text",
97
97
  "text": f"[Structured Data]\n{json.dumps(part.data, indent=2)}"
98
98
  }
99
-
100
- raise ValueError(f"Unsupported part type: {type(part)}")
101
99
 
102
100
 
103
101
  def _convert_messages(self, messages: List[Message]) -> List[dict]:
@@ -1,15 +1,18 @@
1
1
  # Library imports
2
2
  from typing import Optional, Dict, Any, List
3
+ import base64
3
4
 
4
5
  # Local imports
5
6
  from smarta2a.state_stores.base_state_store import BaseStateStore
7
+ from smarta2a.file_stores.base_file_store import BaseFileStore
6
8
  from smarta2a.history_update_strategies.history_update_strategy import HistoryUpdateStrategy
7
- from smarta2a.utils.types import Message, StateData, Task, TaskStatus, TaskState, PushNotificationConfig, Part
9
+ from smarta2a.utils.types import Message, StateData, Task, TaskStatus, TaskState, PushNotificationConfig, Part, FilePart
8
10
  from smarta2a.server.nats_client import NATSClient
9
11
 
10
12
  class StateManager:
11
- def __init__(self, state_store: BaseStateStore, history_strategy: HistoryUpdateStrategy, nats_server_url: Optional[str] = "nats://localhost:4222"):
13
+ def __init__(self, state_store: BaseStateStore, file_store: BaseFileStore, history_strategy: HistoryUpdateStrategy, nats_server_url: Optional[str] = "nats://localhost:4222"):
12
14
  self.state_store = state_store
15
+ self.file_store = file_store
13
16
  self.strategy = history_strategy
14
17
  self.nats_client = NATSClient(server_url=nats_server_url)
15
18
 
@@ -72,6 +75,9 @@ class StateManager:
72
75
  )
73
76
  latest_state.task.metadata = metadata
74
77
 
78
+ # Process files before persistence
79
+ await self._process_file_parts(latest_state)
80
+
75
81
  await self.update_state(latest_state)
76
82
 
77
83
  return latest_state
@@ -187,3 +193,16 @@ class StateManager:
187
193
  except ValueError as e:
188
194
  print(f"Invalid part in artifact: {e}")
189
195
  return parts
196
+
197
+ async def _process_file_parts(self, state: StateData):
198
+ """Replace file bytes with URIs and persist files"""
199
+ for msg in state.context_history:
200
+ for part in msg.parts:
201
+ if isinstance(part, FilePart) and part.file.bytes:
202
+ uri = await self.file_store.upload(
203
+ content=base64.b64decode(part.file.bytes),
204
+ task_id=state.task_id,
205
+ filename=part.file.name
206
+ )
207
+ part.file.uri = uri
208
+ part.file.bytes = None # Remove from state
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smarta2a
3
- Version: 0.4.11
3
+ Version: 0.4.12
4
4
  Summary: a Python framework that helps you build servers and AI agents that communicate using the A2A protocol
5
5
  Project-URL: Homepage, https://github.com/siddharthsma/smarta2a
6
6
  Project-URL: Bug Tracker, https://github.com/siddharthsma/smarta2a/issues
@@ -1,20 +1,22 @@
1
1
  smarta2a/__init__.py,sha256=T_EECYqWrxshix0FbgUv22zlKRX22HFU-HKXcYTOb3w,175
2
2
  smarta2a/agent/a2a_agent.py,sha256=EurcxpV14e3OPWCMutYL0EXMHb5ZKQqAHEGZZF6pNgg,1892
3
- smarta2a/agent/a2a_human.py,sha256=tXrEtl0OqZEXZfmPTLnbbAiREMTmlqA42XIf7KocqMU,1850
3
+ smarta2a/agent/a2a_human.py,sha256=EZP9TD54Gum509OMOGmfFeCeoMk2Lyi1Y24RoRWh4hY,1720
4
4
  smarta2a/agent/a2a_mcp_server.py,sha256=X_mxkgYgCA_dSNtCvs0rSlOoWYc-8d3Qyxv0e-a7NKY,1015
5
5
  smarta2a/archive/smart_mcp_client.py,sha256=0s2OWFKWSv-_UF7rb9fOrsh1OIYsYOsGukkXXp_E1cU,4158
6
6
  smarta2a/archive/subscription_service.py,sha256=vftmZD94HbdjPFa_1UBvsBm-WkW-s3ZCVq60fF7OCgA,4109
7
7
  smarta2a/archive/task_service.py,sha256=ptf-oMHy98Rw4XSxyK1-lpqc1JtkCkEEHTmwAaunet4,8199
8
8
  smarta2a/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- smarta2a/client/a2a_client.py,sha256=apDkKFtq61T79LpkbkzVTKWA0mSjR_eTNdGPUYozyvk,12100
9
+ smarta2a/client/a2a_client.py,sha256=Tl9FLADlK5n_y_0C3qcFB25T6Uu02iXDSxopm40LbKs,12726
10
10
  smarta2a/client/mcp_client.py,sha256=JeXhBqxM9TYAArpExLRtEr3lZeQZMcnTmGFl6XKsdu8,3797
11
+ smarta2a/file_stores/base_file_store.py,sha256=fcwFIOoFjLQiIKb8lIRVujnV6udyuI9Dq8cEc2ldmIQ,591
12
+ smarta2a/file_stores/local_file_store.py,sha256=4GLDrsKxSoLWn2Oha4OD-P2r5vBpfV-8ePvZ5bhP1e8,2616
11
13
  smarta2a/history_update_strategies/__init__.py,sha256=x5WtiE9rG5ze8d8hA6E6wJOciBhWHa_ZgGgoIAZcXEQ,213
12
14
  smarta2a/history_update_strategies/append_strategy.py,sha256=j7Qbhs69Wwr-HBLB8GJ3-mEPaBSHiBV2xz9ZZi86k2w,312
13
15
  smarta2a/history_update_strategies/history_update_strategy.py,sha256=n2sfIGu8ztKI7gJTwRX26m4tZr28B8Xdhrk6RlBFlI8,373
14
16
  smarta2a/history_update_strategies/rolling_window_strategy.py,sha256=7Ch042JWt4TM_r1-sFKlSIxHj8VX1P3ZoqjCvIdeSqA,540
15
17
  smarta2a/model_providers/__init__.py,sha256=hJj0w00JjqTiBgJmHmOWwL6MU_hwmro9xTiX3XYf6ts,148
16
18
  smarta2a/model_providers/base_llm_provider.py,sha256=iQUqjnypl0f2M929iU0WhHoxAE4ek-NUFJPbEnNQ8-4,412
17
- smarta2a/model_providers/openai_provider.py,sha256=SJfEOktgbjyZ3FtE-Q_K7dYJJI0khrltj2kJoYwz0WQ,12317
19
+ smarta2a/model_providers/openai_provider.py,sha256=dGVnQ94H6-pohUYwT_IzmUf0Nc7BDMJtI_dpjHLL2ak,12131
18
20
  smarta2a/server/__init__.py,sha256=f2X454Ll4vJc02V4JLJHTN-h8u0TBm4d_FkiO4t686U,53
19
21
  smarta2a/server/handler_registry.py,sha256=OVRG5dTvxB7qUNXgsqWxVNxIyRljUShSYxb1gtbi5XM,820
20
22
  smarta2a/server/json_rpc_request_processor.py,sha256=qRB3sfj_n9ImkIOCdaUKMsDmKcO7CiMhaZ4VdQS7Mb4,6993
@@ -22,7 +24,7 @@ smarta2a/server/nats_client.py,sha256=akyNg1hLd9XYoLSH_qQVs8uoiTQerztgvqu_3TifSg
22
24
  smarta2a/server/request_handler.py,sha256=5KMtfpHQX6bOgk1DJbhs1fUCQ5tSvMYXWzheT3IW2Bo,26374
23
25
  smarta2a/server/send_task_handler.py,sha256=fiBeCCHCu9c2H4EJOUc0t3EZgpHVFJy4B_6qZOC140s,6336
24
26
  smarta2a/server/server.py,sha256=RKkQM8jpSndt_nOuUB0kswOqLdm7JfvjZA1O424sYdY,6722
25
- smarta2a/server/state_manager.py,sha256=omjN3C9Sl4hqw5FepnX2qBm1do6QTSE5P9uip78dvEI,6807
27
+ smarta2a/server/state_manager.py,sha256=JhF6jma8t2YtBzb3sADGvxtmUMOlDafqxllPzLb3DU0,7668
26
28
  smarta2a/server/webhook_request_processor.py,sha256=_0XoUDmueSl9CvFQE-1zgKRSts-EW8QxbmolPTfFER8,5306
27
29
  smarta2a/state_stores/__init__.py,sha256=vafxAqpwvag_cYFH2XKGk3DPmJIWJr4Ioey30yLFkVQ,220
28
30
  smarta2a/state_stores/base_state_store.py,sha256=_3LInM-qepKwwdypJTDNs9-DozBNrKVycwPwUm7bYdU,512
@@ -34,7 +36,7 @@ smarta2a/utils/task_builder.py,sha256=wqSyfVHNTaXuGESu09dhlaDi7D007gcN3-8tH-nPQ4
34
36
  smarta2a/utils/task_request_builder.py,sha256=6cOGOqj2Rg43xWM03GRJQzlIZHBptsMCJRp7oD-TDAQ,3362
35
37
  smarta2a/utils/tools_manager.py,sha256=igKYeSi0SaYzd36jUqOMPvnYd5kK55EPQ0X_pdTo5e4,4857
36
38
  smarta2a/utils/types.py,sha256=kzA6Vv5xXfu1sJuxhEXrglI9e9S6eZVIljMnsrQVyN0,13650
37
- smarta2a-0.4.11.dist-info/METADATA,sha256=qiNnMJUcRkkKVHJwXD9yT01MB9pVdvxtQeP_3K2i5Es,12988
38
- smarta2a-0.4.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
39
- smarta2a-0.4.11.dist-info/licenses/LICENSE,sha256=lDbqrxVnzDMY5KJ8JS1WhvkWE8TJaw-O-CHDy-ecsJA,2095
40
- smarta2a-0.4.11.dist-info/RECORD,,
39
+ smarta2a-0.4.12.dist-info/METADATA,sha256=_dkL7gOJcF84lsAYt8vnal-hetsh3gWC8J-CDCGLQww,12988
40
+ smarta2a-0.4.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
41
+ smarta2a-0.4.12.dist-info/licenses/LICENSE,sha256=lDbqrxVnzDMY5KJ8JS1WhvkWE8TJaw-O-CHDy-ecsJA,2095
42
+ smarta2a-0.4.12.dist-info/RECORD,,