agentstack-sdk 0.5.2rc4__py3-none-any.whl → 0.6.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -38,8 +38,8 @@ class ToolCallServer(BaseModel):
38
38
  class ToolCallApprovalRequest(BaseModel):
39
39
  action: Literal["tool-call"] = "tool-call"
40
40
 
41
- title: str | None = Field(None, description="A human-readable title for the tool call being approved.")
42
- description: str | None = Field(None, description="A human-readable description of the tool call being approved.")
41
+ title: str | None = Field(None, description="A human-readable title of the tool.")
42
+ description: str | None = Field(None, description="A human-readable description of the tool.")
43
43
  name: str = Field(description="The programmatic name of the tool.")
44
44
  input: dict[str, Any] | None = Field(description="The input for the tool.")
45
45
  server: ToolCallServer | None = Field(None, description="The server executing the tool.")
@@ -3,6 +3,7 @@
3
3
 
4
4
  from .client import *
5
5
  from .configuration import *
6
+ from .connector import *
6
7
  from .file import *
7
8
  from .model_provider import *
8
9
  from .provider import *
@@ -0,0 +1,337 @@
1
+ # Copyright 2025 © BeeAI a Series of LF Projects, LLC
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from __future__ import annotations
5
+
6
+ import asyncio
7
+ import webbrowser
8
+ from collections.abc import AsyncIterator
9
+ from enum import StrEnum
10
+ from typing import Annotated, Literal
11
+ from uuid import UUID
12
+
13
+ import pydantic
14
+ from pydantic import AnyUrl, BeforeValidator, Field
15
+
16
+ from agentstack_sdk.platform.client import PlatformClient, get_platform_client
17
+ from agentstack_sdk.platform.common import PaginatedResult
18
+ from agentstack_sdk.platform.types import Metadata
19
+
20
+
21
+ def uuid_to_str(v: UUID | str) -> str:
22
+ """Convert UUID or str to str."""
23
+ if isinstance(v, UUID):
24
+ return str(v)
25
+ return v
26
+
27
+
28
+ UuidStr = Annotated[UUID | str, BeforeValidator(uuid_to_str)]
29
+
30
+
31
+ class AuthorizationCodeRequest(pydantic.BaseModel):
32
+ """Authorization request for code-based OAuth flow."""
33
+
34
+ type: Literal["code"] = "code"
35
+ authorization_endpoint: AnyUrl
36
+
37
+
38
+ class ConnectorPreset(pydantic.BaseModel):
39
+ """Represents a preset connector configuration."""
40
+
41
+ url: AnyUrl
42
+ metadata: Metadata | None = None
43
+
44
+
45
+ class ConnectorState(StrEnum):
46
+ """Enumeration of possible connector states."""
47
+
48
+ created = "created"
49
+ auth_required = "auth_required"
50
+ connected = "connected"
51
+ disconnected = "disconnected"
52
+
53
+
54
+ class MCPProxyResponse(pydantic.BaseModel):
55
+ """Response from an MCP proxy request with headers and streaming content."""
56
+
57
+ headers: Annotated[dict[str, str], Field(description="HTTP headers from the proxy response")]
58
+ status_code: Annotated[int, Field(description="HTTP status code from the proxy response")]
59
+ chunk: Annotated[bytes, Field(description="Bytes chunk from streaming response content")]
60
+
61
+
62
+ class Connector(pydantic.BaseModel):
63
+ """Represents a configured connector instance."""
64
+
65
+ id: UUID
66
+ url: AnyUrl
67
+ state: ConnectorState
68
+ auth_request: AuthorizationCodeRequest | None = None
69
+ disconnect_reason: str | None = None
70
+ metadata: Metadata | None = None
71
+ created_at: pydantic.AwareDatetime | None = None
72
+ updated_at: pydantic.AwareDatetime | None = None
73
+ created_by: UUID | None = None
74
+
75
+ @staticmethod
76
+ async def create(
77
+ url: AnyUrl | str,
78
+ *,
79
+ client_id: str | None = None,
80
+ client_secret: str | None = None,
81
+ metadata: Metadata | None = None,
82
+ match_preset: bool = True,
83
+ client: PlatformClient | None = None,
84
+ ) -> Connector:
85
+ """
86
+ Create a new connector.
87
+
88
+ Args:
89
+ url: The URL of the connector/MCP server
90
+ client_id: OAuth client ID (optional)
91
+ client_secret: OAuth client secret (optional)
92
+ metadata: Additional metadata for the connector (optional)
93
+ match_preset: Whether to match against preset connectors
94
+ client: Optional PlatformClient instance
95
+
96
+ Returns:
97
+ The created Connector instance
98
+ """
99
+ async with client or get_platform_client() as client:
100
+ response = await client.post(
101
+ url="/api/v1/connectors",
102
+ json={
103
+ "url": str(url),
104
+ "client_id": client_id,
105
+ "client_secret": client_secret,
106
+ "metadata": metadata,
107
+ "match_preset": match_preset,
108
+ },
109
+ )
110
+ response.raise_for_status()
111
+ return pydantic.TypeAdapter(Connector).validate_python(response.json())
112
+
113
+ @staticmethod
114
+ async def list(
115
+ *,
116
+ client: PlatformClient | None = None,
117
+ ) -> PaginatedResult[Connector]:
118
+ """
119
+ List all connectors for the current user.
120
+
121
+ Returns:
122
+ A paginated list of Connector instances
123
+ """
124
+ async with client or get_platform_client() as client:
125
+ response = await client.get(url="/api/v1/connectors")
126
+ response.raise_for_status()
127
+ return pydantic.TypeAdapter(PaginatedResult[Connector]).validate_python(response.json())
128
+
129
+ async def get(
130
+ self: Connector | UuidStr,
131
+ *,
132
+ client: PlatformClient | None = None,
133
+ ) -> Connector:
134
+ """
135
+ Read a specific connector by ID.
136
+ """
137
+ connector_id = str(self.id) if isinstance(self, Connector) else self
138
+ async with client or get_platform_client() as client:
139
+ response = await client.get(url=f"/api/v1/connectors/{connector_id}")
140
+ response.raise_for_status()
141
+ return pydantic.TypeAdapter(Connector).validate_python(response.json())
142
+
143
+ async def delete(
144
+ self: Connector | UuidStr,
145
+ *,
146
+ client: PlatformClient | None = None,
147
+ ) -> None:
148
+ """
149
+ Delete a connector.
150
+
151
+ Args:
152
+ client: Optional PlatformClient instance
153
+ """
154
+ connector_id = str(self.id) if isinstance(self, Connector) else self
155
+ async with client or get_platform_client() as client:
156
+ response = await client.delete(url=f"/api/v1/connectors/{connector_id}")
157
+ response.raise_for_status()
158
+
159
+ async def refresh(
160
+ self: Connector | UuidStr,
161
+ *,
162
+ client: PlatformClient | None = None,
163
+ ) -> Connector:
164
+ """
165
+ This is just a syntactic sugar for calling Connector.get().
166
+ """
167
+ async with client or get_platform_client() as client:
168
+ return await Connector.get(self, client=client)
169
+
170
+ async def wait_for_state(
171
+ self: Connector | UuidStr,
172
+ *,
173
+ state: ConnectorState = ConnectorState.connected,
174
+ poll_interval: int = 1,
175
+ client: PlatformClient | None = None,
176
+ ) -> Connector:
177
+ """
178
+ Wait for the connector to reach connected state.
179
+
180
+ This is useful after calling connect() and opening the browser for OAuth.
181
+ It will poll the server until the connector reaches 'connected' state or
182
+ timeout is exceeded.
183
+
184
+ Args:
185
+ poll_interval: Seconds between polls (default: 2)
186
+ client: Optional PlatformClient instance
187
+
188
+ Returns:
189
+ Updated Connector instance when connected
190
+
191
+ Raises:
192
+ TimeoutError: If connector doesn't reach connected state within timeout (300 seconds)
193
+ """
194
+ async with client or get_platform_client() as client:
195
+ connector = self if isinstance(self, Connector) else await Connector.get(self, client=client)
196
+
197
+ async with asyncio.timeout(300):
198
+ while connector.state != state:
199
+ await asyncio.sleep(poll_interval)
200
+ connector = await connector.refresh(client=client)
201
+ return connector
202
+
203
+ async def wait_for_deletion(
204
+ self: Connector | UuidStr,
205
+ *,
206
+ poll_interval: int = 1,
207
+ client: PlatformClient | None = None,
208
+ ) -> None:
209
+ connector_id = str(self.id) if isinstance(self, Connector) else self
210
+ async with client or get_platform_client() as client:
211
+ async with asyncio.timeout(30):
212
+ while True:
213
+ connector_list = await Connector.list(client=client)
214
+ if not any(str(conn.id) == connector_id for conn in connector_list.items):
215
+ return
216
+ await asyncio.sleep(poll_interval)
217
+
218
+ async def connect(
219
+ self: Connector | UuidStr,
220
+ *,
221
+ redirect_url: AnyUrl | str | None = None,
222
+ access_token: str | None = None,
223
+ client: PlatformClient | None = None,
224
+ ) -> Connector:
225
+ """
226
+ Connect a connector (establish authorization).
227
+
228
+ If the connector requires OAuth authorization, this will automatically
229
+ open the browser with the authorization endpoint.
230
+
231
+ Args:
232
+ redirect_url: OAuth redirect URL (optional)
233
+ access_token: OAuth access token (optional)
234
+ client: Optional PlatformClient instance
235
+
236
+ Returns:
237
+ The updated Connector instance
238
+ """
239
+ connector_id = str(self.id) if isinstance(self, Connector) else self
240
+ async with client or get_platform_client() as client:
241
+ response = await client.post(
242
+ url=f"/api/v1/connectors/{connector_id}/connect",
243
+ json={
244
+ "redirect_url": str(redirect_url) if redirect_url else None,
245
+ "access_token": access_token,
246
+ },
247
+ )
248
+ response.raise_for_status()
249
+ connector = pydantic.TypeAdapter(Connector).validate_python(response.json())
250
+ # If auth is required, open the browser automatically and returns the connector in
251
+ # `auth_required` state
252
+ if connector.state == ConnectorState.auth_required and connector.auth_request:
253
+ webbrowser.open(connector.auth_request.authorization_endpoint.unicode_string(), new=2)
254
+
255
+ return connector
256
+
257
+ async def disconnect(
258
+ self: Connector | UuidStr,
259
+ *,
260
+ client: PlatformClient | None = None,
261
+ ) -> Connector:
262
+ """
263
+ Disconnect a connector.
264
+
265
+ Args:
266
+ client: Optional PlatformClient instance
267
+
268
+ Returns:
269
+ The updated Connector instance
270
+ """
271
+ connector_id = str(self.id) if isinstance(self, Connector) else self
272
+ async with client or get_platform_client() as client:
273
+ response = await client.post(url=f"/api/v1/connectors/{connector_id}/disconnect")
274
+ response.raise_for_status()
275
+ return pydantic.TypeAdapter(Connector).validate_python(response.json())
276
+
277
+ async def mcp_proxy(
278
+ self: Connector | UuidStr,
279
+ *,
280
+ method: str,
281
+ headers: dict | None = None,
282
+ content: bytes | None = None,
283
+ client: PlatformClient | None = None,
284
+ ) -> AsyncIterator[MCPProxyResponse]:
285
+ """
286
+ Proxy a streaming request through to the connector's MCP endpoint.
287
+
288
+ This allows direct communication with the Model Context Protocol server
289
+ exposed by the connector. The response is streamed to avoid loading
290
+ large responses into memory.
291
+
292
+ Args:
293
+ method: HTTP method (GET, POST, etc.)
294
+ headers: Optional HTTP headers to include
295
+ content: Optional request body content
296
+ client: Optional PlatformClient instance
297
+
298
+ Yields:
299
+ Response content chunks as bytes
300
+ """
301
+ connector_id = str(self.id) if isinstance(self, Connector) else self
302
+ async with client or get_platform_client() as client:
303
+ url = f"/api/v1/connectors/{connector_id}/mcp"
304
+
305
+ # Merge headers - add Content-Type for JSON content if not already set
306
+ request_headers = dict(headers or {})
307
+ if content and "content-type" not in {k.lower() for k in request_headers}:
308
+ request_headers["Content-Type"] = "application/json"
309
+
310
+ # Use streaming to support large/long-lived connections
311
+ async with client.stream(
312
+ method=method.upper(),
313
+ url=url,
314
+ headers=request_headers,
315
+ content=content,
316
+ ) as response:
317
+ response.raise_for_status()
318
+ async for chunk in response.aiter_bytes():
319
+ yield pydantic.TypeAdapter(MCPProxyResponse).validate_python(
320
+ {"headers": dict(response.headers), "status_code": response.status_code, "chunk": chunk}
321
+ )
322
+
323
+ @staticmethod
324
+ async def presets(
325
+ *,
326
+ client: PlatformClient | None = None,
327
+ ) -> PaginatedResult[ConnectorPreset]:
328
+ """
329
+ List all available connector presets.
330
+
331
+ Returns:
332
+ A paginated list of ConnectorPreset instances
333
+ """
334
+ async with client or get_platform_client() as client:
335
+ response = await client.get(url="/api/v1/connectors/presets")
336
+ response.raise_for_status()
337
+ return pydantic.TypeAdapter(PaginatedResult[ConnectorPreset]).validate_python(response.json())
@@ -27,7 +27,6 @@ class User(pydantic.BaseModel):
27
27
  role: UserRole
