agentstack-sdk 0.5.2rc2__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.
- agentstack_sdk/__init__.py +6 -0
- agentstack_sdk/a2a/__init__.py +2 -0
- agentstack_sdk/a2a/extensions/__init__.py +8 -0
- agentstack_sdk/a2a/extensions/auth/__init__.py +5 -0
- agentstack_sdk/a2a/extensions/auth/oauth/__init__.py +4 -0
- agentstack_sdk/a2a/extensions/auth/oauth/oauth.py +151 -0
- agentstack_sdk/a2a/extensions/auth/oauth/storage/__init__.py +5 -0
- agentstack_sdk/a2a/extensions/auth/oauth/storage/base.py +11 -0
- agentstack_sdk/a2a/extensions/auth/oauth/storage/memory.py +38 -0
- agentstack_sdk/a2a/extensions/auth/secrets/__init__.py +4 -0
- agentstack_sdk/a2a/extensions/auth/secrets/secrets.py +77 -0
- agentstack_sdk/a2a/extensions/base.py +205 -0
- agentstack_sdk/a2a/extensions/common/__init__.py +4 -0
- agentstack_sdk/a2a/extensions/common/form.py +149 -0
- agentstack_sdk/a2a/extensions/exceptions.py +11 -0
- agentstack_sdk/a2a/extensions/interactions/__init__.py +4 -0
- agentstack_sdk/a2a/extensions/interactions/approval.py +125 -0
- agentstack_sdk/a2a/extensions/services/__init__.py +8 -0
- agentstack_sdk/a2a/extensions/services/embedding.py +106 -0
- agentstack_sdk/a2a/extensions/services/form.py +54 -0
- agentstack_sdk/a2a/extensions/services/llm.py +100 -0
- agentstack_sdk/a2a/extensions/services/mcp.py +193 -0
- agentstack_sdk/a2a/extensions/services/platform.py +141 -0
- agentstack_sdk/a2a/extensions/tools/__init__.py +5 -0
- agentstack_sdk/a2a/extensions/tools/call.py +114 -0
- agentstack_sdk/a2a/extensions/tools/exceptions.py +6 -0
- agentstack_sdk/a2a/extensions/ui/__init__.py +10 -0
- agentstack_sdk/a2a/extensions/ui/agent_detail.py +54 -0
- agentstack_sdk/a2a/extensions/ui/canvas.py +71 -0
- agentstack_sdk/a2a/extensions/ui/citation.py +78 -0
- agentstack_sdk/a2a/extensions/ui/error.py +223 -0
- agentstack_sdk/a2a/extensions/ui/form_request.py +52 -0
- agentstack_sdk/a2a/extensions/ui/settings.py +73 -0
- agentstack_sdk/a2a/extensions/ui/trajectory.py +70 -0
- agentstack_sdk/a2a/types.py +104 -0
- agentstack_sdk/platform/__init__.py +12 -0
- agentstack_sdk/platform/client.py +123 -0
- agentstack_sdk/platform/common.py +37 -0
- agentstack_sdk/platform/configuration.py +47 -0
- agentstack_sdk/platform/context.py +291 -0
- agentstack_sdk/platform/file.py +295 -0
- agentstack_sdk/platform/model_provider.py +131 -0
- agentstack_sdk/platform/provider.py +219 -0
- agentstack_sdk/platform/provider_build.py +190 -0
- agentstack_sdk/platform/types.py +45 -0
- agentstack_sdk/platform/user.py +70 -0
- agentstack_sdk/platform/user_feedback.py +42 -0
- agentstack_sdk/platform/variables.py +44 -0
- agentstack_sdk/platform/vector_store.py +217 -0
- agentstack_sdk/py.typed +0 -0
- agentstack_sdk/server/__init__.py +4 -0
- agentstack_sdk/server/agent.py +594 -0
- agentstack_sdk/server/app.py +87 -0
- agentstack_sdk/server/constants.py +9 -0
- agentstack_sdk/server/context.py +68 -0
- agentstack_sdk/server/dependencies.py +117 -0
- agentstack_sdk/server/exceptions.py +3 -0
- agentstack_sdk/server/middleware/__init__.py +3 -0
- agentstack_sdk/server/middleware/platform_auth_backend.py +131 -0
- agentstack_sdk/server/server.py +376 -0
- agentstack_sdk/server/store/__init__.py +3 -0
- agentstack_sdk/server/store/context_store.py +35 -0
- agentstack_sdk/server/store/memory_context_store.py +59 -0
- agentstack_sdk/server/store/platform_context_store.py +58 -0
- agentstack_sdk/server/telemetry.py +53 -0
- agentstack_sdk/server/utils.py +26 -0
- agentstack_sdk/types.py +15 -0
- agentstack_sdk/util/__init__.py +4 -0
- agentstack_sdk/util/file.py +260 -0
- agentstack_sdk/util/httpx.py +18 -0
- agentstack_sdk/util/logging.py +63 -0
- agentstack_sdk/util/resource_context.py +44 -0
- agentstack_sdk/util/utils.py +47 -0
- agentstack_sdk-0.5.2rc2.dist-info/METADATA +120 -0
- agentstack_sdk-0.5.2rc2.dist-info/RECORD +76 -0
- agentstack_sdk-0.5.2rc2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import base64
|
|
5
|
+
import typing
|
|
6
|
+
from collections.abc import AsyncIterator, Iterator
|
|
7
|
+
from contextlib import AsyncExitStack, asynccontextmanager
|
|
8
|
+
from functools import cached_property
|
|
9
|
+
from typing import Protocol
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
from a2a.types import FilePart, FileWithBytes, FileWithUri
|
|
13
|
+
from httpx._decoders import LineDecoder
|
|
14
|
+
from pydantic import AnyUrl, HttpUrl, RootModel, UrlConstraints
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LoadedFile(Protocol):
|
|
18
|
+
filename: str | None
|
|
19
|
+
content_type: str
|
|
20
|
+
file_size_bytes: int | None
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def content(self) -> bytes: ...
|
|
24
|
+
@property
|
|
25
|
+
def text(self) -> str: ...
|
|
26
|
+
|
|
27
|
+
def read(self) -> bytes:
|
|
28
|
+
"""
|
|
29
|
+
Read and return the response content.
|
|
30
|
+
"""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]:
|
|
34
|
+
"""
|
|
35
|
+
A byte-iterator over the decoded response content.
|
|
36
|
+
This allows us to handle gzip, deflate, brotli, and zstd encoded responses.
|
|
37
|
+
"""
|
|
38
|
+
yield b""
|
|
39
|
+
|
|
40
|
+
def iter_text(self, chunk_size: int | None = None) -> Iterator[str]:
|
|
41
|
+
"""
|
|
42
|
+
A str-iterator over the decoded response content
|
|
43
|
+
that handles both gzip, deflate, etc but also detects the content's
|
|
44
|
+
string encoding.
|
|
45
|
+
"""
|
|
46
|
+
yield ""
|
|
47
|
+
|
|
48
|
+
def iter_lines(self) -> Iterator[str]:
|
|
49
|
+
yield ""
|
|
50
|
+
|
|
51
|
+
async def aread(self) -> bytes: ...
|
|
52
|
+
|
|
53
|
+
async def aiter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
|
|
54
|
+
"""
|
|
55
|
+
A byte-iterator over the decoded response content.
|
|
56
|
+
This allows us to handle gzip, deflate, brotli, and zstd encoded responses.
|
|
57
|
+
"""
|
|
58
|
+
yield b""
|
|
59
|
+
|
|
60
|
+
async def aiter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]:
|
|
61
|
+
"""
|
|
62
|
+
A str-iterator over the decoded response content
|
|
63
|
+
that handles both gzip, deflate, etc but also detects the content's
|
|
64
|
+
string encoding.
|
|
65
|
+
"""
|
|
66
|
+
yield ""
|
|
67
|
+
|
|
68
|
+
async def aiter_lines(self) -> AsyncIterator[str]:
|
|
69
|
+
yield ""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class LoadedFileWithBytes(LoadedFile):
|
|
73
|
+
_content: bytes
|
|
74
|
+
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
content: bytes,
|
|
78
|
+
filename: str | None = None,
|
|
79
|
+
content_type: str | None = None,
|
|
80
|
+
encoding: str | None = None,
|
|
81
|
+
):
|
|
82
|
+
self.filename = filename
|
|
83
|
+
self.content_type = content_type or "application/octet-stream"
|
|
84
|
+
self.file_size_bytes = len(content)
|
|
85
|
+
self._content = content
|
|
86
|
+
self._encoding = encoding or "utf-8"
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def content(self) -> bytes:
|
|
90
|
+
return self._content
|
|
91
|
+
|
|
92
|
+
@cached_property
|
|
93
|
+
def text(self) -> str: # pyright: ignore [reportIncompatibleMethodOverride]
|
|
94
|
+
return self._content.decode(self._encoding)
|
|
95
|
+
|
|
96
|
+
def read(self) -> bytes:
|
|
97
|
+
return b"".join(self.iter_bytes())
|
|
98
|
+
|
|
99
|
+
def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]:
|
|
100
|
+
chunk_size = len(self._content) if chunk_size is None else chunk_size
|
|
101
|
+
for i in range(0, len(self._content), max(chunk_size, 1)):
|
|
102
|
+
yield self._content[i : i + chunk_size]
|
|
103
|
+
|
|
104
|
+
def iter_text(self, chunk_size: int | None = None) -> Iterator[str]:
|
|
105
|
+
chunk_size = len(self._content) if chunk_size is None else chunk_size
|
|
106
|
+
for i in range(0, len(self._content), max(chunk_size, 1)):
|
|
107
|
+
yield self.text[i : i + chunk_size]
|
|
108
|
+
|
|
109
|
+
def iter_lines(self) -> typing.Iterator[str]:
|
|
110
|
+
decoder = LineDecoder()
|
|
111
|
+
for text in self.iter_text():
|
|
112
|
+
for line in decoder.decode(text):
|
|
113
|
+
yield line
|
|
114
|
+
for line in decoder.flush():
|
|
115
|
+
yield line
|
|
116
|
+
|
|
117
|
+
async def aread(self) -> bytes:
|
|
118
|
+
return self._content
|
|
119
|
+
|
|
120
|
+
async def aiter_bytes(self, chunk_size: int | None = None) -> typing.AsyncIterator[bytes]:
|
|
121
|
+
for chunk in self.iter_bytes(chunk_size):
|
|
122
|
+
yield chunk
|
|
123
|
+
|
|
124
|
+
async def aiter_text(self, chunk_size: int | None = None) -> typing.AsyncIterator[str]:
|
|
125
|
+
for chunk in self.iter_text(chunk_size):
|
|
126
|
+
yield chunk
|
|
127
|
+
|
|
128
|
+
async def aiter_lines(self) -> typing.AsyncIterator[str]:
|
|
129
|
+
for line in self.iter_lines():
|
|
130
|
+
yield line
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class LoadedFileWithUri:
|
|
134
|
+
_response: httpx.Response # a (potentially still opened) response object
|
|
135
|
+
|
|
136
|
+
def __init__(
|
|
137
|
+
self,
|
|
138
|
+
response: httpx.Response,
|
|
139
|
+
filename: str | None = None,
|
|
140
|
+
content_type: str | None = None,
|
|
141
|
+
file_size_bytes: int | None = None,
|
|
142
|
+
):
|
|
143
|
+
self._response = response
|
|
144
|
+
|
|
145
|
+
response_filename = self._response.headers.get("Content-Disposition", "").split("filename=")[-1]
|
|
146
|
+
response_content_type = self._response.headers.get("Content-Type", "application/octet-stream")
|
|
147
|
+
|
|
148
|
+
self.file_size_bytes = file_size_bytes or int(response.headers.get("Content-Length", "0")) or None
|
|
149
|
+
self.filename = filename or response_filename
|
|
150
|
+
self.content_type = content_type or response_content_type or "application/octet-stream"
|
|
151
|
+
self._response = response
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def content(self) -> bytes:
|
|
155
|
+
return self._response.content
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def text(self) -> str:
|
|
159
|
+
return self._response.text
|
|
160
|
+
|
|
161
|
+
def read(self) -> bytes:
|
|
162
|
+
"""
|
|
163
|
+
Read and return the response content.
|
|
164
|
+
"""
|
|
165
|
+
return self._response.read()
|
|
166
|
+
|
|
167
|
+
def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]:
|
|
168
|
+
"""
|
|
169
|
+
A byte-iterator over the decoded response content.
|
|
170
|
+
This allows us to handle gzip, deflate, brotli, and zstd encoded responses.
|
|
171
|
+
"""
|
|
172
|
+
yield from self._response.iter_bytes(chunk_size)
|
|
173
|
+
|
|
174
|
+
def iter_text(self, chunk_size: int | None = None) -> Iterator[str]:
|
|
175
|
+
"""
|
|
176
|
+
A str-iterator over the decoded response content
|
|
177
|
+
that handles both gzip, deflate, etc but also detects the content's
|
|
178
|
+
string encoding.
|
|
179
|
+
"""
|
|
180
|
+
yield from self._response.iter_text(chunk_size)
|
|
181
|
+
|
|
182
|
+
def iter_lines(self) -> Iterator[str]:
|
|
183
|
+
yield from self._response.iter_lines()
|
|
184
|
+
|
|
185
|
+
async def aread(self) -> bytes:
|
|
186
|
+
"""
|
|
187
|
+
Read and return the response content.
|
|
188
|
+
"""
|
|
189
|
+
return await self._response.aread()
|
|
190
|
+
|
|
191
|
+
async def aiter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
|
|
192
|
+
"""
|
|
193
|
+
A byte-iterator over the decoded response content.
|
|
194
|
+
This allows us to handle gzip, deflate, brotli, and zstd encoded responses.
|
|
195
|
+
"""
|
|
196
|
+
async for chunk in self._response.aiter_bytes(chunk_size):
|
|
197
|
+
yield chunk
|
|
198
|
+
|
|
199
|
+
async def aiter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]:
|
|
200
|
+
"""
|
|
201
|
+
A str-iterator over the decoded response content
|
|
202
|
+
that handles both gzip, deflate, etc but also detects the content's
|
|
203
|
+
string encoding.
|
|
204
|
+
"""
|
|
205
|
+
async for chunk in self._response.aiter_text(chunk_size):
|
|
206
|
+
yield chunk
|
|
207
|
+
|
|
208
|
+
async def aiter_lines(self) -> AsyncIterator[str]:
|
|
209
|
+
async for line in self._response.aiter_lines():
|
|
210
|
+
yield line
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class PlatformFileUrl(AnyUrl):
|
|
214
|
+
_constraints = UrlConstraints(allowed_schemes=["agentstack"])
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def file_id(self) -> str:
|
|
218
|
+
assert self.host
|
|
219
|
+
return self.host
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
UriType = RootModel[PlatformFileUrl | HttpUrl]
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@asynccontextmanager
|
|
226
|
+
async def load_file(
|
|
227
|
+
part: FilePart,
|
|
228
|
+
stream: bool = False,
|
|
229
|
+
client: httpx.AsyncClient | None = None,
|
|
230
|
+
) -> AsyncIterator[LoadedFile]:
|
|
231
|
+
"""
|
|
232
|
+
:param stream: if stream is set to False, 'content' and 'text' fields are immediately available.
|
|
233
|
+
Otherwise, they are only available after calling the '(a)read' method.
|
|
234
|
+
"""
|
|
235
|
+
match part.file:
|
|
236
|
+
case FileWithUri(mime_type=content_type, name=filename, uri=uri):
|
|
237
|
+
match UriType.model_validate(uri).root:
|
|
238
|
+
case PlatformFileUrl() as url:
|
|
239
|
+
from agentstack_sdk.platform import File
|
|
240
|
+
|
|
241
|
+
async with File.load_content(url.file_id, stream=stream) as file:
|
|
242
|
+
# override filename and content_type from part
|
|
243
|
+
if filename:
|
|
244
|
+
file.filename = filename
|
|
245
|
+
if content_type:
|
|
246
|
+
file.content_type = content_type
|
|
247
|
+
yield file
|
|
248
|
+
case HttpUrl():
|
|
249
|
+
async with AsyncExitStack() as stack:
|
|
250
|
+
if client is None:
|
|
251
|
+
client = await stack.enter_async_context(httpx.AsyncClient())
|
|
252
|
+
async with client.stream("GET", uri) as response:
|
|
253
|
+
response.raise_for_status()
|
|
254
|
+
file = LoadedFileWithUri(response=response, filename=filename, content_type=content_type)
|
|
255
|
+
if not stream:
|
|
256
|
+
await file.aread()
|
|
257
|
+
yield file
|
|
258
|
+
|
|
259
|
+
case FileWithBytes(bytes=content, name=filename, mime_type=content_type):
|
|
260
|
+
yield LoadedFileWithBytes(content=base64.b64decode(content), filename=filename, content_type=content_type)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BearerAuth(httpx.Auth):
|
|
9
|
+
def __init__(self, token: str):
|
|
10
|
+
self.token = token
|
|
11
|
+
|
|
12
|
+
def auth_flow(self, request: httpx.Request):
|
|
13
|
+
request.headers["Authorization"] = f"Bearer {self.token}"
|
|
14
|
+
yield request
|
|
15
|
+
|
|
16
|
+
async def async_auth_flow(self, request: httpx.Request):
|
|
17
|
+
request.headers["Authorization"] = f"Bearer {self.token}"
|
|
18
|
+
yield request
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import logging.config
|
|
6
|
+
import sys
|
|
7
|
+
from logging import getLevelName
|
|
8
|
+
from typing import ClassVar
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("agentstack_sdk")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ColoredFormatter(logging.Formatter):
|
|
14
|
+
COLORS: ClassVar[dict[str, str]] = {
|
|
15
|
+
"DEBUG": "\033[36m", # Cyan
|
|
16
|
+
"INFO": "\033[32m", # Green
|
|
17
|
+
"WARNING": "\033[33m", # Yellow
|
|
18
|
+
"ERROR": "\033[31m", # Red
|
|
19
|
+
"CRITICAL": "\033[35m", # Magenta
|
|
20
|
+
"RESET": "\033[0m", # Reset
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def format(self, record):
|
|
24
|
+
log_message = super().format(record)
|
|
25
|
+
color = self.COLORS.get(record.levelname, self.COLORS["RESET"])
|
|
26
|
+
reset = self.COLORS["RESET"]
|
|
27
|
+
return f"{color}{log_message}{reset}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Dictionary configuration
|
|
31
|
+
LOGGING_CONFIG = {
|
|
32
|
+
"version": 1,
|
|
33
|
+
"disable_existing_loggers": False,
|
|
34
|
+
"formatters": {
|
|
35
|
+
"colored": {
|
|
36
|
+
"()": ColoredFormatter,
|
|
37
|
+
"format": "%(asctime)s | %(levelname)-8s | %(name)-12s | %(message)s",
|
|
38
|
+
"datefmt": "%Y-%m-%d %H:%M:%S",
|
|
39
|
+
},
|
|
40
|
+
"simple": {"format": "%(asctime)s | %(levelname)-8s | %(message)s", "datefmt": "%H:%M:%S"},
|
|
41
|
+
},
|
|
42
|
+
"handlers": {"console": {"class": "logging.StreamHandler", "formatter": "colored", "stream": sys.stdout}},
|
|
43
|
+
"loggers": {
|
|
44
|
+
"root": {"level": "INFO", "handlers": ["console"]},
|
|
45
|
+
"httpx": {"level": "WARNING"},
|
|
46
|
+
"uvicorn": {"handlers": ["console"], "level": "INFO", "propagate": False},
|
|
47
|
+
"uvicorn.error": {"level": "INFO"},
|
|
48
|
+
"uvicorn.access": {"handlers": ["console"], "level": "WARNING", "propagate": False},
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def configure_logger(level: int | str | None = None) -> None:
|
|
54
|
+
if level is not None:
|
|
55
|
+
level = level if isinstance(level, int) else logging.getLevelNamesMapping()[level.upper()]
|
|
56
|
+
logging.config.dictConfig(
|
|
57
|
+
{
|
|
58
|
+
**LOGGING_CONFIG,
|
|
59
|
+
"loggers": {"root": {"level": getLevelName(level), "handlers": ["console"]}},
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
logging.config.dictConfig(LOGGING_CONFIG)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import contextlib
|
|
5
|
+
import contextvars
|
|
6
|
+
import typing
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def noop():
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
P = typing.ParamSpec("P")
|
|
14
|
+
T = typing.TypeVar("T")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def resource_context(
|
|
18
|
+
factory: typing.Callable[P, T],
|
|
19
|
+
default_factory: typing.Callable[[], T],
|
|
20
|
+
) -> tuple[typing.Callable[[], T], typing.Callable[P, contextlib.AbstractContextManager[T]]]:
|
|
21
|
+
contextvar: contextvars.ContextVar[T] = contextvars.ContextVar(f"resource_context({factory.__name__})")
|
|
22
|
+
|
|
23
|
+
def use_resource(*args: P.args, **kwargs: P.kwargs):
|
|
24
|
+
@contextlib.contextmanager
|
|
25
|
+
def manager():
|
|
26
|
+
resource = factory(*args, **kwargs)
|
|
27
|
+
token = contextvar.set(resource)
|
|
28
|
+
try:
|
|
29
|
+
yield resource
|
|
30
|
+
finally:
|
|
31
|
+
contextvar.reset(token)
|
|
32
|
+
|
|
33
|
+
return manager()
|
|
34
|
+
|
|
35
|
+
def get_resource() -> T:
|
|
36
|
+
try:
|
|
37
|
+
return contextvar.get()
|
|
38
|
+
except LookupError:
|
|
39
|
+
return default_factory()
|
|
40
|
+
|
|
41
|
+
return get_resource, use_resource
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
__all__ = ["resource_context"]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from collections.abc import AsyncIterator
|
|
6
|
+
from datetime import UTC, datetime
|
|
7
|
+
from typing import Any, TypeVar, cast
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from httpx import HTTPStatusError
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
V = TypeVar("V")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def filter_dict(map: dict[str, T | V], value_to_exclude: V = None) -> dict[str, T]:
|
|
17
|
+
"""Remove entries with unwanted values (None by default) from dictionary."""
|
|
18
|
+
return {key: cast(T, value) for key, value in map.items() if value is not value_to_exclude}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def parse_stream(response: httpx.Response) -> AsyncIterator[dict[str, Any]]:
|
|
22
|
+
if response.is_error:
|
|
23
|
+
error = ""
|
|
24
|
+
try:
|
|
25
|
+
[error] = [json.loads(message) async for message in response.aiter_text()]
|
|
26
|
+
error = error.get("detail", str(error))
|
|
27
|
+
except Exception:
|
|
28
|
+
response.raise_for_status()
|
|
29
|
+
raise HTTPStatusError(message=error, request=response.request, response=response)
|
|
30
|
+
async for line in response.aiter_lines():
|
|
31
|
+
if line:
|
|
32
|
+
data = re.sub("^data:", "", line).strip()
|
|
33
|
+
try:
|
|
34
|
+
yield json.loads(data)
|
|
35
|
+
except json.JSONDecodeError:
|
|
36
|
+
yield {"event": data}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def extract_messages(exc: BaseException) -> list[tuple[str, str]]:
|
|
40
|
+
if isinstance(exc, BaseExceptionGroup):
|
|
41
|
+
return [(exc_type, msg) for e in exc.exceptions for exc_type, msg in extract_messages(e)]
|
|
42
|
+
else:
|
|
43
|
+
return [(type(exc).__name__, str(exc))]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def utc_now() -> datetime:
|
|
47
|
+
return datetime.now(UTC)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: agentstack-sdk
|
|
3
|
+
Version: 0.5.2rc2
|
|
4
|
+
Summary: Agent Stack SDK
|
|
5
|
+
Author: IBM Corp.
|
|
6
|
+
Requires-Dist: a2a-sdk==0.3.21
|
|
7
|
+
Requires-Dist: objprint>=0.3.0
|
|
8
|
+
Requires-Dist: uvicorn>=0.35.0
|
|
9
|
+
Requires-Dist: asyncclick>=8.1.8
|
|
10
|
+
Requires-Dist: sse-starlette>=2.2.1
|
|
11
|
+
Requires-Dist: starlette>=0.47.2
|
|
12
|
+
Requires-Dist: anyio>=4.9.0
|
|
13
|
+
Requires-Dist: opentelemetry-api>=1.35.0
|
|
14
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.35.0
|
|
15
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.56b0
|
|
16
|
+
Requires-Dist: opentelemetry-sdk>=1.35.0
|
|
17
|
+
Requires-Dist: tenacity>=9.1.2
|
|
18
|
+
Requires-Dist: janus>=2.0.0
|
|
19
|
+
Requires-Dist: httpx
|
|
20
|
+
Requires-Dist: mcp>=1.12.3
|
|
21
|
+
Requires-Dist: fastapi>=0.116.1
|
|
22
|
+
Requires-Dist: authlib>=1.3.0
|
|
23
|
+
Requires-Dist: async-lru>=2.0.4
|
|
24
|
+
Requires-Python: >=3.11, <3.14
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# Agent Stack Server SDK
|
|
28
|
+
|
|
29
|
+
Python SDK for packaging agents for deployment to Agent Stack infrastructure.
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/agentstack-sdk/)
|
|
32
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
33
|
+
[](https://lfaidata.foundation/projects/)
|
|
34
|
+
|
|
35
|
+
## Overview
|
|
36
|
+
|
|
37
|
+
The `agentstack-sdk` provides Python utilities for wrapping agents built with any framework (LangChain, CrewAI, BeeAI Framework, etc.) for deployment on Agent Stack. It handles the A2A (Agent-to-Agent) protocol implementation, platform service integration, and runtime requirements so you can focus on agent logic.
|
|
38
|
+
|
|
39
|
+
## Key Features
|
|
40
|
+
|
|
41
|
+
- **Framework-Agnostic Deployment** - Wrap agents from any framework for Agent Stack deployment
|
|
42
|
+
- **A2A Protocol Support** - Automatic handling of Agent-to-Agent communication
|
|
43
|
+
- **Platform Service Integration** - Connect to Agent Stack's managed LLM, embedding, file storage, and vector store services
|
|
44
|
+
- **Context Storage** - Manage data associated with conversation contexts
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv add agentstack-sdk
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quickstart
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import os
|
|
56
|
+
|
|
57
|
+
from a2a.types import (
|
|
58
|
+
Message,
|
|
59
|
+
)
|
|
60
|
+
from a2a.utils.message import get_message_text
|
|
61
|
+
from agentstack_sdk.server import Server
|
|
62
|
+
from agentstack_sdk.server.context import RunContext
|
|
63
|
+
from agentstack_sdk.a2a.types import AgentMessage
|
|
64
|
+
|
|
65
|
+
server = Server()
|
|
66
|
+
|
|
67
|
+
@server.agent()
|
|
68
|
+
async def example_agent(input: Message, context: RunContext):
|
|
69
|
+
"""Polite agent that greets the user"""
|
|
70
|
+
hello_template: str = os.getenv("HELLO_TEMPLATE", "Ciao %s!")
|
|
71
|
+
yield AgentMessage(text=hello_template % get_message_text(input))
|
|
72
|
+
|
|
73
|
+
def run():
|
|
74
|
+
try:
|
|
75
|
+
server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000)))
|
|
76
|
+
except KeyboardInterrupt:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
run()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Run the agent:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
uv run my_agent.py
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Available Extensions
|
|
91
|
+
|
|
92
|
+
The SDK includes extension support for:
|
|
93
|
+
|
|
94
|
+
- **Citations** - Source attribution (`CitationExtensionServer`, `CitationExtensionSpec`)
|
|
95
|
+
- **Trajectory** - Agent decision logging (`TrajectoryExtensionServer`, `TrajectoryExtensionSpec`)
|
|
96
|
+
- **Settings** - User-configurable agent parameters (`SettingsExtensionServer`, `SettingsExtensionSpec`)
|
|
97
|
+
- **LLM Services** - Platform-managed language models (`LLMServiceExtensionServer`, `LLMServiceExtensionSpec`)
|
|
98
|
+
- **Agent Details** - Metadata and UI enhancements (`AgentDetail`)
|
|
99
|
+
- **And more** - See [Documentation](https://agentstack.beeai.dev/stable/agent-development/overview)
|
|
100
|
+
|
|
101
|
+
Each extension provides both server-side handlers and A2A protocol specifications for seamless integration with Agent Stack's UI and infrastructure.
|
|
102
|
+
|
|
103
|
+
## Resources
|
|
104
|
+
|
|
105
|
+
- [Agent Stack Documentation](https://agentstack.beeai.dev)
|
|
106
|
+
- [GitHub Repository](https://github.com/i-am-bee/agentstack)
|
|
107
|
+
- [PyPI Package](https://pypi.org/project/agentstack-sdk/)
|
|
108
|
+
|
|
109
|
+
## Contributing
|
|
110
|
+
|
|
111
|
+
Contributions are welcome! Please see the [Contributing Guide](https://github.com/i-am-bee/agentstack/blob/main/CONTRIBUTING.md) for details.
|
|
112
|
+
|
|
113
|
+
## Support
|
|
114
|
+
|
|
115
|
+
- [GitHub Issues](https://github.com/i-am-bee/agentstack/issues)
|
|
116
|
+
- [GitHub Discussions](https://github.com/i-am-bee/agentstack/discussions)
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
Developed by contributors to the BeeAI project, this initiative is part of the [Linux Foundation AI & Data program](https://lfaidata.foundation/projects/). Its development follows open, collaborative, and community-driven practices.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
agentstack_sdk/__init__.py,sha256=PjJzra4hWMfNi_dNOe6n5gasb6NrDQo7apEKxioGxko,174
|
|
2
|
+
agentstack_sdk/a2a/__init__.py,sha256=9xW5VdTorDw2AgBfWH5BrVzjmDbTjKPVsZV86PgsobI,93
|
|
3
|
+
agentstack_sdk/a2a/extensions/__init__.py,sha256=WT_4375xwPvdqcxjZOHG6a9QG1zh2CCyBEevNyJvCD8,205
|
|
4
|
+
agentstack_sdk/a2a/extensions/auth/__init__.py,sha256=Rz8hEJCrWqE2NUUBnjKL1cyNEpI_E58NZuSwgeSgNVE,138
|
|
5
|
+
agentstack_sdk/a2a/extensions/auth/oauth/__init__.py,sha256=iha9SLBVA3V4tRID-bBaxoZq79NIEOMXrNlyGDs2VdQ,115
|
|
6
|
+
agentstack_sdk/a2a/extensions/auth/oauth/oauth.py,sha256=89Y6SexsUY1AxhwN23g9YTUD-AQC_7-LfSe1kA2LP9g,6045
|
|
7
|
+
agentstack_sdk/a2a/extensions/auth/oauth/storage/__init__.py,sha256=TZk5xNltzmEVFedsFaChrfrckGiFHD_T7yyX7ptr_TM,136
|
|
8
|
+
agentstack_sdk/a2a/extensions/auth/oauth/storage/base.py,sha256=QNvijmaYW3LmTrhTDpCimdYtPOCNk0YS21XQRNwSsvc,265
|
|
9
|
+
agentstack_sdk/a2a/extensions/auth/oauth/storage/memory.py,sha256=HWEKqgfMtq3ZJPQrYhYF-Qb3DzUlOfHWHYCladz15QQ,1236
|
|
10
|
+
agentstack_sdk/a2a/extensions/auth/secrets/__init__.py,sha256=K0Dknv6Hpt3CAnqCyRGIs4a6s_6Z0pHLsyERo9jUMGc,117
|
|
11
|
+
agentstack_sdk/a2a/extensions/auth/secrets/secrets.py,sha256=06UygsBiNrWhaRdw8t-nqPnkWKDj0sB2Y-QWtEJuPqc,2802
|
|
12
|
+
agentstack_sdk/a2a/extensions/base.py,sha256=TkwwkDpPITThkTt_vRZwA03S02thLtG9_nFO6euqBIw,7073
|
|
13
|
+
agentstack_sdk/a2a/extensions/common/__init__.py,sha256=3lg8P9ASBFHDipP1oZ3IcPqCAwVFtWr3LMBJzpT0Yqo,114
|
|
14
|
+
agentstack_sdk/a2a/extensions/common/form.py,sha256=tbDD3V-ZW90TjXXkyf3SCQd3zJ3tvbJfJA5cbJuPdH0,3792
|
|
15
|
+
agentstack_sdk/a2a/extensions/exceptions.py,sha256=k4_OFuglnm3XaAr9tl47dNXLLkIhjOQVLohIQ7ieC0M,369
|
|
16
|
+
agentstack_sdk/a2a/extensions/interactions/__init__.py,sha256=GruhHZaI-iMrmzZhkgtb4KOzlVKgnKMYEylfNjIn6_4,118
|
|
17
|
+
agentstack_sdk/a2a/extensions/interactions/approval.py,sha256=DMdiLFEkdgaYyZP-E5pq_tARAn2VNQIIyn_jiZL2RcI,4795
|
|
18
|
+
agentstack_sdk/a2a/extensions/services/__init__.py,sha256=9njkm58sMs_sMQwKGTMkPU-I7c7T99BWy30fcw4nCoI,201
|
|
19
|
+
agentstack_sdk/a2a/extensions/services/embedding.py,sha256=GuvbKaOlZSBn04a7SsIlJrQkehEKnZDxQmJCxpVbf_8,3892
|
|
20
|
+
agentstack_sdk/a2a/extensions/services/form.py,sha256=BmZ-Cqwp4o-j1zgCneuZ_9-1vCOm1OkQMJZLc_Rp1qc,1707
|
|
21
|
+
agentstack_sdk/a2a/extensions/services/llm.py,sha256=pReamcrKU_IkjYn1Ia-dID__PDKUBQiSpRcOSUtIL7s,3644
|
|
22
|
+
agentstack_sdk/a2a/extensions/services/mcp.py,sha256=04wKPvISPsWTVR-YXwd06ajoU6O3VlStGwDDQvEbnKY,7114
|
|
23
|
+
agentstack_sdk/a2a/extensions/services/platform.py,sha256=gImzMBmsQ8ot6sbUWNgW0X_B1fPe6W4UMJhe-LU_8YA,5706
|
|
24
|
+
agentstack_sdk/a2a/extensions/tools/__init__.py,sha256=PWVZLGvw9ZESBfBbSFJuFNcW1SHkCbZi0pj5yt5Uw2g,140
|
|
25
|
+
agentstack_sdk/a2a/extensions/tools/call.py,sha256=JwnlA6K9QJFC0Ca4zSMa2jHDlT0QgcMSu-gJNKejIiU,4288
|
|
26
|
+
agentstack_sdk/a2a/extensions/tools/exceptions.py,sha256=6SmMOjm9ayicfIkdFd0X_vwY7Tqn6M7dNiSF9jpeVeg,148
|
|
27
|
+
agentstack_sdk/a2a/extensions/ui/__init__.py,sha256=pn0blPq1QmquxBOEWaAIzP98ECXnVuLQy4rkhpxzhEg,267
|
|
28
|
+
agentstack_sdk/a2a/extensions/ui/agent_detail.py,sha256=OGKToMFq2FnXI0dRmjXpdaOM3BUPU2YJbA4IZZXr93E,1607
|
|
29
|
+
agentstack_sdk/a2a/extensions/ui/canvas.py,sha256=NAY4R-BjriZJON-79GTD7CfVItl8bNsFfwQCnBQfFAA,2403
|
|
30
|
+
agentstack_sdk/a2a/extensions/ui/citation.py,sha256=TdkIXummv_g9eEuDQfnC4ZjI-BE4umTvctp1471OpdE,2761
|
|
31
|
+
agentstack_sdk/a2a/extensions/ui/error.py,sha256=upZXaq1ZCvv4Ow_ShZGH0-sFYPaiRixanYFZCjzodb4,7393
|
|
32
|
+
agentstack_sdk/a2a/extensions/ui/form_request.py,sha256=YeRiovV3EgqeBD8fHtu_b5qr3negXezxyi6lfFLrqb8,2026
|
|
33
|
+
agentstack_sdk/a2a/extensions/ui/settings.py,sha256=uZu_8u29gAu1H85twFb_LCRBO0jeaEiLgfamaFNUPGM,1814
|
|
34
|
+
agentstack_sdk/a2a/extensions/ui/trajectory.py,sha256=G66rIpr2VTe1UvbK82EgLXwFtUC3QfbyN_uLQGUU8jA,2334
|
|
35
|
+
agentstack_sdk/a2a/types.py,sha256=z1-OxRLIJgYDHwaBJ66MZNIqSTpE1zQ04BPGBGw9IC0,3582
|
|
36
|
+
agentstack_sdk/platform/__init__.py,sha256=jFlxr6P-6DJZVt1W8Xt1suISJpodD_EWX92GmcFLXwM,326
|
|
37
|
+
agentstack_sdk/platform/client.py,sha256=KDN3R1wyoo-ZUYl6ArjRJxre0yAqB0Y6NyKrQpdI268,4234
|
|
38
|
+
agentstack_sdk/platform/common.py,sha256=p6w4l49NaFp7LsjEBDAwghX6FliNChOeO7IHP_-TnXA,750
|
|
39
|
+
agentstack_sdk/platform/configuration.py,sha256=YfOasb6LxE9LN6sMZPEU07Q61F7uIhiU3DIQFRaqinU,1690
|
|
40
|
+
agentstack_sdk/platform/context.py,sha256=vYAQOgZUEelvhP4Sldzt4Y1CP4WVtEE42DSuAMGnn-0,12099
|
|
41
|
+
agentstack_sdk/platform/file.py,sha256=lOqMwMKHKGWFy5P1huQ6DLRTiHU0KexX-s2qF3FIRxw,13062
|
|
42
|
+
agentstack_sdk/platform/model_provider.py,sha256=hv5UbFUFQ23IlalH5W-nCTvnUCrxBtZF4UrkeNWnwFE,4588
|
|
43
|
+
agentstack_sdk/platform/provider.py,sha256=1juAfYc39sVwZ8ZbYz1ohVnzzRacJx6Junvo-7GgVmY,9071
|
|
44
|
+
agentstack_sdk/platform/provider_build.py,sha256=Vn2XDdMGgQWvChWJmg9nxYFVecp42A1jXzURkFgSEQQ,7317
|
|
45
|
+
agentstack_sdk/platform/types.py,sha256=3OHERUoli6t9rlCiaZ2YfN26GXqMQzDFQx_UluCZVYA,1502
|
|
46
|
+
agentstack_sdk/platform/user.py,sha256=ZxTPJYPGxcRtRQJpxPPf2ylz3Erngu2vSMuia1oJ04Q,2148
|
|
47
|
+
agentstack_sdk/platform/user_feedback.py,sha256=IfMzZsaFKpWCPz5MjppDsFLLMM-iVQUBGugz0_FmrqY,1305
|
|
48
|
+
agentstack_sdk/platform/variables.py,sha256=VW7eHO_V6B5I-vvqNgUangkcuUXDcjFIrxUSSfX5cHw,1637
|
|
49
|
+
agentstack_sdk/platform/vector_store.py,sha256=h98RKGuN1SvmfL_bxf3WCLvY1IeQ00Wf7NTP6yr05qg,8695
|
|
50
|
+
agentstack_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
|
+
agentstack_sdk/server/__init__.py,sha256=vc06L5ILofytmLJC9hJUzA9LakUYUNYGiBoSXNs9hQc,152
|
|
52
|
+
agentstack_sdk/server/agent.py,sha256=tprDvYwsSoImJkMCMVvHKzacH2pWtQUWu19qgWAyNMk,29170
|
|
53
|
+
agentstack_sdk/server/app.py,sha256=1-g_6FBU4lnrBI2KZVnTvsfdKrHyChkeYmJ01iIrj4Y,3396
|
|
54
|
+
agentstack_sdk/server/constants.py,sha256=huQAP351AeNt536tB-axJSseHMHfyDKwJdlackjLGnw,188
|
|
55
|
+
agentstack_sdk/server/context.py,sha256=yBAk3RgBsat1nULzFs2OvF2UKKeNAT8yaSN_s-bcPTU,2712
|
|
56
|
+
agentstack_sdk/server/dependencies.py,sha256=kyUwrIJe99n7UQmqGn_bo--YkPiAR6S9SoeOBwaeW68,5022
|
|
57
|
+
agentstack_sdk/server/exceptions.py,sha256=jr8otByt8HFY9OWptuWdpq4WHE7A1srCSRXZV066FrI,94
|
|
58
|
+
agentstack_sdk/server/middleware/__init__.py,sha256=pEJG3OupRq7IF3O70g2pvDU_EmDF0D3iAuGWXB-_Ipk,94
|
|
59
|
+
agentstack_sdk/server/middleware/platform_auth_backend.py,sha256=h2P4jKJpXOmie1tFU2E5YW-c6-BksINmWEonNUQ5l3g,4986
|
|
60
|
+
agentstack_sdk/server/server.py,sha256=twD5j4Re8rfg63CjcMLJg_tpPBKAkffnP22hTkVS5iY,15909
|
|
61
|
+
agentstack_sdk/server/store/__init__.py,sha256=jr8otByt8HFY9OWptuWdpq4WHE7A1srCSRXZV066FrI,94
|
|
62
|
+
agentstack_sdk/server/store/context_store.py,sha256=ZzXSlxaV_xaTEVDAO0oC0eCiNQve2ydjYUJK3oUAOXA,1060
|
|
63
|
+
agentstack_sdk/server/store/memory_context_store.py,sha256=S604ATyk9wA2D_pZL-Q-I4x01mItKihLJmGwp7bumC4,2346
|
|
64
|
+
agentstack_sdk/server/store/platform_context_store.py,sha256=bv5-sVJUVVblYIAjLsl0_VOqstjfAXWuQgWhewE_9OI,2614
|
|
65
|
+
agentstack_sdk/server/telemetry.py,sha256=MJ5JGF3L_ef8eqx-yguJHgIhbpDtHRSfKm2QKFtlHmI,2014
|
|
66
|
+
agentstack_sdk/server/utils.py,sha256=0s7N-kWEWnPuI2Ki4PZuX5uY48KFLCOHg-lnxRDVEg8,917
|
|
67
|
+
agentstack_sdk/types.py,sha256=1GOt-l2xnJTS_6u37YNoMXkuckX2XwjQ1aH1iv6QRIQ,593
|
|
68
|
+
agentstack_sdk/util/__init__.py,sha256=dCOBqJYOJYvvQSjDZysZGkLxvZV-o9j367ze3jnX_Rc,126
|
|
69
|
+
agentstack_sdk/util/file.py,sha256=rm5b47tHTmJBsHlqGcUqFCP0Bz5HKXPHWW_h-IpG9pw,9035
|
|
70
|
+
agentstack_sdk/util/httpx.py,sha256=g6WKgkGCLc40wZA2CPYO_R2P-nav6QP1XmVlgkh9wYY,491
|
|
71
|
+
agentstack_sdk/util/logging.py,sha256=hGLkjw-P3GefiUQmDmcz7BZQxfT2Y44XlLyAMNxZXMo,2136
|
|
72
|
+
agentstack_sdk/util/resource_context.py,sha256=OmjEXvrLQA6nBkVSBt0n24hNNxJkucd-N32haxJ4Mno,1093
|
|
73
|
+
agentstack_sdk/util/utils.py,sha256=18qFqMRkX4g4eIpvIvLb4FZAs8Q8ojJrpm19oJleb-k,1593
|
|
74
|
+
agentstack_sdk-0.5.2rc2.dist-info/WHEEL,sha256=M6du7VZflc4UPsGphmOXHANdgk8zessdJG0DBUuoA-U,78
|
|
75
|
+
agentstack_sdk-0.5.2rc2.dist-info/METADATA,sha256=FRj4acL7b2M-drk8OH6YgA_a8m8dNOds6jE-WOKiP-A,4433
|
|
76
|
+
agentstack_sdk-0.5.2rc2.dist-info/RECORD,,
|