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.
Files changed (83) hide show
  1. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/PKG-INFO +5 -4
  2. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/pyproject.toml +6 -5
  3. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/__init__.py +6 -0
  4. agentstack_sdk-0.7.0rc11/src/agentstack_sdk/a2a/__init__.py +4 -0
  5. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/__init__.py +2 -0
  6. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/__init__.py +2 -0
  7. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/oauth/__init__.py +2 -0
  8. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/oauth/oauth.py +23 -5
  9. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/oauth/storage/__init__.py +2 -0
  10. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/oauth/storage/base.py +2 -0
  11. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/oauth/storage/memory.py +2 -0
  12. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/secrets/__init__.py +2 -0
  13. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/auth/secrets/secrets.py +33 -4
  14. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/base.py +32 -3
  15. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/common/__init__.py +2 -0
  16. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/common/form.py +58 -8
  17. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/exceptions.py +2 -0
  18. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/interactions/__init__.py +2 -0
  19. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/interactions/approval.py +46 -9
  20. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/services/__init__.py +2 -0
  21. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/services/embedding.py +19 -3
  22. agentstack_sdk-0.7.0rc11/src/agentstack_sdk/a2a/extensions/services/form.py +107 -0
  23. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/services/llm.py +23 -3
  24. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/services/mcp.py +42 -8
  25. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/services/platform.py +20 -11
  26. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/tools/__init__.py +2 -0
  27. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/tools/call.py +18 -4
  28. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/tools/exceptions.py +2 -0
  29. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/__init__.py +2 -0
  30. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/canvas.py +8 -0
  31. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/citation.py +1 -0
  32. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/error.py +1 -0
  33. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/form_request.py +7 -0
  34. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/settings.py +17 -3
  35. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/trajectory.py +1 -0
  36. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/types.py +2 -0
  37. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/__init__.py +2 -0
  38. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/client.py +2 -0
  39. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/common.py +2 -0
  40. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/configuration.py +3 -2
  41. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/connector.py +17 -16
  42. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/context.py +15 -14
  43. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/file.py +14 -13
  44. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/model_provider.py +8 -6
  45. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/provider.py +12 -9
  46. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/provider_build.py +7 -6
  47. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/provider_discovery.py +3 -2
  48. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/types.py +1 -0
  49. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/user.py +3 -2
  50. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/user_feedback.py +4 -1
  51. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/variables.py +3 -2
  52. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/platform/vector_store.py +9 -8
  53. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/__init__.py +2 -0
  54. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/agent.py +19 -3
  55. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/app.py +2 -0
  56. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/constants.py +2 -0
  57. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/context.py +2 -0
  58. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/dependencies.py +17 -11
  59. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/exceptions.py +2 -0
  60. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/middleware/__init__.py +2 -0
  61. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/middleware/platform_auth_backend.py +2 -0
  62. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/server.py +57 -54
  63. agentstack_sdk-0.7.0rc11/src/agentstack_sdk/server/store/__init__.py +5 -0
  64. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/store/context_store.py +6 -0
  65. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/store/memory_context_store.py +2 -0
  66. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/store/platform_context_store.py +2 -0
  67. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/telemetry.py +2 -0
  68. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/server/utils.py +2 -0
  69. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/types.py +8 -0
  70. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/util/__init__.py +2 -0
  71. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/util/file.py +2 -0
  72. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/util/httpx.py +2 -0
  73. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/util/logging.py +2 -0
  74. agentstack_sdk-0.7.0rc11/src/agentstack_sdk/util/pydantic.py +134 -0
  75. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/util/resource_context.py +2 -0
  76. agentstack_sdk-0.7.0rc11/src/agentstack_sdk/util/telemetry.py +280 -0
  77. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/util/utils.py +2 -0
  78. agentstack_sdk-0.6.2rc6/src/agentstack_sdk/a2a/__init__.py +0 -2
  79. agentstack_sdk-0.6.2rc6/src/agentstack_sdk/a2a/extensions/services/form.py +0 -54
  80. agentstack_sdk-0.6.2rc6/src/agentstack_sdk/server/store/__init__.py +0 -3
  81. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/README.md +0 -0
  82. {agentstack_sdk-0.6.2rc6 → agentstack_sdk-0.7.0rc11}/src/agentstack_sdk/a2a/extensions/ui/agent_detail.py +0 -0
  83. {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.6.2rc6
3
+ Version: 0.7.0rc11
4
4
  Summary: Agent Stack SDK
5
5
  Author: IBM Corp.
6
- Requires-Dist: a2a-sdk==0.3.21
6
+ Requires-Dist: a2a-sdk==0.3.24
7
7
  Requires-Dist: objprint>=0.3.0
8
- Requires-Dist: uvicorn>=0.35.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.14
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.6.2-rc6"
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.14"
7
+ requires-python = ">=3.11,<3.15"
8
8
  dependencies = [
9
- "a2a-sdk==0.3.21",
9
+ "a2a-sdk==0.3.24",
10
10
  "objprint>=0.3.0",
11
- "uvicorn>=0.35.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.9.0,<0.10.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()
@@ -0,0 +1,4 @@
1
+ # Copyright 2025 © BeeAI a Series of LF Projects, LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from __future__ import annotations
@@ -1,6 +1,8 @@
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 .auth import *
5
7
  from .interactions import *
6
8
  from .services import *
@@ -1,5 +1,7 @@
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 .oauth import *
5
7
  from .secrets import *
@@ -1,4 +1,6 @@
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 .oauth import *
@@ -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(pydantic.BaseModel):
43
+ class AuthRequest(SecureBaseModel):
30
44
  authorization_endpoint_url: pydantic.AnyUrl
31
45
 
32
46
 
33
- class AuthResponse(pydantic.BaseModel):
47
+ class AuthResponse(SecureBaseModel):
34
48
  redirect_uri: pydantic.AnyUrl
35
49
 
36
50
 
37
- class OAuthFulfillment(pydantic.BaseModel):
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(text="Authorization required", metadata={self.spec.URI: data.model_dump(mode="json")})
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,5 +1,7 @@
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 .base import *
5
7
  from .memory import *
@@ -1,6 +1,8 @@
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
  import abc
5
7
 
6
8
  from mcp.client.auth import TokenStorage
@@ -2,6 +2,8 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from mcp.client.auth import TokenStorage
6
8
  from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
7
9
 
@@ -1,4 +1,6 @@
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 .secrets import *
@@ -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(pydantic.BaseModel):
26
- secret: str
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
- return self.parse_secret_response(message=resume)
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
 
@@ -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
- cls.MetadataFromClient = _get_generic_args(cls, BaseExtensionServer)[1]
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
- cls.MetadataFromServer = _get_generic_args(cls, BaseExtensionClient)[1]
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,4 +1,6 @@
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 .form import *
@@ -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
- FormField = TextField | DateField | FileField | SingleSelectField | MultiSelectField | CheckboxField
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
- class FormRender(BaseModel):
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[FormField]
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 FormResponse(BaseModel):
139
- values: dict[str, FormFieldValue]
176
+ class BaseFormResponse(BaseModel, Generic[FV]):
177
+ values: dict[str, FV]
140
178
 
141
- def __iter__(self) -> Generator[tuple[str, list[dict[str, str | None]] | list[str] | str | bool | None]]:
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,8 @@
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 agentstack_sdk.a2a.extensions.base import BaseExtensionSpec
5
7
 
6
8
 
@@ -1,4 +1,6 @@
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 .approval import *
@@ -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(BaseModel):
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(BaseModel):
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(BaseModel):
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(BaseModel):
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(text="Approval requested", metadata={self.spec.URI: request.model_dump(mode="json")})
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
- return self.parse_response(message=message)
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})}
@@ -1,6 +1,8 @@
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 .embedding import *
5
7
  from .form import *
6
8
  from .llm import *