28
28
  email: str
29
29
  created_at: pydantic.AwareDatetime
30
- role_updated_at: pydantic.AwareDatetime | None = None
31
30
 
32
31
  @staticmethod
33
32
  async def get(*, client: PlatformClient | None = None) -> User:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: agentstack-sdk
3
- Version: 0.5.2rc4
3
+ Version: 0.6.0rc2
4
4
  Summary: Agent Stack SDK
5
5
  Author: IBM Corp.
6
6
  Requires-Dist: a2a-sdk==0.3.21
@@ -14,7 +14,7 @@ agentstack_sdk/a2a/extensions/common/__init__.py,sha256=3lg8P9ASBFHDipP1oZ3IcPqC
14
14
  agentstack_sdk/a2a/extensions/common/form.py,sha256=tbDD3V-ZW90TjXXkyf3SCQd3zJ3tvbJfJA5cbJuPdH0,3792
15
15
  agentstack_sdk/a2a/extensions/exceptions.py,sha256=k4_OFuglnm3XaAr9tl47dNXLLkIhjOQVLohIQ7ieC0M,369
16
16
  agentstack_sdk/a2a/extensions/interactions/__init__.py,sha256=GruhHZaI-iMrmzZhkgtb4KOzlVKgnKMYEylfNjIn6_4,118
