smarta2a 0.2.1__py3-none-any.whl → 0.2.2__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.
- smarta2a/__init__.py +4 -4
- smarta2a/client/__init__.py +0 -0
- smarta2a/client/a2a_client.py +173 -0
- smarta2a/common/__init__.py +32 -0
- smarta2a/common/task_request_builder.py +114 -0
- smarta2a/server/__init__.py +3 -0
- smarta2a/{server.py → server/server.py} +1 -4
- {smarta2a-0.2.1.dist-info → smarta2a-0.2.2.dist-info}/METADATA +2 -2
- smarta2a-0.2.2.dist-info/RECORD +12 -0
- smarta2a-0.2.1.dist-info/RECORD +0 -7
- /smarta2a/{types.py → common/types.py} +0 -0
- {smarta2a-0.2.1.dist-info → smarta2a-0.2.2.dist-info}/WHEEL +0 -0
- {smarta2a-0.2.1.dist-info → smarta2a-0.2.2.dist-info}/licenses/LICENSE +0 -0
smarta2a/__init__.py
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
py_a2a - A Python package for implementing an A2A server
|
3
3
|
"""
|
4
4
|
|
5
|
-
|
5
|
+
from .server.server import SmartA2A
|
6
|
+
from .common.types import *
|
6
7
|
|
7
|
-
|
8
|
-
from . import types as models
|
8
|
+
__version__ = "0.1.0"
|
9
9
|
|
10
|
-
__all__ = ["SmartA2A"
|
10
|
+
__all__ = ["SmartA2A"]
|
File without changes
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# Library imports
|
2
|
+
from typing import Any, Literal, AsyncIterable
|
3
|
+
import httpx
|
4
|
+
import json
|
5
|
+
from httpx_sse import connect_sse
|
6
|
+
|
7
|
+
# Local imports
|
8
|
+
from smarta2a.common.types import (
|
9
|
+
PushNotificationConfig,
|
10
|
+
SendTaskStreamingResponse,
|
11
|
+
SendTaskResponse,
|
12
|
+
SendTaskStreamingRequest,
|
13
|
+
SendTaskRequest,
|
14
|
+
JSONRPCRequest,
|
15
|
+
A2AClientJSONError,
|
16
|
+
A2AClientHTTPError,
|
17
|
+
AgentCard,
|
18
|
+
AuthenticationInfo,
|
19
|
+
GetTaskResponse,
|
20
|
+
CancelTaskResponse,
|
21
|
+
SetTaskPushNotificationResponse,
|
22
|
+
GetTaskPushNotificationResponse,
|
23
|
+
)
|
24
|
+
from smarta2a.common.task_request_builder import TaskRequestBuilder
|
25
|
+
|
26
|
+
|
27
|
+
class A2AClient:
|
28
|
+
def __init__(self, agent_card: AgentCard = None, url: str = None):
|
29
|
+
if agent_card:
|
30
|
+
self.url = agent_card.url
|
31
|
+
elif url:
|
32
|
+
self.url = url
|
33
|
+
else:
|
34
|
+
raise ValueError("Must provide either agent_card or url")
|
35
|
+
|
36
|
+
async def send(
|
37
|
+
self,
|
38
|
+
*,
|
39
|
+
id: str,
|
40
|
+
role: Literal["user", "agent"] = "user",
|
41
|
+
text: str | None = None,
|
42
|
+
data: dict[str, Any] | None = None,
|
43
|
+
file_uri: str | None = None,
|
44
|
+
session_id: str | None = None,
|
45
|
+
accepted_output_modes: list[str] | None = None,
|
46
|
+
push_notification: PushNotificationConfig | None = None,
|
47
|
+
history_length: int | None = None,
|
48
|
+
metadata: dict[str, Any] | None = None,
|
49
|
+
):
|
50
|
+
params = TaskRequestBuilder.build_send_task_request(
|
51
|
+
id=id,
|
52
|
+
role=role,
|
53
|
+
text=text,
|
54
|
+
data=data,
|
55
|
+
file_uri=file_uri,
|
56
|
+
session_id=session_id,
|
57
|
+
accepted_output_modes=accepted_output_modes,
|
58
|
+
push_notification=push_notification,
|
59
|
+
history_length=history_length,
|
60
|
+
metadata=metadata,
|
61
|
+
)
|
62
|
+
request = SendTaskRequest(params=params)
|
63
|
+
return SendTaskResponse(**await self._send_request(request))
|
64
|
+
|
65
|
+
def subscribe(
|
66
|
+
self,
|
67
|
+
*,
|
68
|
+
id: str,
|
69
|
+
role: Literal["user", "agent"] = "user",
|
70
|
+
text: str | None = None,
|
71
|
+
data: dict[str, Any] | None = None,
|
72
|
+
file_uri: str | None = None,
|
73
|
+
session_id: str | None = None,
|
74
|
+
accepted_output_modes: list[str] | None = None,
|
75
|
+
push_notification: PushNotificationConfig | None = None,
|
76
|
+
history_length: int | None = None,
|
77
|
+
metadata: dict[str, Any] | None = None,
|
78
|
+
):
|
79
|
+
params = TaskRequestBuilder.build_send_task_request(
|
80
|
+
id=id,
|
81
|
+
role=role,
|
82
|
+
text=text,
|
83
|
+
data=data,
|
84
|
+
file_uri=file_uri,
|
85
|
+
session_id=session_id,
|
86
|
+
accepted_output_modes=accepted_output_modes,
|
87
|
+
push_notification=push_notification,
|
88
|
+
history_length=history_length,
|
89
|
+
metadata=metadata,
|
90
|
+
)
|
91
|
+
request = SendTaskStreamingRequest(params=params)
|
92
|
+
with httpx.Client(timeout=None) as client:
|
93
|
+
with connect_sse(
|
94
|
+
client, "POST", self.url, json=request.model_dump()
|
95
|
+
) as event_source:
|
96
|
+
try:
|
97
|
+
for sse in event_source.iter_sse():
|
98
|
+
yield SendTaskStreamingResponse(**json.loads(sse.data))
|
99
|
+
except json.JSONDecodeError as e:
|
100
|
+
raise A2AClientJSONError(str(e)) from e
|
101
|
+
except httpx.RequestError as e:
|
102
|
+
raise A2AClientHTTPError(400, str(e)) from e
|
103
|
+
|
104
|
+
async def get_task(
|
105
|
+
self,
|
106
|
+
*,
|
107
|
+
id: str,
|
108
|
+
history_length: int | None = None,
|
109
|
+
metadata: dict[str, Any] | None = None,
|
110
|
+
) -> GetTaskResponse:
|
111
|
+
req = TaskRequestBuilder.get_task(id, history_length, metadata)
|
112
|
+
raw = await self._send_request(req)
|
113
|
+
return GetTaskResponse(**raw)
|
114
|
+
|
115
|
+
async def cancel_task(
|
116
|
+
self,
|
117
|
+
*,
|
118
|
+
id: str,
|
119
|
+
metadata: dict[str, Any] | None = None,
|
120
|
+
) -> CancelTaskResponse:
|
121
|
+
req = TaskRequestBuilder.cancel_task(id, metadata)
|
122
|
+
raw = await self._send_request(req)
|
123
|
+
return CancelTaskResponse(**raw)
|
124
|
+
|
125
|
+
async def set_push_notification(
|
126
|
+
self,
|
127
|
+
*,
|
128
|
+
id: str,
|
129
|
+
url: str,
|
130
|
+
token: str | None = None,
|
131
|
+
authentication: AuthenticationInfo | dict[str, Any] | None = None,
|
132
|
+
) -> SetTaskPushNotificationResponse:
|
133
|
+
req = TaskRequestBuilder.set_push_notification(id, url, token, authentication)
|
134
|
+
raw = await self._send_request(req)
|
135
|
+
return SetTaskPushNotificationResponse(**raw)
|
136
|
+
|
137
|
+
async def get_push_notification(
|
138
|
+
self,
|
139
|
+
*,
|
140
|
+
id: str,
|
141
|
+
metadata: dict[str, Any] | None = None,
|
142
|
+
) -> GetTaskPushNotificationResponse:
|
143
|
+
req = TaskRequestBuilder.get_push_notification(id, metadata)
|
144
|
+
raw = await self._send_request(req)
|
145
|
+
return GetTaskPushNotificationResponse(**raw)
|
146
|
+
|
147
|
+
|
148
|
+
async def _send_request(self, request: JSONRPCRequest) -> dict[str, Any]:
|
149
|
+
async with httpx.AsyncClient() as client:
|
150
|
+
try:
|
151
|
+
# Image generation could take time, adding timeout
|
152
|
+
response = await client.post(
|
153
|
+
self.url, json=request.model_dump(), timeout=30
|
154
|
+
)
|
155
|
+
response.raise_for_status()
|
156
|
+
return response.json()
|
157
|
+
except httpx.HTTPStatusError as e:
|
158
|
+
raise A2AClientHTTPError(e.response.status_code, str(e)) from e
|
159
|
+
except json.JSONDecodeError as e:
|
160
|
+
raise A2AClientJSONError(str(e)) from e
|
161
|
+
|
162
|
+
async def _send_streaming_request(self, request: JSONRPCRequest) -> AsyncIterable[SendTaskStreamingResponse]:
|
163
|
+
with httpx.Client(timeout=None) as client:
|
164
|
+
with connect_sse(
|
165
|
+
client, "POST", self.url, json=request.model_dump()
|
166
|
+
) as event_source:
|
167
|
+
try:
|
168
|
+
for sse in event_source.iter_sse():
|
169
|
+
yield SendTaskStreamingResponse(**json.loads(sse.data))
|
170
|
+
except json.JSONDecodeError as e:
|
171
|
+
raise A2AClientJSONError(str(e)) from e
|
172
|
+
except httpx.RequestError as e:
|
173
|
+
raise A2AClientHTTPError(400, str(e)) from e
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from .types import *
|
2
|
+
|
3
|
+
__all__ = [
|
4
|
+
"TaskSendParams",
|
5
|
+
"SendTaskRequest",
|
6
|
+
"GetTaskRequest",
|
7
|
+
"CancelTaskRequest",
|
8
|
+
"CancelTaskResponse",
|
9
|
+
"Task",
|
10
|
+
"TaskStatus",
|
11
|
+
"TaskState",
|
12
|
+
"Artifact",
|
13
|
+
"TextPart",
|
14
|
+
"FilePart",
|
15
|
+
"FileContent",
|
16
|
+
"A2AResponse",
|
17
|
+
"A2ARequest",
|
18
|
+
"TaskQueryParams",
|
19
|
+
"TaskStatusUpdateEvent",
|
20
|
+
"TaskArtifactUpdateEvent",
|
21
|
+
"A2AStatus",
|
22
|
+
"A2AStreamResponse",
|
23
|
+
"SendTaskResponse",
|
24
|
+
"Message",
|
25
|
+
"InternalError",
|
26
|
+
"TaskNotFoundError",
|
27
|
+
"SetTaskPushNotificationRequest",
|
28
|
+
"GetTaskPushNotificationRequest",
|
29
|
+
"SetTaskPushNotificationResponse",
|
30
|
+
"GetTaskPushNotificationResponse",
|
31
|
+
"TaskPushNotificationConfig"
|
32
|
+
]
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# Library imports
|
2
|
+
from typing import Any, Literal
|
3
|
+
from uuid import uuid4
|
4
|
+
|
5
|
+
# Local imports
|
6
|
+
from smarta2a.common.types import (
|
7
|
+
TaskPushNotificationConfig,
|
8
|
+
PushNotificationConfig,
|
9
|
+
TaskSendParams,
|
10
|
+
TextPart,
|
11
|
+
DataPart,
|
12
|
+
FilePart,
|
13
|
+
FileContent,
|
14
|
+
Message,
|
15
|
+
Part,
|
16
|
+
TaskQueryParams,
|
17
|
+
TaskIdParams,
|
18
|
+
GetTaskRequest,
|
19
|
+
CancelTaskRequest,
|
20
|
+
SetTaskPushNotificationRequest,
|
21
|
+
GetTaskPushNotificationRequest,
|
22
|
+
AuthenticationInfo,
|
23
|
+
)
|
24
|
+
|
25
|
+
class TaskRequestBuilder:
|
26
|
+
@staticmethod
|
27
|
+
def build_send_task_request(
|
28
|
+
*,
|
29
|
+
id: str,
|
30
|
+
role: Literal["user", "agent"] = "user",
|
31
|
+
text: str | None = None,
|
32
|
+
data: dict[str, Any] | None = None,
|
33
|
+
file_uri: str | None = None,
|
34
|
+
session_id: str | None = None,
|
35
|
+
accepted_output_modes: list[str] | None = None,
|
36
|
+
push_notification: PushNotificationConfig | None = None,
|
37
|
+
history_length: int | None = None,
|
38
|
+
metadata: dict[str, Any] | None = None,
|
39
|
+
) -> TaskSendParams:
|
40
|
+
parts: list[Part] = []
|
41
|
+
|
42
|
+
if text is not None:
|
43
|
+
parts.append(TextPart(text=text))
|
44
|
+
|
45
|
+
if data is not None:
|
46
|
+
parts.append(DataPart(data=data))
|
47
|
+
|
48
|
+
if file_uri is not None:
|
49
|
+
file_content = FileContent(uri=file_uri)
|
50
|
+
parts.append(FilePart(file=file_content))
|
51
|
+
|
52
|
+
message = Message(role=role, parts=parts)
|
53
|
+
|
54
|
+
return TaskSendParams(
|
55
|
+
id=id,
|
56
|
+
sessionId=session_id or uuid4().hex,
|
57
|
+
message=message,
|
58
|
+
acceptedOutputModes=accepted_output_modes,
|
59
|
+
pushNotification=push_notification,
|
60
|
+
historyLength=history_length,
|
61
|
+
metadata=metadata,
|
62
|
+
)
|
63
|
+
|
64
|
+
@staticmethod
|
65
|
+
def get_task(
|
66
|
+
id: str,
|
67
|
+
history_length: int | None = None,
|
68
|
+
metadata: dict[str, Any] | None = None,
|
69
|
+
) -> GetTaskRequest:
|
70
|
+
params = TaskQueryParams(
|
71
|
+
id=id,
|
72
|
+
historyLength=history_length,
|
73
|
+
metadata=metadata,
|
74
|
+
)
|
75
|
+
return GetTaskRequest(params=params)
|
76
|
+
|
77
|
+
@staticmethod
|
78
|
+
def cancel_task(
|
79
|
+
id: str,
|
80
|
+
metadata: dict[str, Any] | None = None,
|
81
|
+
) -> CancelTaskRequest:
|
82
|
+
params = TaskIdParams(id=id, metadata=metadata)
|
83
|
+
return CancelTaskRequest(params=params)
|
84
|
+
|
85
|
+
@staticmethod
|
86
|
+
def set_push_notification(
|
87
|
+
id: str,
|
88
|
+
url: str,
|
89
|
+
token: str | None = None,
|
90
|
+
authentication: AuthenticationInfo | dict[str, Any] | None = None,
|
91
|
+
) -> SetTaskPushNotificationRequest:
|
92
|
+
# allow passing AuthenticationInfo _or_ raw dict
|
93
|
+
auth = (
|
94
|
+
authentication
|
95
|
+
if isinstance(authentication, AuthenticationInfo)
|
96
|
+
else (AuthenticationInfo(**authentication) if authentication else None)
|
97
|
+
)
|
98
|
+
push_cfg = TaskPushNotificationConfig(
|
99
|
+
id=id,
|
100
|
+
pushNotificationConfig=PushNotificationConfig(
|
101
|
+
url=url,
|
102
|
+
token=token,
|
103
|
+
authentication=auth,
|
104
|
+
)
|
105
|
+
)
|
106
|
+
return SetTaskPushNotificationRequest(params=push_cfg)
|
107
|
+
|
108
|
+
@staticmethod
|
109
|
+
def get_push_notification(
|
110
|
+
id: str,
|
111
|
+
metadata: dict[str, Any] | None = None,
|
112
|
+
) -> GetTaskPushNotificationRequest:
|
113
|
+
params = TaskIdParams(id=id, metadata=metadata)
|
114
|
+
return GetTaskPushNotificationRequest(params=params)
|
@@ -1,6 +1,5 @@
|
|
1
1
|
from typing import Callable, Any, Optional, Dict, Union, List, AsyncGenerator
|
2
2
|
import json
|
3
|
-
import inspect
|
4
3
|
from datetime import datetime
|
5
4
|
from collections import defaultdict
|
6
5
|
from fastapi import FastAPI, Request, HTTPException, APIRouter
|
@@ -11,8 +10,7 @@ import uvicorn
|
|
11
10
|
from fastapi.responses import StreamingResponse
|
12
11
|
from uuid import uuid4
|
13
12
|
|
14
|
-
|
15
|
-
from .types import (
|
13
|
+
from smarta2a.common.types import (
|
16
14
|
JSONRPCResponse,
|
17
15
|
Task,
|
18
16
|
Artifact,
|
@@ -39,7 +37,6 @@ from .types import (
|
|
39
37
|
JSONParseError,
|
40
38
|
InvalidRequestError,
|
41
39
|
MethodNotFoundError,
|
42
|
-
ContentTypeNotSupportedError,
|
43
40
|
InternalError,
|
44
41
|
UnsupportedOperationError,
|
45
42
|
TaskNotFoundError,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: smarta2a
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.2
|
4
4
|
Summary: A Python package for creating servers and clients following Google's Agent2Agent protocol
|
5
5
|
Project-URL: Homepage, https://github.com/siddharthsma/smarta2a
|
6
6
|
Project-URL: Bug Tracker, https://github.com/siddharthsma/smarta2a/issues
|
@@ -45,7 +45,7 @@ pip install smarta2a
|
|
45
45
|
## Simple Echo Server Implementation
|
46
46
|
|
47
47
|
```python
|
48
|
-
from smarta2a import SmartA2A
|
48
|
+
from smarta2a.server import SmartA2A
|
49
49
|
|
50
50
|
app = SmartA2A("EchoServer")
|
51
51
|
|
@@ -0,0 +1,12 @@
|
|
1
|
+
smarta2a/__init__.py,sha256=V6TtUzRtzqDUV8i5PDU6amAWVeiENdibgMV8btix26Y,176
|
2
|
+
smarta2a/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
smarta2a/client/a2a_client.py,sha256=ejWZq8F-G49sse2EMeVKg9ISiLXyWe-ElacQdjkzzfY,6203
|
4
|
+
smarta2a/common/__init__.py,sha256=5db5VgDGgbMUGEF-xuyaC3qrgRQkUE9WAITkFSiNqSA,702
|
5
|
+
smarta2a/common/task_request_builder.py,sha256=GPa_T8jy_Z5Lj-i5Wvbw-mhRiUsSQUbWGZxQbq834T8,3363
|
6
|
+
smarta2a/common/types.py,sha256=_UuFtOsnHIIqfQ2m_FiIBBp141iYmhpPGgxE0jmHSHg,10807
|
7
|
+
smarta2a/server/__init__.py,sha256=f2X454Ll4vJc02V4JLJHTN-h8u0TBm4d_FkiO4t686U,53
|
8
|
+
smarta2a/server/server.py,sha256=JpRyJMdu4xb76Vye4SXN9Y06BzjVoHLFL2BGK6S9Lxo,30880
|
9
|
+
smarta2a-0.2.2.dist-info/METADATA,sha256=R3bU9hf8AFS1GxLAT53C6UMQN1sGM6x2rXWXvt0EHZI,2485
|
10
|
+
smarta2a-0.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
11
|
+
smarta2a-0.2.2.dist-info/licenses/LICENSE,sha256=ECMEVHuFkvpEmH-_A9HSxs_UnnsUqpCkiAYNHPCf2z0,1078
|
12
|
+
smarta2a-0.2.2.dist-info/RECORD,,
|
smarta2a-0.2.1.dist-info/RECORD
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
smarta2a/__init__.py,sha256=f_RqeaAHiBXO0O8n2kR8EBLGM3ezNwmR-CV-xHeOHLM,182
|
2
|
-
smarta2a/server.py,sha256=lFUlAz4TEeEXCs2bgJmMTN7JpZTwYEKXyt4KWEQwS6U,30915
|
3
|
-
smarta2a/types.py,sha256=_UuFtOsnHIIqfQ2m_FiIBBp141iYmhpPGgxE0jmHSHg,10807
|
4
|
-
smarta2a-0.2.1.dist-info/METADATA,sha256=H4os9rBnBzByJI1FWr7vYW2w8Or8PInU7XtmwsHCIcY,2478
|
5
|
-
smarta2a-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
6
|
-
smarta2a-0.2.1.dist-info/licenses/LICENSE,sha256=ECMEVHuFkvpEmH-_A9HSxs_UnnsUqpCkiAYNHPCf2z0,1078
|
7
|
-
smarta2a-0.2.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|