Skip to content

Commit 94e52de

Browse files
committed
wip
1 parent c6d7d2e commit 94e52de

File tree

26 files changed

+605
-245
lines changed

26 files changed

+605
-245
lines changed

agents/chat/.vscode/launch.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
{
88
"name": "agent-chat",
99
"type": "debugpy",
10+
"justMyCode": false,
1011
"request": "launch",
1112
"program": "${workspaceFolder}/src/chat/agent.py",
1213
"console": "integratedTerminal"

agents/chat/src/chat/agent.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,11 @@
1212
from agentstack_sdk.server.middleware.platform_auth_backend import PlatformAuthBackend
1313
from beeai_framework.agents.requirement.utils._tool import FinalAnswerTool
1414
from beeai_framework.errors import FrameworkError
15-
from pydantic import BaseModel
1615

1716
from agentstack_sdk.a2a.extensions import (
1817
AgentDetail,
1918
AgentDetailContributor,
2019
AgentDetailTool,
21-
BaseExtensionServer,
22-
BaseExtensionSpec,
2320
CitationExtensionServer,
2421
CitationExtensionSpec,
2522
ErrorExtensionParams,
@@ -31,15 +28,8 @@
3128
LLMServiceExtensionSpec,
3229
)
3330

34-
# Monkey-patch to remove FormExtensionSpec which no longer exists
35-
# TODO: remove after next release
36-
import agentstack_sdk.a2a.extensions as agentstack_extensions
3731
from chat.tools.files.file_reader import FileReaderTool
3832

39-
agentstack_extensions.FormExtensionSpec = BaseExtensionSpec
40-
agentstack_extensions.FormExtensionServer = BaseExtensionServer
41-
agentstack_extensions.TextField = BaseModel
42-
4333
from beeai_framework.adapters.agentstack.backend.chat import AgentStackChatModel
4434
from beeai_framework.agents.requirement import RequirementAgent
4535
from beeai_framework.agents.requirement.events import (
@@ -301,7 +291,7 @@ def serve():
301291
port=int(os.getenv("PORT", 8000)),
302292
configure_telemetry=True,
303293
context_store=PlatformContextStore(),
304-
auth_backend=PlatformAuthBackend(skip_audience_validation=True),
294+
auth_backend=PlatformAuthBackend(),
305295
)
306296
except KeyboardInterrupt:
307297
pass

agents/rag/.vscode/launch.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
{
88
"name": "agent-rag",
99
"type": "debugpy",
10+
"justMyCode": false,
1011
"request": "launch",
1112
"program": "${workspaceFolder}/src/rag/agent.py",
1213
"console": "integratedTerminal"

agents/rag/src/rag/agent.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from typing import Annotated
88
import os
99

10-
from pydantic import BaseModel
1110
from agentstack_sdk.a2a.extensions import (
1211
AgentDetail,
1312
CitationExtensionServer,
@@ -18,18 +17,8 @@
1817
LLMServiceExtensionSpec,
1918
EmbeddingServiceExtensionServer,
2019
EmbeddingServiceExtensionSpec,
21-
BaseExtensionSpec,
22-
BaseExtensionServer,
2320
)
2421

25-
# Monkey-patch to remove FormExtensionSpec which no longer exists
26-
# TODO: remove after next release
27-
import agentstack_sdk.a2a.extensions as agentstack_extensions
28-
29-
agentstack_extensions.FormExtensionSpec = BaseExtensionSpec
30-
agentstack_extensions.FormExtensionServer = BaseExtensionServer
31-
agentstack_extensions.TextField = BaseModel
32-
3322
from a2a.types import AgentSkill, Message
3423
from beeai_framework.adapters.agentstack.backend.chat import AgentStackChatModel
3524
from beeai_framework.agents.requirement import RequirementAgent

apps/agentstack-sdk-py/src/agentstack_sdk/a2a/extensions/ui/canvas.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from __future__ import annotations
55

66
from typing import TYPE_CHECKING
7+
from typing_extensions import override
78

89
import pydantic
910
from a2a.server.agent_execution.context import RequestContext

apps/agentstack-sdk-py/src/agentstack_sdk/platform/context.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from agentstack_sdk.platform.client import PlatformClient, get_platform_client
1515
from agentstack_sdk.platform.common import PaginatedResult
16+
from agentstack_sdk.platform.provider import Provider
1617
from agentstack_sdk.platform.types import Metadata, MetadataPatch
1718
from agentstack_sdk.util.utils import filter_dict
1819

@@ -40,7 +41,7 @@ class ContextPermissions(pydantic.BaseModel):
4041
class Permissions(ContextPermissions):
4142
llm: set[Literal["*"] | str] = set()
4243
embeddings: set[Literal["*"] | str] = set()
43-
a2a_proxy: set[Literal["*"]] = set()
44+
a2a_proxy: set[Literal["*"] | str] = set()
4445
model_providers: set[Literal["read", "write", "*"]] = set()
4546
variables: SerializeAsAny[set[Literal["read", "write", "*"]]] = set()
4647

@@ -179,6 +180,7 @@ async def delete(
179180
async def generate_token(
180181
self: Context | str,
181182
*,
183+
providers: list[str] | list[Provider] | None = None,
182184
client: PlatformClient | None = None,
183185
grant_global_permissions: Permissions | None = None,
184186
grant_context_permissions: ContextPermissions | None = None,
@@ -193,6 +195,18 @@ async def generate_token(
193195
context_id = self if isinstance(self, str) else self.id
194196
grant_global_permissions = grant_global_permissions or Permissions()
195197
grant_context_permissions = grant_context_permissions or Permissions()
198+
199+
if isinstance(self, Context) and self.metadata and (provider_id := self.metadata.get("provider_id", None)):
200+
providers = providers or [provider_id]
201+
202+
if "*" not in grant_global_permissions.a2a_proxy:
203+
if not providers:
204+
raise ValueError(
205+
"Invalid audience: You must specify providers or use '*' in grant_global_permissions.a2a_proxy."
206+
)
207+
208+
grant_global_permissions.a2a_proxy |= {p.id if isinstance(p, Provider) else p for p in providers}
209+
196210
async with client or get_platform_client() as client:
197211
token_response = (
198212
(

apps/agentstack-sdk-py/src/agentstack_sdk/server/dependencies.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
22
# SPDX-License-Identifier: Apache-2.0
33

4+
from __future__ import annotations
5+
46
import inspect
57
from collections import Counter
68
from collections.abc import AsyncIterator, Callable
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
2+
# SPDX-License-Identifier: Apache-2.0
3+

apps/agentstack-sdk-py/src/agentstack_sdk/server/middleware/platform_auth_backend.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import logging
55
import os
66
from datetime import timedelta
7+
from urllib.parse import urljoin
78

89
from a2a.auth.user import User
910
from async_lru import alru_cache
@@ -76,12 +77,13 @@ def __init__(self, public_url: str | None = None, skip_audience_validation: bool
7677
if skip_audience_validation is not None
7778
else os.getenv("PLATFORM_AUTH__SKIP_AUDIENCE_VALIDATION", "false").lower() in ("true", "1")
7879
)
79-
_audience = public_url or os.getenv("PLATFORM_AUTH__PUBLIC_URL", "http://host.docker.internal:8333")
80-
if not self.skip_audience_validation and not _audience:
81-
raise ValueError(
82-
"Public URL must be provided if audience validation is enabled (hint: set PLATFORM_AUTH__PUBLIC_URL env variable)"
80+
self._audience: str | None = public_url or os.getenv("PLATFORM_AUTH__PUBLIC_URL", None)
81+
if not self.skip_audience_validation and not self._audience:
82+
logger.warning(
83+
"Public URL is not provided and audience validation is enabled. Proceeding to check audience from the request target URL. "
84+
+ "This may not work when requests to agents are proxied. (hint: set PLATFORM_AUTH__PUBLIC_URL env variable)"
8385
)
84-
self.audience: str = _audience or "skip"
86+
8587
self.security: HTTPBearer = HTTPBearer(auto_error=False)
8688

8789
@override
@@ -95,7 +97,15 @@ async def authenticate(self, conn: HTTPConnection) -> tuple[AuthCredentials, Bas
9597
if not (auth := await self.security(request)):
9698
raise AuthenticationError("Missing Authorization header")
9799

100+
audiences: list[str] = []
101+
if not self.skip_audience_validation:
102+
if self._audience:
103+
audiences = [urljoin(self._audience, path) for path in ["/", "/jsonrpc"]]
104+
else:
105+
audiences = [str(request.url.replace(path=path)) for path in ["/", "/jsonrpc"]]
106+
98107
try:
108+
# check only hostname urljoin("http://host:port/a/b", "/") -> "http://host:port/"
99109
jwks = await discover_jwks()
100110

101111
# Verify signature
@@ -107,7 +117,7 @@ async def authenticate(self, conn: HTTPConnection) -> tuple[AuthCredentials, Bas
107117
"exp": {"essential": True},
108118
# "iss": {"essential": True}, # Issuer validation might be tricky if internal/external URLs differ
109119
}
110-
| ({"aud": {"essential": True, "value": self.audience}} if not self.skip_audience_validation else {}),
120+
| ({"aud": {"essential": True, "values": audiences}} if not self.skip_audience_validation else {}),
111121
)
112122
claims.validate()
113123

apps/agentstack-sdk-py/src/agentstack_sdk/types.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
2+
# SPDX-License-Identifier: Apache-2.0
3+
14
from typing import TYPE_CHECKING, TypeAlias
25

36
if TYPE_CHECKING:

0 commit comments

Comments
 (0)