17
- agentstack_sdk/a2a/extensions/interactions/approval.py,sha256=DMdiLFEkdgaYyZP-E5pq_tARAn2VNQIIyn_jiZL2RcI,4795
17
+ agentstack_sdk/a2a/extensions/interactions/approval.py,sha256=AD4UhHh7fF-9LcbFL1QkzVYA7mhEPMktx-eI_z1yGVQ,4754
18
18
  agentstack_sdk/a2a/extensions/services/__init__.py,sha256=9njkm58sMs_sMQwKGTMkPU-I7c7T99BWy30fcw4nCoI,201
19
19
  agentstack_sdk/a2a/extensions/services/embedding.py,sha256=GuvbKaOlZSBn04a7SsIlJrQkehEKnZDxQmJCxpVbf_8,3892
20
20
  agentstack_sdk/a2a/extensions/services/form.py,sha256=BmZ-Cqwp4o-j1zgCneuZ_9-1vCOm1OkQMJZLc_Rp1qc,1707
@@ -33,17 +33,18 @@ agentstack_sdk/a2a/extensions/ui/form_request.py,sha256=YeRiovV3EgqeBD8fHtu_b5qr
33
33
  agentstack_sdk/a2a/extensions/ui/settings.py,sha256=uZu_8u29gAu1H85twFb_LCRBO0jeaEiLgfamaFNUPGM,1814
