agentstack-sdk 0.4.3rc3__tar.gz → 0.5.0rc4__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 (75) hide show
  1. agentstack_sdk-0.5.0rc4/PKG-INFO +118 -0
  2. agentstack_sdk-0.5.0rc4/README.md +94 -0
  3. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/pyproject.toml +2 -2
  4. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/base.py +1 -1
  5. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/services/form.py +2 -1
  6. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/services/mcp.py +8 -13
  7. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/ui/canvas.py +4 -1
  8. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/ui/citation.py +1 -6
  9. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/ui/error.py +39 -30
  10. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/ui/trajectory.py +1 -2
  11. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/types.py +15 -14
  12. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/platform/__init__.py +2 -0
  13. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/platform/context.py +2 -6
  14. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/platform/file.py +45 -1
  15. agentstack_sdk-0.5.0rc4/src/agentstack_sdk/platform/user.py +70 -0
  16. agentstack_sdk-0.5.0rc4/src/agentstack_sdk/platform/user_feedback.py +42 -0
  17. agentstack_sdk-0.5.0rc4/src/agentstack_sdk/server/agent.py +584 -0
  18. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/server/app.py +13 -2
  19. agentstack_sdk-0.5.0rc4/src/agentstack_sdk/server/constants.py +9 -0
  20. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/server/context.py +0 -9
  21. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/server/dependencies.py +5 -11
  22. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/server/server.py +3 -1
  23. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/util/utils.py +5 -1
  24. agentstack_sdk-0.4.3rc3/PKG-INFO +0 -69
  25. agentstack_sdk-0.4.3rc3/README.md +0 -45
  26. agentstack_sdk-0.4.3rc3/src/agentstack_sdk/platform/user.py +0 -25
  27. agentstack_sdk-0.4.3rc3/src/agentstack_sdk/server/agent.py +0 -573
  28. agentstack_sdk-0.4.3rc3/src/agentstack_sdk/server/constants.py +0 -12
  29. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/__init__.py +0 -0
  30. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/__init__.py +0 -0
  31. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/__init__.py +0 -0
  32. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/auth/__init__.py +0 -0
  33. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/auth/oauth/__init__.py +0 -0
  34. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/auth/oauth/oauth.py +0 -0
  35. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/auth/oauth/storage/__init__.py +0 -0
  36. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/auth/oauth/storage/base.py +0 -0
  37. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/auth/oauth/storage/memory.py +0 -0
  38. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/auth/secrets/__init__.py +0 -0
  39. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/auth/secrets/secrets.py +0 -0
  40. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/common/__init__.py +0 -0
  41. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/common/form.py +0 -0
  42. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/exceptions.py +0 -0
  43. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/services/__init__.py +0 -0
  44. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/services/embedding.py +0 -0
  45. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/services/llm.py +0 -0
  46. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/services/platform.py +0 -0
  47. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/tools/__init__.py +0 -0
  48. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/tools/call.py +0 -0
  49. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/tools/exceptions.py +0 -0
  50. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/ui/__init__.py +0 -0
  51. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/ui/agent_detail.py +0 -0
  52. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/ui/form_request.py +0 -0
  53. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/a2a/extensions/ui/settings.py +0 -0
  54. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/platform/client.py +0 -0
  55. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/platform/common.py +0 -0
  56. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/platform/configuration.py +0 -0
  57. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/platform/model_provider.py +0 -0
  58. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/platform/provider.py +0 -0
  59. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/platform/provider_build.py +0 -0
  60. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/platform/types.py +0 -0
  61. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/platform/variables.py +0 -0
  62. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/platform/vector_store.py +0 -0
  63. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/py.typed +0 -0
  64. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/server/__init__.py +0 -0
  65. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/server/store/__init__.py +0 -0
  66. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/server/store/context_store.py +0 -0
  67. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/server/store/memory_context_store.py +0 -0
  68. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/server/store/platform_context_store.py +0 -0
  69. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/server/telemetry.py +0 -0
  70. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/server/utils.py +0 -0
  71. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/util/__init__.py +0 -0
  72. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/util/file.py +0 -0
  73. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/util/httpx.py +0 -0
  74. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/util/logging.py +0 -0
  75. {agentstack_sdk-0.4.3rc3 → agentstack_sdk-0.5.0rc4}/src/agentstack_sdk/util/resource_context.py +0 -0
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.3
2
+ Name: agentstack-sdk
3
+ Version: 0.5.0rc4
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-Python: >=3.11, <3.14
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Agent Stack Server SDK
26
+
27
+ Python SDK for packaging agents for deployment to Agent Stack infrastructure.
28
+
29
+ [![PyPI version](https://img.shields.io/pypi/v/agentstack-sdk.svg?style=plastic)](https://pypi.org/project/agentstack-sdk/)
30
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg?style=plastic)](https://opensource.org/licenses/Apache-2.0)
31
+ [![LF AI & Data](https://img.shields.io/badge/LF%20AI%20%26%20Data-0072C6?style=plastic&logo=linuxfoundation&logoColor=white)](https://lfaidata.foundation/projects/)
32
+
33
+ ## Overview
34
+
35
+ 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.
36
+
37
+ ## Key Features
38
+
39
+ - **Framework-Agnostic Deployment** - Wrap agents from any framework for Agent Stack deployment
40
+ - **A2A Protocol Support** - Automatic handling of Agent-to-Agent communication
41
+ - **Platform Service Integration** - Connect to Agent Stack's managed LLM, embedding, file storage, and vector store services
42
+ - **Context Storage** - Manage data associated with conversation contexts
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ uv add agentstack-sdk
48
+ ```
49
+
50
+ ## Quickstart
51
+
52
+ ```python
53
+ import os
54
+
55
+ from a2a.types import (
56
+ Message,
57
+ )
58
+ from a2a.utils.message import get_message_text
59
+ from agentstack_sdk.server import Server
60
+ from agentstack_sdk.server.context import RunContext
61
+ from agentstack_sdk.a2a.types import AgentMessage
62
+
63
+ server = Server()
64
+
65
+ @server.agent()
66
+ async def example_agent(input: Message, context: RunContext):
67
+ """Polite agent that greets the user"""
68
+ hello_template: str = os.getenv("HELLO_TEMPLATE", "Ciao %s!")
69
+ yield AgentMessage(text=hello_template % get_message_text(input))
70
+
71
+ def run():
72
+ try:
73
+ server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000)))
74
+ except KeyboardInterrupt:
75
+ pass
76
+
77
+
78
+ if __name__ == "__main__":
79
+ run()
80
+ ```
81
+
82
+ Run the agent:
83
+
84
+ ```bash
85
+ uv run my_agent.py
86
+ ```
87
+
88
+ ## Available Extensions
89
+
90
+ The SDK includes extension support for:
91
+
92
+ - **Citations** - Source attribution (`CitationExtensionServer`, `CitationExtensionSpec`)
93
+ - **Trajectory** - Agent decision logging (`TrajectoryExtensionServer`, `TrajectoryExtensionSpec`)
94
+ - **Settings** - User-configurable agent parameters (`SettingsExtensionServer`, `SettingsExtensionSpec`)
95
+ - **LLM Services** - Platform-managed language models (`LLMServiceExtensionServer`, `LLMServiceExtensionSpec`)
96
+ - **Agent Details** - Metadata and UI enhancements (`AgentDetail`)
97
+ - **And more** - See [Documentation](https://agentstack.beeai.dev/stable/agent-development/overview)
98
+
99
+ Each extension provides both server-side handlers and A2A protocol specifications for seamless integration with Agent Stack's UI and infrastructure.
100
+
101
+ ## Resources
102
+
103
+ - [Agent Stack Documentation](https://agentstack.beeai.dev)
104
+ - [GitHub Repository](https://github.com/i-am-bee/agentstack)
105
+ - [PyPI Package](https://pypi.org/project/agentstack-sdk/)
106
+
107
+ ## Contributing
108
+
109
+ Contributions are welcome! Please see the [Contributing Guide](https://github.com/i-am-bee/agentstack/blob/main/CONTRIBUTING.md) for details.
110
+
111
+ ## Support
112
+
113
+ - [GitHub Issues](https://github.com/i-am-bee/agentstack/issues)
114
+ - [GitHub Discussions](https://github.com/i-am-bee/agentstack/discussions)
115
+
116
+ ---
117
+
118
+ 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,94 @@
1
+ # Agent Stack Server SDK
2
+
3
+ Python SDK for packaging agents for deployment to Agent Stack infrastructure.
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/agentstack-sdk.svg?style=plastic)](https://pypi.org/project/agentstack-sdk/)
6
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg?style=plastic)](https://opensource.org/licenses/Apache-2.0)
7
+ [![LF AI & Data](https://img.shields.io/badge/LF%20AI%20%26%20Data-0072C6?style=plastic&logo=linuxfoundation&logoColor=white)](https://lfaidata.foundation/projects/)
8
+
9
+ ## Overview
10
+
11
+ 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.
12
+
13
+ ## Key Features
14
+
15
+ - **Framework-Agnostic Deployment** - Wrap agents from any framework for Agent Stack deployment
16
+ - **A2A Protocol Support** - Automatic handling of Agent-to-Agent communication
17
+ - **Platform Service Integration** - Connect to Agent Stack's managed LLM, embedding, file storage, and vector store services
18
+ - **Context Storage** - Manage data associated with conversation contexts
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ uv add agentstack-sdk
24
+ ```
25
+
26
+ ## Quickstart
27
+
28
+ ```python
29
+ import os
30
+
31
+ from a2a.types import (
32
+ Message,
33
+ )
34
+ from a2a.utils.message import get_message_text
35
+ from agentstack_sdk.server import Server
36
+ from agentstack_sdk.server.context import RunContext
37
+ from agentstack_sdk.a2a.types import AgentMessage
38
+
39
+ server = Server()
40
+
41
+ @server.agent()
42
+ async def example_agent(input: Message, context: RunContext):
43
+ """Polite agent that greets the user"""
44
+ hello_template: str = os.getenv("HELLO_TEMPLATE", "Ciao %s!")
45
+ yield AgentMessage(text=hello_template % get_message_text(input))
46
+
47
+ def run():
48
+ try:
49
+ server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000)))
50
+ except KeyboardInterrupt:
51
+ pass
52
+
53
+
54
+ if __name__ == "__main__":
55
+ run()
56
+ ```
57
+
58
+ Run the agent:
59
+
60
+ ```bash
61
+ uv run my_agent.py
62
+ ```
63
+
64
+ ## Available Extensions
65
+
66
+ The SDK includes extension support for:
67
+
68
+ - **Citations** - Source attribution (`CitationExtensionServer`, `CitationExtensionSpec`)
69
+ - **Trajectory** - Agent decision logging (`TrajectoryExtensionServer`, `TrajectoryExtensionSpec`)
70
+ - **Settings** - User-configurable agent parameters (`SettingsExtensionServer`, `SettingsExtensionSpec`)
71
+ - **LLM Services** - Platform-managed language models (`LLMServiceExtensionServer`, `LLMServiceExtensionSpec`)
72
+ - **Agent Details** - Metadata and UI enhancements (`AgentDetail`)
73
+ - **And more** - See [Documentation](https://agentstack.beeai.dev/stable/agent-development/overview)
74
+
75
+ Each extension provides both server-side handlers and A2A protocol specifications for seamless integration with Agent Stack's UI and infrastructure.
76
+
77
+ ## Resources
78
+
79
+ - [Agent Stack Documentation](https://agentstack.beeai.dev)
80
+ - [GitHub Repository](https://github.com/i-am-bee/agentstack)
81
+ - [PyPI Package](https://pypi.org/project/agentstack-sdk/)
82
+
83
+ ## Contributing
84
+
85
+ Contributions are welcome! Please see the [Contributing Guide](https://github.com/i-am-bee/agentstack/blob/main/CONTRIBUTING.md) for details.
86
+
87
+ ## Support
88
+
89
+ - [GitHub Issues](https://github.com/i-am-bee/agentstack/issues)
90
+ - [GitHub Discussions](https://github.com/i-am-bee/agentstack/discussions)
91
+
92
+ ---
93
+
94
+ 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.
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "agentstack-sdk"
3
- version = "0.4.3-rc3"
3
+ version = "0.5.0-rc4"
4
4
  description = "Agent Stack SDK"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "IBM Corp." }]
7
7
  requires-python = ">=3.11,<3.14"
8
8
  dependencies = [
9
- "a2a-sdk==0.3.9",
9
+ "a2a-sdk==0.3.21",
10
10
  "objprint>=0.3.0",
11
11
  "uvicorn>=0.35.0",
12
12
  "asyncclick>=8.1.8",
@@ -111,7 +111,7 @@ class NoParamsBaseExtensionSpec(BaseExtensionSpec[NoneType]):
111
111
  return None
112
112
 
113
113
 
114
- ExtensionSpecT = typing.TypeVar("ExtensionSpecT", bound=BaseExtensionSpec)
114
+ ExtensionSpecT = typing.TypeVar("ExtensionSpecT", bound=BaseExtensionSpec[typing.Any])
115
115
 
116
116
 
117
117
  class BaseExtensionServer(abc.ABC, typing.Generic[ExtensionSpecT, MetadataFromClientT]):
@@ -4,9 +4,10 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- from typing import Self, TypedDict, TypeVar, cast
7
+ from typing import Self, TypeVar, cast
8
8
 
9
9
  from pydantic import BaseModel, TypeAdapter
10
+ from typing_extensions import TypedDict
10
11
 
11
12
  from agentstack_sdk.a2a.extensions.base import BaseExtensionClient, BaseExtensionServer, BaseExtensionSpec
12
13
  from agentstack_sdk.a2a.extensions.common.form import FormRender, FormResponse
@@ -39,7 +39,7 @@ class StdioTransport(pydantic.BaseModel):
39
39
  class StreamableHTTPTransport(pydantic.BaseModel):
40
40
  type: Literal["streamable_http"] = "streamable_http"
41
41
 
42
- url: pydantic.AnyHttpUrl
42
+ url: str
43
43
  headers: dict[str, str] | None = None
44
44
 
45
45
 
@@ -111,13 +111,7 @@ class MCPServiceExtensionServer(BaseExtensionServer[MCPServiceExtensionSpec, MCP
111
111
  for fullfilment in self.data.mcp_fulfillments.values():
112
112
  if fullfilment.transport.type == "streamable_http":
113
113
  try:
114
- fullfilment.transport.url = pydantic.AnyHttpUrl(
115
- re.sub(
116
- r"^http[s]?://{platform_url}",
117
- platform_url,
118
- str(fullfilment.transport.url),
119
- )
120
- )
114
+ fullfilment.transport.url = re.sub("^{platform_url}", platform_url, str(fullfilment.transport.url))
121
115
  except Exception:
122
116
  logger.warning("Platform URL substitution failed", exc_info=True)
123
117
 
@@ -126,7 +120,7 @@ class MCPServiceExtensionServer(BaseExtensionServer[MCPServiceExtensionSpec, MCP
126
120
  if metadata:
127
121
  for name, demand in self.spec.params.mcp_demands.items():
128
122
  if not (fulfillment := metadata.mcp_fulfillments.get(name)):
129
- raise ValueError(f'Fulfillment for demand "{name}" missing')
123
+ continue
130
124
  if fulfillment.transport.type not in demand.allowed_transports:
131
125
  raise ValueError(f'Transport "{fulfillment.transport.type}" not allowed for demand "{name}"')
132
126
  return metadata
@@ -148,7 +142,8 @@ class MCPServiceExtensionServer(BaseExtensionServer[MCPServiceExtensionSpec, MCP
148
142
  fulfillment = self.data.mcp_fulfillments.get(demand) if self.data else None
149
143
 
150
144
  if not fulfillment:
151
- raise ValueError(f'No fulfillment for demand "{demand}"')
145
+ yield None
146
+ return
152
147
 
153
148
  transport = fulfillment.transport
154
149
 
@@ -162,7 +157,7 @@ class MCPServiceExtensionServer(BaseExtensionServer[MCPServiceExtensionSpec, MCP
162
157
  yield (read, write)
163
158
  elif isinstance(transport, StreamableHTTPTransport):
164
159
  async with streamablehttp_client(
165
- url=str(transport.url),
160
+ url=transport.url,
166
161
  headers=transport.headers,
167
162
  auth=await self._create_auth(transport),
168
163
  ) as (
@@ -180,12 +175,12 @@ class MCPServiceExtensionServer(BaseExtensionServer[MCPServiceExtensionSpec, MCP
180
175
  platform
181
176
  and platform.data
182
177
  and platform.data.base_url
183
- and str(transport.url).startswith(str(platform.data.base_url))
178
+ and transport.url.startswith(str(platform.data.base_url))
184
179
  ):
185
180
  return await platform.create_httpx_auth()
186
181
  oauth = self._get_oauth_server()
187
182
  if oauth:
188
- return await oauth.create_httpx_auth(resource_url=transport.url)
183
+ return await oauth.create_httpx_auth(resource_url=pydantic.AnyUrl(transport.url))
189
184
  return None
190
185
 
191
186
 
@@ -6,7 +6,7 @@ from __future__ import annotations
6
6
  from typing import TYPE_CHECKING
7
7
 
8
8
  import pydantic
9
- from a2a.types import Artifact
9
+ from a2a.types import Artifact, TextPart
10
10
  from a2a.types import Message as A2AMessage
11
11
 
12
12
  if TYPE_CHECKING:
@@ -38,6 +38,9 @@ class CanvasExtensionSpec(NoParamsBaseExtensionSpec):
38
38
 
39
39
  class CanvasExtensionServer(BaseExtensionServer[CanvasExtensionSpec, CanvasEditRequestMetadata]):
40
40
  def handle_incoming_message(self, message: A2AMessage, context: RunContext):
41
+ if message.metadata and self.spec.URI in message.metadata and message.parts:
42
+ message.parts = [part for part in message.parts if not isinstance(part.root, TextPart)]
43
+
41
44
  super().handle_incoming_message(message, context)
42
45
  self.context = context
43
46
 
@@ -4,7 +4,6 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  from types import NoneType
7
- from typing import Any
8
7
 
9
8
  import pydantic
10
9
  from a2a.types import DataPart, FilePart, Part, TextPart
@@ -59,11 +58,7 @@ class CitationExtensionSpec(NoParamsBaseExtensionSpec):
59
58
 
60
59
 
61
60
  class CitationExtensionServer(BaseExtensionServer[CitationExtensionSpec, NoneType]):
62
- def citation_metadata(
63
- self,
64
- *,
65
- citations: list[Citation],
66
- ) -> Metadata[str, Any]:
61
+ def citation_metadata(self, *, citations: list[Citation]) -> Metadata:
67
62
  return Metadata({self.spec.URI: CitationMetadata(citations=citations).model_dump(mode="json")})
68
63
 
69
64
  def message(
@@ -3,14 +3,13 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
- import contextvars
7
6
  import json
8
7
  import logging
9
8
  import traceback
10
9
  from collections.abc import AsyncIterator
11
10
  from contextlib import asynccontextmanager
12
11
  from types import NoneType
13
- from typing import Any
12
+ from typing import Any, Final
14
13
 
15
14
  import pydantic
16
15
 
@@ -20,6 +19,7 @@ from agentstack_sdk.a2a.extensions.base import (
20
19
  BaseExtensionSpec,
21
20
  )
22
21
  from agentstack_sdk.a2a.types import AgentMessage, JsonDict, Metadata
22
+ from agentstack_sdk.util import resource_context
23
23
 
24
24
  logger = logging.getLogger(__name__)
25
25
 
@@ -125,25 +125,18 @@ def _extract_error(exc: BaseException) -> Error | ErrorGroup:
125
125
  class ErrorExtensionServer(BaseExtensionServer[ErrorExtensionSpec, NoneType]):
126
126
  def __init__(self, *args: Any, **kwargs: Any) -> None:
127
127
  super().__init__(*args, **kwargs)
128
- # Server-scoped ContextVar for request-scoped error context
129
- self._error_context_var: contextvars.ContextVar[JsonDict] = contextvars.ContextVar("error_context")
130
128
 
131
129
  @asynccontextmanager
132
130
  async def lifespan(self) -> AsyncIterator[None]:
133
131
  """Set up request-scoped error context using ContextVar."""
134
- # Set an empty dict for this request's context
135
- token = self._error_context_var.set({})
136
-
137
- try:
132
+ with use_error_extension_context(server=self, context={}):
138
133
  yield
139
- finally:
140
- self._error_context_var.reset(token)
141
134
 
142
135
  @property
143
136
  def context(self) -> JsonDict:
144
137
  """Get the current request's error context."""
145
138
  try:
146
- return self._error_context_var.get()
139
+ return get_error_extension_context().context
147
140
  except LookupError:
148
141
  # Fallback for when lifespan hasn't been entered yet
149
142
  logger.warning(
@@ -151,7 +144,7 @@ class ErrorExtensionServer(BaseExtensionServer[ErrorExtensionSpec, NoneType]):
151
144
  )
152
145
  return {}
153
146
 
154
- def error_metadata(self, error: BaseException) -> Metadata[str, Any]:
147
+ def error_metadata(self, error: BaseException) -> Metadata:
155
148
  """
156
149
  Create metadata for an error.
157
150
 
@@ -186,28 +179,44 @@ class ErrorExtensionServer(BaseExtensionServer[ErrorExtensionSpec, NoneType]):
186
179
  Returns:
187
180
  AgentMessage with error metadata and markdown-formatted text
188
181
  """
189
- metadata = self.error_metadata(error)
190
- error_metadata = ErrorMetadata.model_validate(metadata[self.spec.URI])
191
-
192
- # Serialize to markdown for display
193
- text_lines: list[str] = []
194
- if isinstance(error_metadata.error, ErrorGroup):
195
- text_lines.append(f"## {error_metadata.error.message}\n")
196
- for err in error_metadata.error.errors:
197
- text_lines.append(f"### {err.title}\n{err.message}")
198
- else:
199
- text_lines.append(f"## {error_metadata.error.title}\n{error_metadata.error.message}")
182
+ try:
183
+ metadata = self.error_metadata(error)
184
+ error_metadata = ErrorMetadata.model_validate(metadata[self.spec.URI])
185
+
186
+ # Serialize to markdown for display
187
+ text_lines: list[str] = []
188
+ if isinstance(error_metadata.error, ErrorGroup):
189
+ text_lines.append(f"## {error_metadata.error.message}\n")
190
+ for err in error_metadata.error.errors:
191
+ text_lines.append(f"### {err.title}\n{err.message}")
192
+ else:
193
+ text_lines.append(f"## {error_metadata.error.title}\n{error_metadata.error.message}")
200
194
 
201
- # Add context if present
202
- if error_metadata.context:
203
- text_lines.append(f"## Context\n```json\n{json.dumps(error_metadata.context, indent=2)}\n```")
195
+ # Add context if present
196
+ if error_metadata.context:
197
+ text_lines.append(f"## Context\n```json\n{json.dumps(error_metadata.context, indent=2)}\n```")
204
198
 
205
- if error_metadata.stack_trace:
206
- text_lines.append(f"## Stack Trace\n```\n{error_metadata.stack_trace}\n```")
199
+ if error_metadata.stack_trace:
200
+ text_lines.append(f"## Stack Trace\n```\n{error_metadata.stack_trace}\n```")
207
201
 
208
- text = "\n\n".join(text_lines)
202
+ text = "\n\n".join(text_lines)
209
203
 
210
- return AgentMessage(text=text, metadata=metadata)
204
+ return AgentMessage(text=text, metadata=metadata)
205
+ except Exception as error_exc:
206
+ return AgentMessage(text=f"Failed to create error message: {error_exc!s}\noriginal exc: {error!s}")
211
207
 
212
208
 
213
209
  class ErrorExtensionClient(BaseExtensionClient[ErrorExtensionSpec, ErrorMetadata]): ...
210
+
211
+
212
+ DEFAULT_ERROR_EXTENSION: Final = ErrorExtensionServer(ErrorExtensionSpec(ErrorExtensionParams()))
213
+
214
+
215
+ class ErrorContext(pydantic.BaseModel, arbitrary_types_allowed=True):
216
+ server: ErrorExtensionServer = pydantic.Field(default=DEFAULT_ERROR_EXTENSION)
217
+ context: JsonDict = pydantic.Field(default_factory=dict)
218
+
219
+
220
+ get_error_extension_context, use_error_extension_context = resource_context(
221
+ factory=ErrorContext, default_factory=ErrorContext
222
+ )
@@ -4,7 +4,6 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  from types import NoneType
7
- from typing import Any
8
7
 
9
8
  import pydantic
10
9
  from a2a.types import DataPart, FilePart, Part, TextPart
@@ -49,7 +48,7 @@ class TrajectoryExtensionSpec(NoParamsBaseExtensionSpec):
49
48
  class TrajectoryExtensionServer(BaseExtensionServer[TrajectoryExtensionSpec, NoneType]):
50
49
  def trajectory_metadata(
51
50
  self, *, title: str | None = None, content: str | None = None, group_id: str | None = None
52
- ) -> Metadata[str, Any]:
51
+ ) -> Metadata:
53
52
  return Metadata(
54
53
  {self.spec.URI: Trajectory(title=title, content=content, group_id=group_id).model_dump(mode="json")}
55
54
  )
@@ -1,8 +1,7 @@
1
1
  # Copyright 2025 © BeeAI a Series of LF Projects, LLC
2
2
  # SPDX-License-Identifier: Apache-2.0
3
- import typing
4
3
  import uuid
5
- from typing import Generic, Literal, TypeAlias, TypeAliasType, Union
4
+ from typing import TYPE_CHECKING, Literal, TypeAlias
6
5
 
7
6
  from a2a.types import (
8
7
  Artifact,
@@ -21,11 +20,19 @@ from a2a.types import (
21
20
  )
22
21
  from pydantic import Field, model_validator
23
22
 
24
- K = typing.TypeVar("K")
25
- V = typing.TypeVar("V")
23
+ if TYPE_CHECKING:
24
+ JsonValue: TypeAlias = list["JsonValue"] | dict[str, "JsonValue"] | str | bool | int | float | None
25
+ JsonDict: TypeAlias = dict[str, JsonValue]
26
+ else:
27
+ from typing import Union
26
28
 
29
+ from typing_extensions import TypeAliasType
27
30
 
28
- class Metadata(dict[K, V], Generic[K, V]): ...
31
+ JsonValue = TypeAliasType("JsonValue", "Union[dict[str, JsonValue], list[JsonValue], str, int, float, bool, None]") # noqa: UP007
32
+ JsonDict = TypeAliasType("JsonDict", "dict[str, JsonValue]")
33
+
34
+
35
+ class Metadata(dict[str, JsonValue]): ...
29
36
 
30
37
 
31
38
  RunYield: TypeAlias = (
@@ -42,7 +49,7 @@ RunYield: TypeAlias = (
42
49
  | TaskStatusUpdateEvent
43
50
  | TaskArtifactUpdateEvent
44
51
  | str
45
- | dict
52
+ | JsonDict
46
53
  | Exception
47
54
  )
48
55
  RunYieldResume: TypeAlias = Message | None
@@ -50,7 +57,7 @@ RunYieldResume: TypeAlias = Message | None
50
57
 
51
58
  class AgentArtifact(Artifact):
52
59
  artifact_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
53
- parts: list[Part | TextPart | FilePart | DataPart] # pyright: ignore [reportIncompatibleVariableOverride]
60
+ parts: list[Part | TextPart | FilePart | DataPart]
54
61
 
55
62
  @model_validator(mode="after")
56
63
  def text_message_validate(self):
@@ -60,7 +67,7 @@ class AgentArtifact(Artifact):
60
67
 
61
68
  class ArtifactChunk(Artifact):
62
69
  last_chunk: bool = False
63
- parts: list[Part | TextPart | FilePart | DataPart] # pyright: ignore [reportIncompatibleVariableOverride]
70
+ parts: list[Part | TextPart | FilePart | DataPart]
64
71
 
65
72
  @model_validator(mode="after")
66
73
  def text_message_validate(self):
@@ -104,9 +111,3 @@ class InputRequired(TaskStatus):
104
111
 
105
112
  class AuthRequired(InputRequired):
106
113
  state: Literal[TaskState.auth_required] = TaskState.auth_required # pyright: ignore [reportIncompatibleVariableOverride]
107
-
108
-
109
- JsonDict = TypeAliasType(
110
- "JsonDict",
111
- "Union[dict[str, JsonDict], list[JsonDict], str, int, float, bool, None]", # pyright: ignore[reportDeprecated] # noqa: UP007
112
- )
@@ -7,4 +7,6 @@ from .file import *
7
7
  from .model_provider import *
8
8
  from .provider import *
9
9
  from .provider_build import *
10
+ from .user import *
11
+ from .user_feedback import *
10
12
  from .vector_store import *
@@ -31,10 +31,6 @@ class ContextToken(pydantic.BaseModel):
31
31
  expires_at: pydantic.AwareDatetime | None = None
32
32
 
33
33
 
34
- class ResourceIdPermission(pydantic.BaseModel):
35
- id: str
36
-
37
-
38
34
  class ContextPermissions(pydantic.BaseModel):
39
35
  files: set[Literal["read", "write", "extract", "*"]] = set()
40
36
  vector_stores: set[Literal["read", "write", "*"]] = set()
@@ -42,8 +38,8 @@ class ContextPermissions(pydantic.BaseModel):
42
38
 
43
39
 
44
40
  class Permissions(ContextPermissions):
45
- llm: set[Literal["*"] | ResourceIdPermission] = set()
46
- embeddings: set[Literal["*"] | ResourceIdPermission] = set()
41
+ llm: set[Literal["*"] | str] = set()
42
+ embeddings: set[Literal["*"] | str] = set()
47
43
  a2a_proxy: set[Literal["*"]] = set()
48
44
  model_providers: set[Literal["read", "write", "*"]] = set()
49
45
  variables: SerializeAsAny[set[Literal["read", "write", "*"]]] = set()
@@ -16,11 +16,20 @@ from agentstack_sdk.platform.common import PaginatedResult
16
16
  from agentstack_sdk.util.file import LoadedFile, LoadedFileWithUri, PlatformFileUrl
17
17
  from agentstack_sdk.util.utils import filter_dict
18
18
 
19
+ ExtractionFormatLiteral = typing.Literal["markdown", "vendor_specific_json"]
20
+
21
+
22
+ class ExtractedFileInfo(pydantic.BaseModel):
23
+ """Information about an extracted file."""
24
+
25
+ file_id: str
26
+ format: ExtractionFormatLiteral | None
27
+
19
28
 
20
29
  class Extraction(pydantic.BaseModel):
21
30
  id: str
22
31
  file_id: str
23
- extracted_file_id: str | None = None
32
+ extracted_files: list[ExtractedFileInfo] = pydantic.Field(default_factory=list)
24
33
  status: typing.Literal["pending", "in_progress", "completed", "failed", "cancelled"] = "pending"
25
34
  job_id: str | None = None
26
35
  error_message: str | None = None
@@ -152,9 +161,43 @@ class File(pydantic.BaseModel):
152
161
  await response.aread()
153
162
  yield LoadedFileWithUri(response=response, content_type=file.content_type, filename=file.filename)
154
163
 
164
+ @asynccontextmanager
165
+ async def load_json_content(
166
+ self: File | str,
167
+ *,
168
+ stream: bool = False,
169
+ client: PlatformClient | None = None,
170
+ context_id: str | None | Literal["auto"] = "auto",
171
+ ) -> AsyncIterator[LoadedFile]:
172
+ # `self` has a weird type so that you can call both `instance.load_json_content()` to create an extraction for an instance, or `File.load_json_content("123")`
173
+ file_id = self if isinstance(self, str) else self.id
174
+ async with client or get_platform_client() as platform_client:
175
+ context_id = platform_client.context_id if context_id == "auto" else context_id
176
+
177
+ file = await File.get(file_id, client=client, context_id=context_id) if isinstance(self, str) else self
178
+ extraction = await file.get_extraction(client=client, context_id=context_id)
179
+
180
+ for extracted_file_info in extraction.extracted_files:
181
+ if extracted_file_info.format != "vendor_specific_json":
182
+ continue
183
+ extracted_json_file_id = extracted_file_info.file_id
184
+ async with platform_client.stream(
185
+ "GET",
186
+ url=f"/api/v1/files/{extracted_json_file_id}/content",
187
+ params=context_id and {"context_id": context_id},
188
+ ) as response:
189
+ response.raise_for_status()
190
+ if not stream:
191
+ await response.aread()
192
+ yield LoadedFileWithUri(response=response, content_type=file.content_type, filename=file.filename)
193
+ return
194
+
195
+ raise ValueError("No extracted JSON content available for this file.")
196
+
155
197
  async def create_extraction(
156
198
  self: File | str,
157
199
  *,
200
+ formats: list[ExtractionFormatLiteral] | None = None,
158
201
  client: PlatformClient | None = None,
159
202
  context_id: str | None | Literal["auto"] = "auto",
160
203
  ) -> Extraction:
@@ -167,6 +210,7 @@ class File(pydantic.BaseModel):
167
210
  await platform_client.post(
168
211
  url=f"/api/v1/files/{file_id}/extraction",
169
212
  params=context_id and {"context_id": context_id},
213
+ json={"settings": {"formats": formats}} if formats else None,
170
214
  )
171
215
  )
172
216
  .raise_for_status()