managed-deepagents 0.6.2.dev29__py3-none-win_amd64.whl → 0.6.2.dev30__py3-none-win_amd64.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.
@@ -9,18 +9,18 @@ This package provides two things:
9
9
 
10
10
  Resources use ``define_*`` factories while provider collections use namespaces:
11
11
  ``define_sandbox()``, ``channels.slack(...)``, ``connectors.mcp(...)``,
12
- ``connections.get(...)``, and ``auth.supabase(...)``. Contributing a *new*
12
+ ``credentials.get(...)``, and ``auth.supabase(...)``. Contributing a *new*
13
13
  primitive is not part of the public surface.
14
14
  """
15
15
 
16
16
  from __future__ import annotations
17
17
 
18
- from managed_deepagents._connections import (
19
- ConnectionNotFoundError,
18
+ from managed_deepagents._credentials import (
20
19
  CredentialAuthorizationRequiredError,
20
+ CredentialNotFoundError,
21
+ CredentialOwner,
21
22
  CredentialStoreError,
22
- InvalidAgentAuthEndpointError,
23
- connections,
23
+ credentials,
24
24
  )
25
25
  from managed_deepagents._managed_tools import (
26
26
  ManagedDeepAgentRuntime,
@@ -38,19 +38,19 @@ from managed_deepagents.sandbox import SandboxDefinition
38
38
  from managed_deepagents.schedules import define_schedule
39
39
 
40
40
  __all__ = [
41
- "ConnectionNotFoundError",
42
41
  "CredentialAuthorizationRequiredError",
42
+ "CredentialNotFoundError",
43
+ "CredentialOwner",
43
44
  "CredentialStoreError",
44
45
  "DeepAgentDefinition",
45
- "InvalidAgentAuthEndpointError",
46
46
  "ManagedDeepAgentRuntime",
47
47
  "ManagedServerInfo",
48
48
  "MemoryDefinition",
49
49
  "SandboxDefinition",
50
50
  "auth",
51
51
  "channels",
52
- "connections",
53
52
  "connectors",
53
+ "credentials",
54
54
  "define_deep_agent",
55
55
  "define_identity",
56
56
  "define_memory",
@@ -1,4 +1,4 @@
1
- """Agent Auth client used by ``connections.get`` and the pre-run credential gate.
1
+ """Agent Auth client used by ``credentials.get`` and the pre-run credential gate.
2
2
 
3
3
  Talks to the shipped Agent Auth APIs (credentials + oauth-authorization-sessions),
4
4
  not the aspirational ``credentials/mint`` endpoint.
@@ -33,7 +33,6 @@ __all__ = [
33
33
  "AgentAuthUnknownProviderError",
34
34
  "CredentialAuthorizationRequiredError",
35
35
  "PendingAuthorization",
36
- "WorkspaceConnection",
37
36
  ]
38
37
 
39
38
 
@@ -46,29 +45,20 @@ class AgentAuthContext:
46
45
  principal_id: str
47
46
  api_key: str | None = None
48
47
  workspace_id: str | None = None
49
- #: Agent Builder service JWT for workspace-owned credential material.
50
- service_key: str | None = None
51
48
 
52
49
 
53
50
  class PendingAuthorization(TypedDict):
54
- """One connection slug the caller has not granted, with its connect URL."""
51
+ """One credential slug the caller has not granted, with its connect URL."""
55
52
 
56
53
  slug: str
57
54
  connect_url: str
58
55
  auth_id: str
59
56
 
60
57
 
61
- @dataclass(frozen=True)
62
- class WorkspaceConnection:
63
- connection_id: str
64
- kind: str
65
- owner_policy: str
66
-
67
-
68
58
  class CredentialAuthorizationRequiredError(RuntimeError):
69
59
  def __init__(self, slugs: list[str]) -> None:
70
60
  super().__init__(
71
- f"the caller has not connected: {', '.join(slugs)}. Declare the connection "
61
+ f"the caller has not connected: {', '.join(slugs)}. Declare the credential "
72
62
  "on a connector so the pre-run gate can request it."
73
63
  )
74
64
  self.slugs = slugs
@@ -116,124 +106,6 @@ class AgentAuth:
116
106
  self._timeout = timeout
117
107
  self._request: AgentAuthRequest = request if request is not None else self._urlopen_json
118
108
 
119
- def find_workspace_connection(self, slug: str) -> WorkspaceConnection | None:
120
- """List workspace connections and return the row for ``slug``, if any."""
121
- if not (self.context.workspace_id or "").strip():
122
- raise CredentialStoreError(
123
- "connections are not configured: LANGSMITH_WORKSPACE_ID is not set"
124
- )
125
- cursor: str | None = None
126
- for _ in range(AGENT_AUTH_MAX_PROVIDER_PAGES):
127
- query: dict[str, str] = {
128
- "page_size": str(AGENT_AUTH_PROVIDER_PAGE_SIZE),
129
- }
130
- if cursor:
131
- query["cursor"] = cursor
132
- status, body = self._request(
133
- "GET",
134
- f"/v1/agent-auth/connections?{urllib.parse.urlencode(query)}",
135
- )
136
- if status >= _HTTP_BAD_REQUEST:
137
- raise CredentialStoreError("Agent Auth list connections failed", status)
138
- raw_items = body.get("items")
139
- items = raw_items if isinstance(raw_items, list) else []
140
- for item in items:
141
- if not isinstance(item, Mapping) or item.get("slug") != slug:
142
- continue
143
- connection_id = (
144
- item.get("id") or item.get("connection_id") or item.get("credential_id")
145
- )
146
- kind = _connection_credential_kind(item)
147
- owner_policy = item.get("owner_policy")
148
- if not isinstance(connection_id, str) or not connection_id:
149
- raise CredentialStoreError("Agent Auth connection row missing id or kind")
150
- if not isinstance(kind, str) or not kind:
151
- raise CredentialStoreError("Agent Auth connection row missing id or kind")
152
- return WorkspaceConnection(
153
- connection_id=connection_id,
154
- kind=kind,
155
- owner_policy=str(owner_policy) if isinstance(owner_policy, str) else "",
156
- )
157
- next_cursor = body.get("next_cursor")
158
- if not isinstance(next_cursor, str) or not next_cursor or next_cursor == cursor:
159
- return None
160
- cursor = next_cursor
161
- raise CredentialStoreError("Agent Auth connection list returned too many pages")
162
-
163
- def find_workspace_credential_id(self, slug: str) -> str | None:
164
- """Workspace credential id for an agent-owned opaque secret grant."""
165
- connection = self.find_workspace_connection(slug)
166
- if connection is None:
167
- return None
168
- return self.find_secret_credential_id(connection, slug)
169
-
170
- def find_secret_credential_id(self, connection: WorkspaceConnection, slug: str) -> str | None:
171
- """Resolve the credential id for a secret connection row.
172
-
173
- With an Agent Builder service key, prefer GET-for-owner / workspace list.
174
- Personal API keys may only list agent-owned credentials.
175
- """
176
- if connection.kind != "secret":
177
- return None
178
- workspace_id = (self.context.workspace_id or "").strip()
179
- if not workspace_id:
180
- raise CredentialStoreError(
181
- "connections are not configured: LANGSMITH_WORKSPACE_ID is not set"
182
- )
183
- if (self.context.service_key or "").strip():
184
- from_owner = self._credential_id_for_owner(
185
- connection.connection_id, "workspace", workspace_id
186
- )
187
- if from_owner is not None:
188
- return from_owner
189
- return self.find_credential_id(
190
- owner_type="workspace",
191
- owner_id=workspace_id,
192
- kind="secret",
193
- slug=slug,
194
- )
195
- return self.find_credential_id(
196
- owner_type="agent",
197
- owner_id=self.context.agent_id,
198
- kind="secret",
199
- slug=slug,
200
- )
201
-
202
- def _credential_id_for_owner(
203
- self, connection_id: str, owner_type: str, owner_id: str
204
- ) -> str | None:
205
- query = urllib.parse.urlencode(
206
- {
207
- "credential_owner_type": owner_type,
208
- "credential_owner_id": owner_id,
209
- }
210
- )
211
- status, body = self._request(
212
- "GET",
213
- f"/v1/agent-auth/connections/{_path_segment(connection_id)}?{query}",
214
- )
215
- if status == _HTTP_NOT_FOUND:
216
- return None
217
- if status >= _HTTP_BAD_REQUEST:
218
- raise CredentialStoreError("Agent Auth get connection for owner failed", status)
219
- return _connection_credential_id(body)
220
-
221
- def read_user_secret(self, slug: str) -> str | None:
222
- """Read a caller-owned opaque secret grant for a workspace connection slug."""
223
- credential_id = self.find_credential_id(
224
- owner_type="user",
225
- owner_id=self.context.principal_id,
226
- kind="secret",
227
- slug=slug,
228
- )
229
- if credential_id is None:
230
- return None
231
- return self.read_secret(credential_id)
232
-
233
- def read_connection_access_token(self, connection_id: str) -> str | None:
234
- """Read an OAuth access token stored on a workspace connection row."""
235
- return self._fetch_access_token(connection_id)
236
-
237
109
  def read_user_access_token(self, slug: str) -> str | None:
238
110
  """Read the caller's OAuth access token without starting a session.
