agentstack-sdk 0.6.2rc6__tar.gz → 0.7.0rc11__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.
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/PKG-INFO +5 -4
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/pyproject.toml +6 -5
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/__init__.py +6 -0
- agentstack_sdk-0.7.0rc11/src/agentstack_sdk/a2a/__init__.py +4 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/__init__.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/__init__.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/oauth/__init__.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/oauth/oauth.py +23 -5
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/oauth/storage/__init__.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/oauth/storage/base.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/oauth/storage/memory.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/secrets/__init__.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/secrets/secrets.py +33 -4
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/base.py +32 -3
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/common/__init__.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/common/form.py +58 -8
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/exceptions.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/interactions/__init__.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/interactions/approval.py +46 -9
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/services/__init__.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/services/embedding.py +19 -3
- agentstack_sdk-0.7.0rc11/src/agentstack_sdk/a2a/extensions/services/form.py +107 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/services/llm.py +23 -3
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/services/mcp.py +42 -8
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/services/platform.py +20 -11
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/tools/__init__.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/tools/call.py +18 -4
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/tools/exceptions.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/__init__.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/canvas.py +8 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/citation.py +1 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/error.py +1 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/form_request.py +7 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/settings.py +17 -3
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/trajectory.py +1 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/types.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/__init__.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/client.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/common.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/configuration.py +3 -2
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/connector.py +17 -16
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/context.py +15 -14
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/file.py +14 -13
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/model_provider.py +8 -6
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/provider.py +12 -9
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/provider_build.py +7 -6
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/provider_discovery.py +3 -2
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/types.py +1 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/user.py +3 -2
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/user_feedback.py +4 -1
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/variables.py +3 -2
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/vector_store.py +9 -8
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/__init__.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/agent.py +19 -3
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/app.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/constants.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/context.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/dependencies.py +17 -11
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/exceptions.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/middleware/__init__.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/middleware/platform_auth_backend.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/server.py +57 -54
- agentstack_sdk-0.7.0rc11/src/agentstack_sdk/server/store/__init__.py +5 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/store/context_store.py +6 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/store/memory_context_store.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/store/platform_context_store.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/telemetry.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/utils.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/types.py +8 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/util/__init__.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/util/file.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/util/httpx.py +2 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/util/logging.py +2 -0
- agentstack_sdk-0.7.0rc11/src/agentstack_sdk/util/pydantic.py +134 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/util/resource_context.py +2 -0
- agentstack_sdk-0.7.0rc11/src/agentstack_sdk/util/telemetry.py +280 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/util/utils.py +2 -0
- agentstack_sdk-0.6.2rc6/src/agentstack_sdk/a2a/__init__.py +0 -2
- agentstack_sdk-0.6.2rc6/src/agentstack_sdk/a2a/extensions/services/form.py +0 -54
- agentstack_sdk-0.6.2rc6/src/agentstack_sdk/server/store/__init__.py +0 -3
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/README.md +0 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/agent_detail.py +0 -0
- {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/py.typed +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: agentstack-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0rc11
|
|
4
4
|
Summary: Agent Stack SDK
|
|
5
5
|
Author: IBM Corp.
|
|
6
|
-
Requires-Dist: a2a-sdk==0.3.
|
|
6
|
+
Requires-Dist: a2a-sdk==0.3.24
|
|
7
7
|
Requires-Dist: objprint>=0.3.0
|
|
8
|
-
Requires-Dist: uvicorn>=0.
|
|
8
|
+
Requires-Dist: uvicorn>=0.37.0,<0.38.0
|
|
9
9
|
Requires-Dist: asyncclick>=8.1.8
|
|
10
10
|
Requires-Dist: sse-starlette>=2.2.1
|
|
11
11
|
Requires-Dist: starlette>=0.47.2
|
|
@@ -22,9 +22,10 @@ Requires-Dist: fastapi>=0.116.1
|
|
|
22
22
|
Requires-Dist: authlib>=1.3.0
|
|
23
23
|
Requires-Dist: async-lru>=2.0.4
|
|
24
24
|
Requires-Dist: cachetools>=6.2.3
|
|
25
|
+
Requires-Dist: typing-extensions>=4.15.0
|
|
25
26
|
Requires-Dist: opentelemetry-instrumentation-httpx>=0.60b1
|
|
26
27
|
Requires-Dist: opentelemetry-instrumentation-openai>=0.52.3
|
|
27
|
-
Requires-Python: >=3.11, <3.
|
|
28
|
+
Requires-Python: >=3.11, <3.15
|
|
28
29
|
Description-Content-Type: text/markdown
|
|
29
30
|
|
|
30
31
|
# Agent Stack Server SDK
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agentstack-sdk"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.0-rc11"
|
|
4
4
|
description = "Agent Stack SDK"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "IBM Corp." }]
|
|
7
|
-
requires-python = ">=3.11,<3.
|
|
7
|
+
requires-python = ">=3.11,<3.15"
|
|
8
8
|
dependencies = [
|
|
9
|
-
"a2a-sdk==0.3.
|
|
9
|
+
"a2a-sdk==0.3.24",
|
|
10
10
|
"objprint>=0.3.0",
|
|
11
|
-
"uvicorn>=0.
|
|
11
|
+
"uvicorn>=0.37.0,<0.38.0",
|
|
12
12
|
"asyncclick>=8.1.8",
|
|
13
13
|
"sse-starlette>=2.2.1",
|
|
14
14
|
"starlette>=0.47.2",
|
|
@@ -25,6 +25,7 @@ dependencies = [
|
|
|
25
25
|
"authlib>=1.3.0",
|
|
26
26
|
"async-lru>=2.0.4",
|
|
27
27
|
"cachetools>=6.2.3",
|
|
28
|
+
"typing-extensions>=4.15.0",
|
|
28
29
|
"opentelemetry-instrumentation-httpx>=0.60b1",
|
|
29
30
|
"opentelemetry-instrumentation-openai>=0.52.3",
|
|
30
31
|
]
|
|
@@ -40,7 +41,7 @@ dev = [
|
|
|
40
41
|
]
|
|
41
42
|
|
|
42
43
|
[build-system]
|
|
43
|
-
requires = ["uv_build>=0.
|
|
44
|
+
requires = ["uv_build>=0.10.0,<0.11.0"]
|
|
44
45
|
build-backend = "uv_build"
|
|
45
46
|
|
|
46
47
|
[tool.ruff]
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
4
6
|
from importlib.metadata import version
|
|
5
7
|
|
|
8
|
+
from agentstack_sdk.util.pydantic import apply_compatibility_monkey_patching
|
|
9
|
+
|
|
6
10
|
__version__ = version("agentstack-sdk")
|
|
11
|
+
|
|
12
|
+
apply_compatibility_monkey_patching()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
+
|
|
4
5
|
from __future__ import annotations
|
|
5
6
|
|
|
6
7
|
import uuid
|
|
@@ -19,6 +20,19 @@ from typing_extensions import override
|
|
|
19
20
|
from agentstack_sdk.a2a.extensions.auth.oauth.storage import MemoryTokenStorageFactory, TokenStorageFactory
|
|
20
21
|
from agentstack_sdk.a2a.extensions.base import BaseExtensionClient, BaseExtensionServer, BaseExtensionSpec
|
|
21
22
|
from agentstack_sdk.a2a.types import AgentMessage, AuthRequired, RunYieldResume
|
|
23
|
+
from agentstack_sdk.util.pydantic import REVEAL_SECRETS, SecureBaseModel
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"AuthRequest",
|
|
27
|
+
"AuthResponse",
|
|
28
|
+
"OAuthDemand",
|
|
29
|
+
"OAuthExtensionClient",
|
|
30
|
+
"OAuthExtensionMetadata",
|
|
31
|
+
"OAuthExtensionParams",
|
|
32
|
+
"OAuthExtensionServer",
|
|
33
|
+
"OAuthExtensionSpec",
|
|
34
|
+
"OAuthFulfillment",
|
|
35
|
+
]
|
|
22
36
|
|
|
23
37
|
if TYPE_CHECKING:
|
|
24
38
|
from agentstack_sdk.server.context import RunContext
|
|
@@ -26,15 +40,15 @@ if TYPE_CHECKING:
|
|
|
26
40
|
_DEFAULT_DEMAND_NAME = "default"
|
|
27
41
|
|
|
28
42
|
|
|
29
|
-
class AuthRequest(
|
|
43
|
+
class AuthRequest(SecureBaseModel):
|
|
30
44
|
authorization_endpoint_url: pydantic.AnyUrl
|
|
31
45
|
|
|
32
46
|
|
|
33
|
-
class AuthResponse(
|
|
47
|
+
class AuthResponse(SecureBaseModel):
|
|
34
48
|
redirect_uri: pydantic.AnyUrl
|
|
35
49
|
|
|
36
50
|
|
|
37
|
-
class OAuthFulfillment(
|
|
51
|
+
class OAuthFulfillment(SecureBaseModel):
|
|
38
52
|
redirect_uri: pydantic.AnyUrl
|
|
39
53
|
|
|
40
54
|
|
|
@@ -112,6 +126,7 @@ class OAuthExtensionServer(BaseExtensionServer[OAuthExtensionSpec, OAuthExtensio
|
|
|
112
126
|
oauth_auth = OAuthClientProvider(
|
|
113
127
|
server_url=str(resource_url),
|
|
114
128
|
client_metadata=OAuthClientMetadata(
|
|
129
|
+
client_name="AgentStack Agent",
|
|
115
130
|
redirect_uris=[fulfillment.redirect_uri],
|
|
116
131
|
),
|
|
117
132
|
storage=await self.token_storage_factory.create_storage(),
|
|
@@ -122,7 +137,10 @@ class OAuthExtensionServer(BaseExtensionServer[OAuthExtensionSpec, OAuthExtensio
|
|
|
122
137
|
|
|
123
138
|
def create_auth_request(self, *, authorization_endpoint_url: pydantic.AnyUrl):
|
|
124
139
|
data = AuthRequest(authorization_endpoint_url=authorization_endpoint_url)
|
|
125
|
-
return AgentMessage(
|
|
140
|
+
return AgentMessage(
|
|
141
|
+
text="Authorization required",
|
|
142
|
+
metadata={self.spec.URI: data.model_dump(mode="json", context={REVEAL_SECRETS: True})},
|
|
143
|
+
)
|
|
126
144
|
|
|
127
145
|
def parse_auth_response(self, *, message: A2AMessage):
|
|
128
146
|
if not message or not message.metadata or not (data := message.metadata.get(self.spec.URI)):
|
|
@@ -147,5 +165,5 @@ class OAuthExtensionClient(BaseExtensionClient[OAuthExtensionSpec, NoneType]):
|
|
|
147
165
|
role=Role.user,
|
|
148
166
|
parts=[TextPart(text="Authorization completed")],
|
|
149
167
|
task_id=task_id,
|
|
150
|
-
metadata={self.spec.URI: data.model_dump(mode="json")},
|
|
168
|
+
metadata={self.spec.URI: data.model_dump(mode="json", context={REVEAL_SECRETS: True})},
|
|
151
169
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
+
|
|
4
5
|
from __future__ import annotations
|
|
5
6
|
|
|
6
7
|
from typing import TYPE_CHECKING, Self
|
|
@@ -8,22 +9,40 @@ from typing import TYPE_CHECKING, Self
|
|
|
8
9
|
import pydantic
|
|
9
10
|
from a2a.server.agent_execution.context import RequestContext
|
|
10
11
|
from a2a.types import Message as A2AMessage
|
|
12
|
+
from opentelemetry import trace
|
|
11
13
|
from typing_extensions import override
|
|
12
14
|
|
|
13
15
|
from agentstack_sdk.a2a.extensions.base import BaseExtensionClient, BaseExtensionServer, BaseExtensionSpec
|
|
14
16
|
from agentstack_sdk.a2a.types import AgentMessage, AuthRequired
|
|
17
|
+
from agentstack_sdk.util.pydantic import REDACT_SECRETS, REVEAL_SECRETS, SecureBaseModel
|
|
18
|
+
from agentstack_sdk.util.telemetry import flatten_dict
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"A2A_EXTENSION_SECRETS_REQUESTED",
|
|
22
|
+
"A2A_EXTENSION_SECRETS_RESOLVED",
|
|
23
|
+
"SecretDemand",
|
|
24
|
+
"SecretFulfillment",
|
|
25
|
+
"SecretsExtensionClient",
|
|
26
|
+
"SecretsExtensionServer",
|
|
27
|
+
"SecretsExtensionSpec",
|
|
28
|
+
"SecretsServiceExtensionMetadata",
|
|
29
|
+
"SecretsServiceExtensionParams",
|
|
30
|
+
]
|
|
15
31
|
|
|
16
32
|
if TYPE_CHECKING:
|
|
17
33
|
from agentstack_sdk.server.context import RunContext
|
|
18
34
|
|
|
35
|
+
A2A_EXTENSION_SECRETS_REQUESTED = "a2a_extension.secrets.requested"
|
|
36
|
+
A2A_EXTENSION_SECRETS_RESOLVED = "a2a_extension.secrets.resolved"
|
|
37
|
+
|
|
19
38
|
|
|
20
39
|
class SecretDemand(pydantic.BaseModel):
|
|
21
40
|
name: str
|
|
22
41
|
description: str | None = None
|
|
23
42
|
|
|
24
43
|
|
|
25
|
-
class SecretFulfillment(
|
|
26
|
-
secret:
|
|
44
|
+
class SecretFulfillment(SecureBaseModel):
|
|
45
|
+
secret: pydantic.SecretStr
|
|
27
46
|
|
|
28
47
|
|
|
29
48
|
class SecretsServiceExtensionParams(pydantic.BaseModel):
|
|
@@ -61,15 +80,25 @@ class SecretsExtensionServer(BaseExtensionServer[SecretsExtensionSpec, SecretsSe
|
|
|
61
80
|
return SecretsServiceExtensionMetadata.model_validate(data)
|
|
62
81
|
|
|
63
82
|
async def request_secrets(self, params: SecretsServiceExtensionParams) -> SecretsServiceExtensionMetadata:
|
|
83
|
+
span = trace.get_current_span()
|
|
84
|
+
span.add_event(
|
|
85
|
+
A2A_EXTENSION_SECRETS_REQUESTED,
|
|
86
|
+
attributes=flatten_dict(params.model_dump(context={REDACT_SECRETS: True})),
|
|
87
|
+
)
|
|
64
88
|
resume = await self.context.yield_async(
|
|
65
89
|
AuthRequired(
|
|
66
90
|
message=AgentMessage(
|
|
67
|
-
metadata={self.spec.URI: params.model_dump(mode="json")},
|
|
91
|
+
metadata={self.spec.URI: params.model_dump(mode="json", context={REVEAL_SECRETS: True})},
|
|
68
92
|
)
|
|
69
93
|
)
|
|
70
94
|
)
|
|
71
95
|
if isinstance(resume, A2AMessage):
|
|
72
|
-
|
|
96
|
+
response = self.parse_secret_response(message=resume)
|
|
97
|
+
span.add_event(
|
|
98
|
+
A2A_EXTENSION_SECRETS_RESOLVED,
|
|
99
|
+
attributes=flatten_dict(response.model_dump(context={REDACT_SECRETS: True})),
|
|
100
|
+
)
|
|
101
|
+
return response
|
|
73
102
|
else:
|
|
74
103
|
raise ValueError("Secrets has not been provided in response.")
|
|
75
104
|
|
{agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/base.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
+
|
|
4
5
|
from __future__ import annotations
|
|
5
6
|
|
|
6
7
|
import abc
|
|
@@ -13,8 +14,17 @@ import pydantic
|
|
|
13
14
|
from a2a.server.agent_execution.context import RequestContext
|
|
14
15
|
from a2a.types import AgentCard, AgentExtension
|
|
15
16
|
from a2a.types import Message as A2AMessage
|
|
17
|
+
from opentelemetry import trace
|
|
18
|
+
from opentelemetry.trace import SpanKind
|
|
19
|
+
from pydantic import BaseModel
|
|
16
20
|
from typing_extensions import override
|
|
17
21
|
|
|
22
|
+
from agentstack_sdk.util.pydantic import REDACT_SECRETS
|
|
23
|
+
from agentstack_sdk.util.telemetry import (
|
|
24
|
+
flatten_dict,
|
|
25
|
+
trace_class,
|
|
26
|
+
)
|
|
27
|
+
|
|
18
28
|
ParamsT = typing.TypeVar("ParamsT")
|
|
19
29
|
MetadataFromClientT = typing.TypeVar("MetadataFromClientT")
|
|
20
30
|
MetadataFromServerT = typing.TypeVar("MetadataFromServerT")
|
|
@@ -25,6 +35,10 @@ if typing.TYPE_CHECKING:
|
|
|
25
35
|
from agentstack_sdk.server.dependencies import Dependency
|
|
26
36
|
|
|
27
37
|
|
|
38
|
+
A2A_EXTENSION_URI = "a2a_extension.uri"
|
|
39
|
+
A2A_EXTENSION_METADATA_RECEIVED_EVENT = "a2a_extension.metadata.received"
|
|
40
|
+
|
|
41
|
+
|
|
28
42
|
def _get_generic_args(cls: type, base_class: type) -> tuple[typing.Any, ...]:
|
|
29
43
|
for base in getattr(cls, "__orig_bases__", ()):
|
|
30
44
|
if typing.get_origin(base) is base_class and (args := typing.get_args(base)):
|
|
@@ -67,7 +81,7 @@ class BaseExtensionSpec(abc.ABC, typing.Generic[ParamsT]):
|
|
|
67
81
|
self.params = params
|
|
68
82
|
|
|
69
83
|
@classmethod
|
|
70
|
-
def from_agent_card(cls: type[BaseExtensionSpec], agent: AgentCard) -> typing.Self | None:
|
|
84
|
+
def from_agent_card(cls: type["BaseExtensionSpec"], agent: AgentCard) -> typing.Self | None:
|
|
71
85
|
"""
|
|
72
86
|
Client should construct an extension instance using this classmethod.
|
|
73
87
|
"""
|
|
@@ -121,7 +135,14 @@ class BaseExtensionServer(abc.ABC, typing.Generic[ExtensionSpecT, MetadataFromCl
|
|
|
121
135
|
|
|
122
136
|
def __init_subclass__(cls, **kwargs):
|
|
123
137
|
super().__init_subclass__(**kwargs)
|
|
124
|
-
|
|
138
|
+
|
|
139
|
+
generic_args = _get_generic_args(cls, BaseExtensionServer)
|
|
140
|
+
trace_class(
|
|
141
|
+
kind=SpanKind.SERVER,
|
|
142
|
+
exclude_list=["lifespan", "_fork"],
|
|
143
|
+
attributes={A2A_EXTENSION_URI: generic_args[0].URI},
|
|
144
|
+
)(cls)
|
|
145
|
+
cls.MetadataFromClient = generic_args[1]
|
|
125
146
|
|
|
126
147
|
_metadata_from_client: MetadataFromClientT | None = None
|
|
127
148
|
_dependencies: dict[str, Dependency] = {} # noqa: RUF012
|
|
@@ -151,6 +172,11 @@ class BaseExtensionServer(abc.ABC, typing.Generic[ExtensionSpecT, MetadataFromCl
|
|
|
151
172
|
def handle_incoming_message(self, message: A2AMessage, run_context: RunContext, request_context: RequestContext):
|
|
152
173
|
if self._metadata_from_client is None:
|
|
153
174
|
self._metadata_from_client = self.parse_client_metadata(message)
|
|
175
|
+
if isinstance(self._metadata_from_client, BaseModel):
|
|
176
|
+
trace.get_current_span().add_event(
|
|
177
|
+
A2A_EXTENSION_METADATA_RECEIVED_EVENT,
|
|
178
|
+
attributes=flatten_dict(self._metadata_from_client.model_dump(context={REDACT_SECRETS: True})),
|
|
179
|
+
)
|
|
154
180
|
|
|
155
181
|
def _fork(self) -> typing.Self:
|
|
156
182
|
"""Creates a clone of this instance with the same arguments as the original"""
|
|
@@ -182,7 +208,10 @@ class BaseExtensionClient(abc.ABC, typing.Generic[ExtensionSpecT, MetadataFromSe
|
|
|
182
208
|
|
|
183
209
|
def __init_subclass__(cls, **kwargs):
|
|
184
210
|
super().__init_subclass__(**kwargs)
|
|
185
|
-
|
|
211
|
+
|
|
212
|
+
generic_args = _get_generic_args(cls, BaseExtensionClient)
|
|
213
|
+
trace_class(kind=SpanKind.CLIENT, attributes={A2A_EXTENSION_URI: generic_args[0].URI})(cls)
|
|
214
|
+
cls.MetadataFromServer = generic_args[1]
|
|
186
215
|
|
|
187
216
|
def __init__(self, spec: ExtensionSpecT) -> None:
|
|
188
217
|
self.spec = spec
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
4
6
|
from collections.abc import Generator
|
|
5
|
-
from typing import Literal
|
|
7
|
+
from typing import Generic, Literal, TypeVar
|
|
6
8
|
|
|
7
9
|
from pydantic import BaseModel, Field, model_validator
|
|
8
10
|
|
|
@@ -78,15 +80,37 @@ class CheckboxField(BaseField):
|
|
|
78
80
|
default_value: bool = False
|
|
79
81
|
|
|
80
82
|
|
|
81
|
-
|
|
83
|
+
class CheckboxGroupField(BaseField):
|
|
84
|
+
type: Literal["checkbox_group"] = "checkbox_group"
|
|
85
|
+
fields: list[CheckboxField]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
FormField = (
|
|
89
|
+
TextField | DateField | FileField | SingleSelectField | MultiSelectField | CheckboxField | CheckboxGroupField
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
SettingsFormField = CheckboxGroupField | SingleSelectField
|
|
93
|
+
|
|
82
94
|
|
|
95
|
+
F = TypeVar("F", bound=FormField | SettingsFormField)
|
|
83
96
|
|
|
84
|
-
|
|
97
|
+
|
|
98
|
+
class BaseFormRender(BaseModel, Generic[F]):
|
|
85
99
|
title: str | None = None
|
|
86
100
|
description: str | None = None
|
|
87
101
|
columns: int | None = Field(default=None, ge=1, le=4)
|
|
88
102
|
submit_label: str | None = None
|
|
89
|
-
fields: list[
|
|
103
|
+
fields: list[F]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class FormRender(BaseFormRender[FormField]):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class SettingsFormRender(BaseFormRender[SettingsFormField]):
|
|
111
|
+
"""FormRender for settings - only allows fields defined in SettingsFormField."""
|
|
112
|
+
|
|
113
|
+
pass
|
|
90
114
|
|
|
91
115
|
|
|
92
116
|
class TextFieldValue(BaseModel):
|
|
@@ -125,6 +149,11 @@ class CheckboxFieldValue(BaseModel):
|
|
|
125
149
|
value: bool | None = None
|
|
126
150
|
|
|
127
151
|
|
|
152
|
+
class CheckboxGroupFieldValue(BaseModel):
|
|
153
|
+
type: Literal["checkbox_group"] = "checkbox_group"
|
|
154
|
+
value: dict[str, bool | None] | None = None
|
|
155
|
+
|
|
156
|
+
|
|
128
157
|
FormFieldValue = (
|
|
129
158
|
TextFieldValue
|
|
130
159
|
| DateFieldValue
|
|
@@ -132,13 +161,24 @@ FormFieldValue = (
|
|
|
132
161
|
| SingleSelectFieldValue
|
|
133
162
|
| MultiSelectFieldValue
|
|
134
163
|
| CheckboxFieldValue
|
|
164
|
+
| CheckboxGroupFieldValue
|
|
135
165
|
)
|
|
136
166
|
|
|
167
|
+
SettingsFormFieldValue = CheckboxGroupFieldValue | SingleSelectFieldValue
|
|
168
|
+
|
|
169
|
+
FV = TypeVar("FV", bound=FormFieldValue | SettingsFormFieldValue)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
NonFileIterValue = dict[str, bool | None] | list[str] | str | bool | None
|
|
173
|
+
IterValue = list[dict[str, str | None]] | NonFileIterValue
|
|
174
|
+
|
|
137
175
|
|
|
138
|
-
class
|
|
139
|
-
values: dict[str,
|
|
176
|
+
class BaseFormResponse(BaseModel, Generic[FV]):
|
|
177
|
+
values: dict[str, FV]
|
|
140
178
|
|
|
141
|
-
def __iter__(
|
|
179
|
+
def __iter__(
|
|
180
|
+
self,
|
|
181
|
+
) -> Generator[tuple[str, IterValue]]:
|
|
142
182
|
for key, value in self.values.items():
|
|
143
183
|
match value:
|
|
144
184
|
case FileFieldValue():
|
|
@@ -147,4 +187,14 @@ class FormResponse(BaseModel):
|
|
|
147
187
|
[file.model_dump() for file in value.value] if value.value else None,
|
|
148
188
|
)
|
|
149
189
|
case _:
|
|
150
|
-
yield key, value.value
|
|
190
|
+
yield key, value.value # pyrefly: ignore[invalid-yield]
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class FormResponse(BaseFormResponse[FormFieldValue]):
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class SettingsFormResponse(BaseFormResponse[SettingsFormFieldValue]):
|
|
198
|
+
"""FormResponse for settings - only allows fields defined in SettingsFormFieldValue."""
|
|
199
|
+
|
|
200
|
+
pass
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
+
|
|
4
5
|
from __future__ import annotations
|
|
5
6
|
|
|
6
7
|
import uuid
|
|
@@ -9,33 +10,56 @@ from typing import TYPE_CHECKING, Annotated, Any, Literal
|
|
|
9
10
|
|
|
10
11
|
import a2a.types
|
|
11
12
|
from mcp import Implementation, Tool
|
|
13
|
+
from opentelemetry import trace
|
|
12
14
|
from pydantic import BaseModel, Discriminator, Field, TypeAdapter
|
|
13
15
|
|
|
14
16
|
from agentstack_sdk.a2a.extensions.base import BaseExtensionClient, BaseExtensionServer, BaseExtensionSpec
|
|
15
17
|
from agentstack_sdk.a2a.types import AgentMessage, InputRequired
|
|
18
|
+
from agentstack_sdk.util.pydantic import REDACT_SECRETS, REVEAL_SECRETS, SecureBaseModel
|
|
19
|
+
from agentstack_sdk.util.telemetry import flatten_dict
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"A2A_EXTENSION_APPROVAL_REQUESTED",
|
|
23
|
+
"A2A_EXTENSION_APPROVAL_RESOLVED",
|
|
24
|
+
"ApprovalExtensionClient",
|
|
25
|
+
"ApprovalExtensionMetadata",
|
|
26
|
+
"ApprovalExtensionParams",
|
|
27
|
+
"ApprovalExtensionServer",
|
|
28
|
+
"ApprovalExtensionSpec",
|
|
29
|
+
"ApprovalRejectionError",
|
|
30
|
+
"ApprovalRequest",
|
|
31
|
+
"ApprovalResponse",
|
|
32
|
+
"GenericApprovalRequest",
|
|
33
|
+
"ToolCallApprovalRequest",
|
|
34
|
+
"ToolCallServer",
|
|
35
|
+
]
|
|
16
36
|
|
|
17
37
|
if TYPE_CHECKING:
|
|
18
38
|
from agentstack_sdk.server.context import RunContext
|
|
19
39
|
|
|
20
40
|
|
|
41
|
+
A2A_EXTENSION_APPROVAL_REQUESTED = "a2a_extension.approval.requested"
|
|
42
|
+
A2A_EXTENSION_APPROVAL_RESOLVED = "a2a_extension.approval.resolved"
|
|
43
|
+
|
|
44
|
+
|
|
21
45
|
class ApprovalRejectionError(RuntimeError):
|
|
22
46
|
pass
|
|
23
47
|
|
|
24
48
|
|
|
25
|
-
class GenericApprovalRequest(
|
|
49
|
+
class GenericApprovalRequest(SecureBaseModel):
|
|
26
50
|
action: Literal["generic"] = "generic"
|
|
27
51
|
|
|
28
52
|
title: str | None = Field(None, description="A human-readable title for the action being approved.")
|
|
29
53
|
description: str | None = Field(None, description="A human-readable description of the action being approved.")
|
|
30
54
|
|
|
31
55
|
|
|
32
|
-
class ToolCallServer(
|
|
56
|
+
class ToolCallServer(SecureBaseModel):
|
|
33
57
|
name: str = Field(description="The programmatic name of the server.")
|
|
34
58
|
title: str | None = Field(description="A human-readable title for the server.")
|
|
35
59
|
version: str = Field(description="The version of the server.")
|
|
36
60
|
|
|
37
61
|
|
|
38
|
-
class ToolCallApprovalRequest(
|
|
62
|
+
class ToolCallApprovalRequest(SecureBaseModel):
|
|
39
63
|
action: Literal["tool-call"] = "tool-call"
|
|
40
64
|
|
|
41
65
|
title: str | None = Field(None, description="A human-readable title of the tool.")
|
|
@@ -47,7 +71,7 @@ class ToolCallApprovalRequest(BaseModel):
|
|
|
47
71
|
@staticmethod
|
|
48
72
|
def from_mcp_tool(
|
|
49
73
|
tool: Tool, input: dict[str, Any] | None, server: Implementation | None = None
|
|
50
|
-
) -> ToolCallApprovalRequest:
|
|
74
|
+
) -> "ToolCallApprovalRequest":
|
|
51
75
|
return ToolCallApprovalRequest(
|
|
52
76
|
name=tool.name,
|
|
53
77
|
title=tool.annotations.title if tool.annotations else None,
|
|
@@ -60,7 +84,7 @@ class ToolCallApprovalRequest(BaseModel):
|
|
|
60
84
|
ApprovalRequest = Annotated[GenericApprovalRequest | ToolCallApprovalRequest, Discriminator("action")]
|
|
61
85
|
|
|
62
86
|
|
|
63
|
-
class ApprovalResponse(
|
|
87
|
+
class ApprovalResponse(SecureBaseModel):
|
|
64
88
|
decision: Literal["approve", "reject"]
|
|
65
89
|
|
|
66
90
|
@property
|
|
@@ -86,7 +110,10 @@ class ApprovalExtensionMetadata(BaseModel):
|
|
|
86
110
|
|
|
87
111
|
class ApprovalExtensionServer(BaseExtensionServer[ApprovalExtensionSpec, ApprovalExtensionMetadata]):
|
|
88
112
|
def create_request_message(self, *, request: ApprovalRequest):
|
|
89
|
-
return AgentMessage(
|
|
113
|
+
return AgentMessage(
|
|
114
|
+
text="Approval requested",
|
|
115
|
+
metadata={self.spec.URI: request.model_dump(mode="json", context={REVEAL_SECRETS: True})},
|
|
116
|
+
)
|
|
90
117
|
|
|
91
118
|
def parse_response(self, *, message: a2a.types.Message):
|
|
92
119
|
if not message.metadata or not (data := message.metadata.get(self.spec.URI)):
|
|
@@ -99,11 +126,21 @@ class ApprovalExtensionServer(BaseExtensionServer[ApprovalExtensionSpec, Approva
|
|
|
99
126
|
*,
|
|
100
127
|
context: RunContext,
|
|
101
128
|
) -> ApprovalResponse:
|
|
129
|
+
span = trace.get_current_span()
|
|
130
|
+
span.add_event(
|
|
131
|
+
A2A_EXTENSION_APPROVAL_REQUESTED,
|
|
132
|
+
attributes=flatten_dict(request.model_dump(context={REDACT_SECRETS: True})),
|
|
133
|
+
)
|
|
102
134
|
message = self.create_request_message(request=request)
|
|
103
135
|
message = await context.yield_async(InputRequired(message=message))
|
|
104
136
|
if not message:
|
|
105
137
|
raise RuntimeError("Yield did not return a message")
|
|
106
|
-
|
|
138
|
+
response = self.parse_response(message=message)
|
|
139
|
+
span.add_event(
|
|
140
|
+
A2A_EXTENSION_APPROVAL_RESOLVED,
|
|
141
|
+
attributes=flatten_dict(response.model_dump(context={REDACT_SECRETS: True})),
|
|
142
|
+
)
|
|
143
|
+
return response
|
|
107
144
|
|
|
108
145
|
|
|
109
146
|
class ApprovalExtensionClient(BaseExtensionClient[ApprovalExtensionSpec, NoneType]):
|
|
@@ -113,7 +150,7 @@ class ApprovalExtensionClient(BaseExtensionClient[ApprovalExtensionSpec, NoneTyp
|
|
|
113
150
|
role=a2a.types.Role.user,
|
|
114
151
|
parts=[],
|
|
115
152
|
task_id=task_id,
|
|
116
|
-
metadata={self.spec.URI: response.model_dump(mode="json")},
|
|
153
|
+
metadata={self.spec.URI: response.model_dump(mode="json", context={REVEAL_SECRETS: True})},
|
|
117
154
|
)
|
|
118
155
|
|
|
119
156
|
def parse_request(self, *, message: a2a.types.Message):
|
|
@@ -122,4 +159,4 @@ class ApprovalExtensionClient(BaseExtensionClient[ApprovalExtensionSpec, NoneTyp
|
|
|
122
159
|
return TypeAdapter(ApprovalRequest).validate_python(data)
|
|
123
160
|
|
|
124
161
|
def metadata(self) -> dict[str, Any]:
|
|
125
|
-
return {self.spec.URI: ApprovalExtensionMetadata().model_dump(mode="json")}
|
|
162
|
+
return {self.spec.URI: ApprovalExtensionMetadata().model_dump(mode="json", context={REVEAL_SECRETS: True})}
|