34
34
  agentstack_sdk/a2a/extensions/ui/trajectory.py,sha256=G66rIpr2VTe1UvbK82EgLXwFtUC3QfbyN_uLQGUU8jA,2334
35
35
  agentstack_sdk/a2a/types.py,sha256=z1-OxRLIJgYDHwaBJ66MZNIqSTpE1zQ04BPGBGw9IC0,3582
36
- agentstack_sdk/platform/__init__.py,sha256=jFlxr6P-6DJZVt1W8Xt1suISJpodD_EWX92GmcFLXwM,326
36
+ agentstack_sdk/platform/__init__.py,sha256=2yu0t7mtMPv14MWk1PFiU5t6xaZf9n4VhY6moKjdNFw,351
37
37
  agentstack_sdk/platform/client.py,sha256=KDN3R1wyoo-ZUYl6ArjRJxre0yAqB0Y6NyKrQpdI268,4234
38
38
  agentstack_sdk/platform/common.py,sha256=p6w4l49NaFp7LsjEBDAwghX6FliNChOeO7IHP_-TnXA,750
39
39
  agentstack_sdk/platform/configuration.py,sha256=YfOasb6LxE9LN6sMZPEU07Q61F7uIhiU3DIQFRaqinU,1690
40
+ agentstack_sdk/platform/connector.py,sha256=I3IgZCqbrZ0YXHFkFD-JSYihIahZHGBtQcXfN4cvEFc,12060
40
41
  agentstack_sdk/platform/context.py,sha256=vYAQOgZUEelvhP4Sldzt4Y1CP4WVtEE42DSuAMGnn-0,12099