239
111
 
@@ -256,16 +128,12 @@ class AgentAuth:
256
128
 
257
129
  def start_authorization_session(self, slug: str) -> PendingAuthorization:
258
130
  """Start an OAuth session for a slug the caller has not granted."""
259
- connection = self.find_workspace_connection(slug)
260
- if connection is None:
261
- raise CredentialStoreError(f"workspace connection {slug!r} is not configured")
262
- if connection.kind != "oauth2":
263
- raise CredentialStoreError(f"connection {slug!r} is not an OAuth connection")
131
+ provider_id = self.resolve_oauth_provider_id(slug)
264
132
  status, body = self._request(
265
133
  "POST",
266
134
  "/v1/agent-auth/oauth-authorization-sessions",
267
135
  body={
268
- "connection_id": connection.connection_id,
136
+ "provider_id": provider_id,
269
137
  "principal_id": self.context.principal_id,
270
138
  "agent_id": self.context.agent_id,
271
139
  },
@@ -314,35 +182,18 @@ class AgentAuth:
314
182
  ) -> str | None:
315
183
  """Find a credential id by slug.
316
184
 
317
- The shipped credentials list filters by owner and kind only, so the
318
- slug match is made client-side across pages. The credential key is
319
- ``credential_id``, never ``id``.
185
+ The credential key is ``credential_id``, never ``id``.
320
186
  """
