managed-deepagents 0.6.2.dev20__py3-none-win_amd64.whl → 0.6.2.dev22__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,17 @@ 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
- ``credentials.get(...)``, and ``auth.supabase(...)``. Contributing a *new*
12
+ ``connections.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._credentials import (
18
+ from managed_deepagents._connections import (
19
+ ConnectionNotFoundError,
19
20
  CredentialAuthorizationRequiredError,
20
- CredentialNotFoundError,
21
- CredentialOwner,
22
21
  CredentialStoreError,
23
- credentials,
22
+ connections,
24
23
  )
25
24
  from managed_deepagents._managed_tools import (
26
25
  ManagedDeepAgentRuntime,
@@ -38,9 +37,8 @@ from managed_deepagents.sandbox import SandboxDefinition
38
37
  from managed_deepagents.schedules import define_schedule
39
38
 
40
39
  __all__ = [
40
+ "ConnectionNotFoundError",
41
41
  "CredentialAuthorizationRequiredError",
42
- "CredentialNotFoundError",
43
- "CredentialOwner",
44
42
  "CredentialStoreError",
45
43
  "DeepAgentDefinition",
46
44
  "ManagedDeepAgentRuntime",
@@ -49,8 +47,8 @@ __all__ = [
49
47
  "SandboxDefinition",
50
48
  "auth",
51
49
  "channels",
50
+ "connections",
52
51
  "connectors",
53
- "credentials",
54
52
  "define_deep_agent",
55
53
  "define_identity",
56
54
  "define_memory",
@@ -1,4 +1,4 @@
1
- """Agent Auth client used by ``credentials.get`` and the pre-run credential gate.
1
+ """Agent Auth client used by ``connections.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,6 +33,7 @@ __all__ = [
33
33
  "AgentAuthUnknownProviderError",
34
34
  "CredentialAuthorizationRequiredError",
35
35
  "PendingAuthorization",
36
+ "WorkspaceConnection",
36
37
  ]
37
38
 
38
39
 
@@ -48,17 +49,24 @@ class AgentAuthContext:
48
49
 
49
50
 
50
51
  class PendingAuthorization(TypedDict):
51
- """One credential slug the caller has not granted, with its connect URL."""
52
+ """One connection slug the caller has not granted, with its connect URL."""
52
53
 
53
54
  slug: str
54
55
  connect_url: str
55
56
  auth_id: str
56
57
 
57
58
 
59
+ @dataclass(frozen=True)
60
+ class WorkspaceConnection:
61
+ connection_id: str
62
+ kind: str
63
+ owner_policy: str
64
+
65
+
58
66
  class CredentialAuthorizationRequiredError(RuntimeError):
59
67
  def __init__(self, slugs: list[str]) -> None:
60
68
  super().__init__(
61
- f"the caller has not connected: {', '.join(slugs)}. Declare the credential "
69
+ f"the caller has not connected: {', '.join(slugs)}. Declare the connection "
62
70
  "on a connector so the pre-run gate can request it."
63
71
  )
64
72
  self.slugs = slugs
@@ -106,6 +114,80 @@ class AgentAuth:
106
114
  self._timeout = timeout
107
115
  self._request: AgentAuthRequest = request if request is not None else self._urlopen_json
108
116
 
117
+ def find_workspace_connection(self, slug: str) -> WorkspaceConnection | None:
118
+ """List workspace connections and return the row for ``slug``, if any."""
119
+ if not (self.context.workspace_id or "").strip():
120
+ raise CredentialStoreError(
121
+ "connections are not configured: LANGSMITH_WORKSPACE_ID is not set"
122
+ )
123
+ cursor: str | None = None
124
+ for _ in range(AGENT_AUTH_MAX_PROVIDER_PAGES):
125
+ query: dict[str, str] = {
126
+ "page_size": str(AGENT_AUTH_PROVIDER_PAGE_SIZE),
127
+ }
128
+ if cursor:
129
+ query["cursor"] = cursor
130
+ status, body = self._request(
131
+ "GET",
132
+ f"/v1/agent-auth/connections?{urllib.parse.urlencode(query)}",
133
+ )
134
+ if status >= _HTTP_BAD_REQUEST:
135
+ raise CredentialStoreError("Agent Auth list connections failed", status)
136
+ raw_items = body.get("items")
137
+ items = raw_items if isinstance(raw_items, list) else []
138
+ for item in items:
139
+ if not isinstance(item, Mapping) or item.get("slug") != slug:
140
+ continue
141
+ connection_id = (
142
+ item.get("id") or item.get("connection_id") or item.get("credential_id")
143
+ )
144
+ kind = item.get("kind")
145
+ owner_policy = item.get("owner_policy")
146
+ if not isinstance(connection_id, str) or not connection_id:
147
+ raise CredentialStoreError("Agent Auth connection row missing id or kind")
148
+ if not isinstance(kind, str) or not kind:
149
+ raise CredentialStoreError("Agent Auth connection row missing id or kind")
150
+ return WorkspaceConnection(
151
+ connection_id=connection_id,
152
+ kind=kind,
153
+ owner_policy=str(owner_policy) if isinstance(owner_policy, str) else "",
154
+ )
155
+ next_cursor = body.get("next_cursor")
156
+ if not isinstance(next_cursor, str) or not next_cursor or next_cursor == cursor:
157
+ return None
158
+ cursor = next_cursor
159
+ raise CredentialStoreError("Agent Auth connection list returned too many pages")
160
+
161
+ def find_workspace_credential_id(self, slug: str) -> str | None:
162
+ """Workspace credential id for an agent-owned opaque secret grant."""
163
+ workspace_id = (self.context.workspace_id or "").strip()
164
+ if not workspace_id:
165
+ raise CredentialStoreError(
166
+ "connections are not configured: LANGSMITH_WORKSPACE_ID is not set"
167
+ )
168
+ return self.find_credential_id(
169
+ owner_type="workspace",
170
+ owner_id=workspace_id,
171
+ kind="secret",
172
+ slug=slug,
173
+ )
174
+
175
+ def read_user_secret(self, slug: str) -> str | None:
176
+ """Read a caller-owned opaque secret grant for a workspace connection slug."""
177
+ credential_id = self.find_credential_id(
178
+ owner_type="user",
179
+ owner_id=self.context.principal_id,
180
+ kind="secret",
181
+ slug=slug,
182
+ )
183
+ if credential_id is None:
184
+ return None
185
+ return self.read_secret(credential_id)
186
+
187
+ def read_connection_access_token(self, connection_id: str) -> str | None:
188
+ """Read an OAuth access token stored on a workspace connection row."""
189
+ return self._fetch_access_token(connection_id)
190
+
109
191
  def read_user_access_token(self, slug: str) -> str | None:
110
192
  """Read the caller's OAuth access token without starting a session.
111
193
 
@@ -128,12 +210,16 @@ class AgentAuth:
128
210
 
129
211
  def start_authorization_session(self, slug: str) -> PendingAuthorization:
130
212
  """Start an OAuth session for a slug the caller has not granted."""
131
- provider_id = self.resolve_oauth_provider_id(slug)
213
+ connection = self.find_workspace_connection(slug)
214
+ if connection is None:
215
+ raise CredentialStoreError(f"workspace connection {slug!r} is not configured")
216
+ if connection.kind != "oauth2":
217
+ raise CredentialStoreError(f"connection {slug!r} is not an OAuth connection")
132
218
  status, body = self._request(
133
219
  "POST",
134
220
  "/v1/agent-auth/oauth-authorization-sessions",
135
221
  body={
136
- "provider_id": provider_id,
222
+ "connection_id": connection.connection_id,
137
223
  "principal_id": self.context.principal_id,
138
224
  "agent_id": self.context.agent_id,
139
225
  },
@@ -182,18 +268,35 @@ class AgentAuth:
182
268
  ) -> str | None:
183
269
  """Find a credential id by slug.
184
270
 
185
- The credential key is ``credential_id``, never ``id``.
271
+ The shipped credentials list filters by owner and kind only, so the
272
+ slug match is made client-side across pages. The credential key is
273
+ ``credential_id``, never ``id``.
186
274
  """
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
275
+ cursor: str | None = None
276
+ for _ in range(AGENT_AUTH_MAX_PROVIDER_PAGES):
277
+ query = agent_auth_credential_query(
278
+ owner_type=owner_type,
279
+ owner_id=owner_id,
280
+ kind=kind,
281
+ page_size=AGENT_AUTH_PROVIDER_PAGE_SIZE,
282
+ cursor=cursor,
283
+ )
284
+ status, body = self._request("GET", f"/v1/agent-auth/credentials?{query}")
285
+ if status >= _HTTP_BAD_REQUEST:
286
+ raise CredentialStoreError("Agent Auth list credentials failed", status)
287
+ raw_items = body.get("items")
288
+ items = raw_items if isinstance(raw_items, list) else []
289
+ for item in items:
290
+ if not isinstance(item, Mapping) or item.get("slug") != slug:
291
+ continue
292
+ credential_id = item.get("credential_id")
293
+ if isinstance(credential_id, str) and credential_id:
294
+ return credential_id
295
+ next_cursor = body.get("next_cursor")
296
+ if not isinstance(next_cursor, str) or not next_cursor or next_cursor == cursor:
297
+ return None
298
+ cursor = next_cursor
299
+ raise CredentialStoreError("Agent Auth credential list returned too many pages")
197
300
 
198
301
  def read_secret(self, credential_id: str) -> str | None:
199
302
  status, body = self._request(
@@ -46,12 +46,24 @@ def agent_auth_credential_query(
46
46
  owner_type: str,
47
47
  owner_id: str,
48
48
  kind: str,
49
- slug: str,
49
+ page_size: int | None = None,
50
+ cursor: str | None = None,
50
51
  ) -> str:
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
- )
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)
55
67
 
56
68
 
57
69
  def agent_auth_headers(
Binary file
@@ -17,6 +17,8 @@ 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]
20
22
 
21
23
 
22
24
  class ChannelManifest(_OptionalChannelManifest):