41
42
  agentstack_sdk/platform/file.py,sha256=lOqMwMKHKGWFy5P1huQ6DLRTiHU0KexX-s2qF3FIRxw,13062
42
43
  agentstack_sdk/platform/model_provider.py,sha256=hv5UbFUFQ23IlalH5W-nCTvnUCrxBtZF4UrkeNWnwFE,4588
43
44
  agentstack_sdk/platform/provider.py,sha256=1juAfYc39sVwZ8ZbYz1ohVnzzRacJx6Junvo-7GgVmY,9071
44
45
  agentstack_sdk/platform/provider_build.py,sha256=Vn2XDdMGgQWvChWJmg9nxYFVecp42A1jXzURkFgSEQQ,7317
45
46
  agentstack_sdk/platform/types.py,sha256=3OHERUoli6t9rlCiaZ2YfN26GXqMQzDFQx_UluCZVYA,1502
46
- agentstack_sdk/platform/user.py,sha256=ZxTPJYPGxcRtRQJpxPPf2ylz3Erngu2vSMuia1oJ04Q,2148
47
+ agentstack_sdk/platform/user.py,sha256=pqkac4JZqagOYVvdm8mk48xHvVn6SPq2tbe5tXx_W4M,2090
47
48
  agentstack_sdk/platform/user_feedback.py,sha256=IfMzZsaFKpWCPz5MjppDsFLLMM-iVQUBGugz0_FmrqY,1305
48
49
  agentstack_sdk/platform/variables.py,sha256=VW7eHO_V6B5I-vvqNgUangkcuUXDcjFIrxUSSfX5cHw,1637
49
50
  agentstack_sdk/platform/vector_store.py,sha256=h98RKGuN1SvmfL_bxf3WCLvY1IeQ00Wf7NTP6yr05qg,8695
@@ -71,6 +72,6 @@ agentstack_sdk/util/httpx.py,sha256=g6WKgkGCLc40wZA2CPYO_R2P-nav6QP1XmVlgkh9wYY,
71
72
  agentstack_sdk/util/logging.py,sha256=hGLkjw-P3GefiUQmDmcz7BZQxfT2Y44XlLyAMNxZXMo,2136
72
73
  agentstack_sdk/util/resource_context.py,sha256=OmjEXvrLQA6nBkVSBt0n24hNNxJkucd-N32haxJ4Mno,1093
73
74
  agentstack_sdk/util/utils.py,sha256=18qFqMRkX4g4eIpvIvLb4FZAs8Q8ojJrpm19oJleb-k,1593
74
- agentstack_sdk-0.5.2rc4.dist-info/WHEEL,sha256=M6du7VZflc4UPsGphmOXHANdgk8zessdJG0DBUuoA-U,78
75
- agentstack_sdk-0.5.2rc4.dist-info/METADATA,sha256=qJ5Qa_bZ2UC4A-EAY5NqtWk5-B-iuFTgOZv7X4dmRlA,4433
76
- agentstack_sdk-0.5.2rc4.dist-info/RECORD,,
75
+ agentstack_sdk-0.6.0rc2.dist-info/WHEEL,sha256=M6du7VZflc4UPsGphmOXHANdgk8zessdJG0DBUuoA-U,78
76
+ agentstack_sdk-0.6.0rc2.dist-info/METADATA,sha256=0-Tqa6hadZ7xiYobmfINCfIQK-xS22d-X4381G1Y9C4,4433
77
+ agentstack_sdk-0.6.0rc2.dist-info/RECORD,,