321
- cursor: str | None = None
322
- for _ in range(AGENT_AUTH_MAX_PROVIDER_PAGES):
323
- query = agent_auth_credential_query(
324
- owner_type=owner_type,
325
- owner_id=owner_id,
326
- kind=kind,
327
- page_size=AGENT_AUTH_PROVIDER_PAGE_SIZE,
328
- cursor=cursor,
329
- )
330
- status, body = self._request("GET", f"/v1/agent-auth/credentials?{query}")
331
- if status >= _HTTP_BAD_REQUEST:
332
- raise CredentialStoreError("Agent Auth list credentials failed", status)
333
- raw_items = body.get("items")
334
- items = raw_items if isinstance(raw_items, list) else []
335
- for item in items:
336
- if not isinstance(item, Mapping) or item.get("slug") != slug:
337
- continue
338
- credential_id = item.get("credential_id")
339
- if isinstance(credential_id, str) and credential_id:
340
- return credential_id
341
- next_cursor = body.get("next_cursor")
342
- if not isinstance(next_cursor, str) or not next_cursor or next_cursor == cursor:
343
- return None
344
- cursor = next_cursor
345
- raise CredentialStoreError("Agent Auth credential list returned too many pages")
187
+ query = agent_auth_credential_query(
188
+ owner_type=owner_type, owner_id=owner_id, kind=kind, slug=slug
189
+ )
190
+ status, body = self._request("GET", f"/v1/agent-auth/credentials?{query}")
191
+ if status >= _HTTP_BAD_REQUEST:
192
+ raise CredentialStoreError("Agent Auth list credentials failed", status)
193
+ items = body.get("items")
194
+ row = items[0] if isinstance(items, list) and items else None
195
+ credential_id = row.get("credential_id") if isinstance(row, Mapping) else None
196
+ return credential_id if isinstance(credential_id, str) else None
346
197
 
347
198
  def read_secret(self, credential_id: str) -> str | None:
348
199
  status, body = self._request(
@@ -390,7 +241,6 @@ class AgentAuth:
390
241
  return agent_auth_headers(
391
242
  api_key=self.context.api_key,
392
243
  workspace_id=self.context.workspace_id,
393
- service_key=self.context.service_key,
394
244
  )
395
245
 
396
246
 
@@ -398,24 +248,6 @@ def _path_segment(value: str) -> str:
398
248
  return urllib.parse.quote(value, safe="")
399
249
 
400
250
 
401
- def _connection_credential_kind(item: Mapping[str, object]) -> str:
402
- credential = item.get("credential")
403
- if isinstance(credential, Mapping):
404
- kind = credential.get("kind")
405
- if isinstance(kind, str) and kind:
406
- return kind
407
- kind = item.get("kind")
408
- return kind if isinstance(kind, str) else ""
409
-
410
-
411
- def _connection_credential_id(body: Mapping[str, object]) -> str | None:
412
- credential = body.get("credential")
413
- if not isinstance(credential, Mapping):
414
- return None
415
- credential_id = credential.get("credential_id")
416
- return credential_id if isinstance(credential_id, str) and credential_id else None
417
-
418
-
419
251
  def _is_authorization_required(status: int, body: dict[str, object]) -> bool:
420
252
  """Agent Auth answers 409 when a stored grant has lapsed and needs re-auth."""
421
253
  if status != _HTTP_CONFLICT:
@@ -46,40 +46,24 @@ def agent_auth_credential_query(
46
46
  owner_type: str,
47
47
  owner_id: str,
48
48
  kind: str,
49
- page_size: int | None = None,
50
- cursor: str | None = None,
49
+ slug: str,
51
50
  ) -> str:
52
- """Query string for listing credentials filtered by owner and kind.
53
-
54
- The shipped list endpoint does not filter by slug; callers match ``slug``
55
- client-side across pages.
56
- """
57
- params: dict[str, str] = {
58
- "owner_type": owner_type,
59
- "owner_id": owner_id,
60
- "kind": kind,
61
- }
62
- if page_size is not None:
63
- params["page_size"] = str(page_size)
64
- if cursor:
65
- params["cursor"] = cursor
66
- return urllib.parse.urlencode(params)
51
+ """Query string that addresses one credential by its natural key."""
52
+ return urllib.parse.urlencode(
53
+ {"owner_type": owner_type, "owner_id": owner_id, "kind": kind, "slug": slug}
54
+ )
67
55
 
68
56
 
69
57
  def agent_auth_headers(
70
58
  *,
71
59
  api_key: str | None = None,
72
60
  workspace_id: str | None = None,
73
- service_key: str | None = None,
74
61
  ) -> dict[str, str]:
75
62
  headers = {
76
63
  "accept": "application/json",
77
64
  "content-type": "application/json",
78
65
  "x-langsmith-product": "mda",
79
66
  }
80
- resolved_service_key = (service_key or "").strip()
81
- if resolved_service_key:
82
- headers["x-service-key"] = resolved_service_key
83
67
  resolved_api_key = (api_key or "").strip()
84
68
  if resolved_api_key:
85
69
  headers["x-api-key"] = resolved_api_key
Binary file
@@ -17,8 +17,6 @@ JsonObject = dict[str, JsonValue]
17
17
  class _OptionalChannelManifest(TypedDict, total=False):
18
18
  transport: Literal["trigger_server"]
19
19
  userCredentials: list[str]
20
- userConnections: list[str]
21
- agentConnections: list[str]
22
20
 
23
21
 
24
22
  class ChannelManifest(_OptionalChannelManifest):
@@ -250,7 +250,7 @@ async def _observe_accepted_run(
250
250
  event["channel_id"],
251
251
  {
252
252
  "type": "post",
253
- "action_id": event["event_id"],
253
+ "action_id": str(uuid.uuid4()),
254
254
  "address": event["address"],
255
255
  "source_thread_key": event["source_thread_key"],
256
256
  "message": {"text": text},
@@ -9,7 +9,7 @@ name, before any managed ``{server}__`` prefix is applied.
9
9
  Example::
10
10
 
11
11
  # connectors/mcp.py
12
- from managed_deepagents import connectors, connections
12
+ from managed_deepagents import connectors, credentials
13
13
 
14
14
  connector = connectors.mcp(
15
15
  use_standard_content_blocks=True,
@@ -24,7 +24,7 @@ Example::
24
24
  "notion": {
25
25
  "transport": "http",
26
26
  "url": "https://mcp.notion.com/mcp",
27
- "connection": connections.get("notion", {"type": "user"}),
27
+ "credential": credentials.get("notion", {"type": "user"}),
28
28
  },
29
29
  },
30
30
  )
@@ -33,7 +33,6 @@ Example::
33
33
  from __future__ import annotations
34
34
 
35
35
  import math
36
- import warnings
37
36
  from collections.abc import Mapping, Sequence
38
37
  from typing import TYPE_CHECKING, Annotated, Any, Literal, TypedDict, cast
39
38
 
@@ -46,9 +45,9 @@ from pydantic import (
46
45
  model_validator,
47
46
  )
48
47
 
49
- from managed_deepagents._connections import ConnectionReference, is_connection_reference
50
48
  from managed_deepagents._connector import Connector
51
49
  from managed_deepagents._connectors.mcp.load import load_managed_mcp_tools
50
+ from managed_deepagents._credentials import CredentialReference
52
51
  from managed_deepagents._extension import define_extension
53
52
 
54
53
  if TYPE_CHECKING:
@@ -63,9 +62,9 @@ class McpToolSelectionConfig(TypedDict, total=False):
63
62
  class RemoteMcpServerBaseConfig(McpToolSelectionConfig, total=False):
64
63
  url: str
65
64
  headers: dict[str, str]
66
- #: Bearer connection: an agent owner is a deployment-wide secret read once
67
- #: at load, a user owner is the caller's grant loaded after the gate.
68
- connection: ConnectionReference
65
+ #: Bearer credential: an agent owner is a deployment-wide secret read once
66
+ #: at load, a user owner is the caller's OAuth grant loaded after the gate.
67
+ credential: CredentialReference
69
68
  #: Default timeout in seconds for tools loaded from this server.
70
69
  default_tool_timeout: float
71
70
 
@@ -124,26 +123,20 @@ class McpServersDefinition(Connector):
124
123
  return load_managed_mcp_tools(self)
125
124
 
126
125
  def requirements(self, name: str) -> ChannelManifest:
127
- """Describe user and agent connections that deploy must validate."""
128
- user_slugs: set[str] = set()
129
- agent_slugs: set[str] = set()
130
- for server in self.config["mcp_servers"].values():
131
- connection = server.get("connection") if isinstance(server, dict) else None
132
- if not is_connection_reference(connection):
133
- continue
134
- if connection.owner["type"] == "user":
135
- user_slugs.add(connection.slug)
136
- else:
137
- agent_slugs.add(connection.slug)
126
+ """Describe user credentials that deploy must validate."""
127
+ slugs = {
128
+ credential.slug
129
+ for server in self.config["mcp_servers"].values()
130
+ if isinstance((credential := server.get("credential")), CredentialReference)
131
+ and credential.owner["type"] == "user"
132
+ }
138
133
  return {
139
134
  "name": name,
140
135
  "provider": "mcp",
141
136
  "autoReply": False,
142
137
  "requiredPermissions": [],
143
138
  "requiredEnv": [],
144
- "userConnections": sorted(user_slugs),
145
- "agentConnections": sorted(agent_slugs),
146
- "userCredentials": sorted(user_slugs),
139
+ "userCredentials": sorted(slugs),
147
140
  "providerConfig": {},
148
141
  }
149
142
 
@@ -159,12 +152,6 @@ _STDIO_REJECTION = (
159
152
  "HTTP or write a normal authored tool instead."
160
153
  )
161
154
 
162
- _CREDENTIAL_FIELD_DEPRECATION = (
163
- 'the "credential" MCP server field is deprecated; use "connection" instead'
164
- )
165
-
166
- _CREDENTIAL_FIELD_CONFLICT = 'use either "connection" or "credential", not both'
167
-
168
155
 
169
156
  def _non_empty_tool_name(name: str) -> str:
170
157
  if not name.strip():
@@ -197,16 +184,7 @@ class _RemoteMcpServer(BaseModel):
197
184
  default_tool_timeout: float | None = None
198
185
  include_tools: list[_ToolName] | None = None
199
186
  exclude_tools: list[_ToolName] | None = None
200
- connection: ConnectionReference | None = None
201
-
202
- @field_validator("connection")
203
- @classmethod
204
- def _valid_connection(
205
- cls, connection: ConnectionReference | None
206
- ) -> ConnectionReference | None:
207
- if connection is not None and not is_connection_reference(connection):
208
- raise ValueError("must be a connections.get(slug, owner) reference")
209
- return connection
187
+ credential: CredentialReference | None = None
210
188
 
211
189
  @field_validator("default_tool_timeout")
212
190
  @classmethod
@@ -217,19 +195,12 @@ class _RemoteMcpServer(BaseModel):
217
195
 
218
196
  @model_validator(mode="before")
219
197
  @classmethod
220
- def _normalize_legacy_fields(cls, server: Any) -> Any:
221
- if not isinstance(server, Mapping):
222
- return server
223
- if server.get("transport") == "stdio" or "command" in server:
198
+ def _reject_stdio(cls, server: Any) -> Any:
199
+ if isinstance(server, Mapping) and (
200
+ server.get("transport") == "stdio" or "command" in server
201
+ ):
224
202
  raise ValueError(_STDIO_REJECTION)
225
- if "credential" not in server:
226
- return server
227
- if "connection" in server:
228
- raise ValueError(_CREDENTIAL_FIELD_CONFLICT)
229
- warnings.warn(_CREDENTIAL_FIELD_DEPRECATION, DeprecationWarning, stacklevel=1)
230
- normalized = dict(server)
231
- normalized["connection"] = normalized.pop("credential")
232
- return normalized
203
+ return server
233
204
 
234
205
  @field_validator("transport", mode="before")
235
206
  @classmethod