managed-deepagents 0.6.2.dev45__py3-none-win_amd64.whl → 0.6.2.dev47__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.
Potentially problematic release.
This version of managed-deepagents might be problematic. Click here for more details.
- managed_deepagents/_agent_auth.py +81 -8
- managed_deepagents/_binaries/mda.exe +0 -0
- managed_deepagents/_connections.py +128 -37
- managed_deepagents/_identity_auth.py +16 -1
- managed_deepagents/_identity_runtime.py +6 -0
- managed_deepagents/_ingress.py +2 -0
- managed_deepagents/_validated_token.py +2 -0
- managed_deepagents/identity/__init__.py +4 -0
- {managed_deepagents-0.6.2.dev45.dist-info → managed_deepagents-0.6.2.dev47.dist-info}/METADATA +10 -7
- {managed_deepagents-0.6.2.dev45.dist-info → managed_deepagents-0.6.2.dev47.dist-info}/RECORD +14 -14
- {managed_deepagents-0.6.2.dev45.dist-info → managed_deepagents-0.6.2.dev47.dist-info}/WHEEL +0 -0
- {managed_deepagents-0.6.2.dev45.dist-info → managed_deepagents-0.6.2.dev47.dist-info}/entry_points.txt +0 -0
- {managed_deepagents-0.6.2.dev45.dist-info → managed_deepagents-0.6.2.dev47.dist-info}/licenses/LICENSE +0 -0
- {managed_deepagents-0.6.2.dev45.dist-info → managed_deepagents-0.6.2.dev47.dist-info}/top_level.txt +0 -0
|
@@ -34,21 +34,48 @@ __all__ = [
|
|
|
34
34
|
"AgentAuthUnknownProviderError",
|
|
35
35
|
"CredentialAuthorizationRequiredError",
|
|
36
36
|
"PendingAuthorization",
|
|
37
|
+
"ResolvePrincipalIdentity",
|
|
37
38
|
"WorkspaceConnection",
|
|
39
|
+
"clear_principal_resolve_cache_for_tests",
|
|
38
40
|
]
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
@dataclass(frozen=True)
|
|
42
44
|
class AgentAuthContext:
|
|
43
45
|
base_url: str
|
|
44
|
-
#: Deployment id, passed to oauth-authorization-sessions as ``agent_id``.
|
|
45
|
-
agent_id: str
|
|
46
46
|
#: Authenticated caller, used as ``owner_id`` for user credentials.
|
|
47
47
|
principal_id: str
|
|
48
|
+
#: Deployment id when present. Required for agent-owned lookups; optional for user OAuth.
|
|
49
|
+
agent_id: str | None = None
|
|
48
50
|
api_key: str | None = None
|
|
49
51
|
workspace_id: str | None = None
|
|
50
52
|
|
|
51
53
|
|
|
54
|
+
class ResolvePrincipalIdentity(TypedDict):
|
|
55
|
+
provider: str
|
|
56
|
+
issuer: str
|
|
57
|
+
subject: str
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
_principal_resolve_cache: dict[str, str] = {}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def clear_principal_resolve_cache_for_tests() -> None:
|
|
64
|
+
"""Drop cached principal resolutions. Test-only."""
|
|
65
|
+
_principal_resolve_cache.clear()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _principal_resolve_cache_key(
|
|
69
|
+
context: AgentAuthContext, identity: ResolvePrincipalIdentity
|
|
70
|
+
) -> str:
|
|
71
|
+
origin = context.base_url.rstrip("/")
|
|
72
|
+
workspace = (context.workspace_id or "").strip()
|
|
73
|
+
return (
|
|
74
|
+
f"{origin}\0{workspace}\0"
|
|
75
|
+
f"{identity['provider']}\0{identity['issuer']}\0{identity['subject']}"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
52
79
|
class PendingAuthorization(TypedDict):
|
|
53
80
|
"""One connection slug the caller has not granted, with its connect URL."""
|
|
54
81
|
|
|
@@ -115,6 +142,49 @@ class AgentAuth:
|
|
|
115
142
|
self._timeout = timeout
|
|
116
143
|
self._request: AgentAuthRequest = request if request is not None else self._urlopen_json
|
|
117
144
|
|
|
145
|
+
def resolve_principal(self, identity: ResolvePrincipalIdentity) -> str:
|
|
146
|
+
"""Resolve a verified external identity to an Agent Auth principal UUID."""
|
|
147
|
+
provider = identity["provider"].strip()
|
|
148
|
+
issuer = identity["issuer"].strip()
|
|
149
|
+
subject = identity["subject"].strip()
|
|
150
|
+
if not provider or not issuer or not subject:
|
|
151
|
+
raise CredentialStoreError(
|
|
152
|
+
"Agent Auth principal resolve requires provider, issuer, and subject"
|
|
153
|
+
)
|
|
154
|
+
key = _principal_resolve_cache_key(
|
|
155
|
+
self.context, {"provider": provider, "issuer": issuer, "subject": subject}
|
|
156
|
+
)
|
|
157
|
+
cached = _principal_resolve_cache.get(key)
|
|
158
|
+
if cached is not None:
|
|
159
|
+
return cached
|
|
160
|
+
principal_id = self._resolve_principal_uncached(
|
|
161
|
+
{"provider": provider, "issuer": issuer, "subject": subject}
|
|
162
|
+
)
|
|
163
|
+
_principal_resolve_cache[key] = principal_id
|
|
164
|
+
return principal_id
|
|
165
|
+
|
|
166
|
+
def _resolve_principal_uncached(self, identity: ResolvePrincipalIdentity) -> str:
|
|
167
|
+
status, body = self._request(
|
|
168
|
+
"POST",
|
|
169
|
+
"/v1/agent-auth/principals",
|
|
170
|
+
body={
|
|
171
|
+
"provider": identity["provider"],
|
|
172
|
+
"issuer": identity["issuer"],
|
|
173
|
+
"subject": identity["subject"],
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
if status >= _HTTP_BAD_REQUEST:
|
|
177
|
+
raise CredentialStoreError("Agent Auth resolve principal failed", status, body)
|
|
178
|
+
principal = body.get("principal")
|
|
179
|
+
principal_id = ""
|
|
180
|
+
if isinstance(principal, Mapping):
|
|
181
|
+
raw = principal.get("id")
|
|
182
|
+
if isinstance(raw, str):
|
|
183
|
+
principal_id = raw.strip()
|
|
184
|
+
if not principal_id:
|
|
185
|
+
raise CredentialStoreError("Agent Auth resolve principal returned no principal id")
|
|
186
|
+
return principal_id
|
|
187
|
+
|
|
118
188
|
def find_workspace_connection(self, slug: str) -> WorkspaceConnection | None:
|
|
119
189
|
"""List workspace connections and return the row for ``slug``, if any."""
|
|
120
190
|
if not (self.context.workspace_id or "").strip():
|
|
@@ -175,14 +245,17 @@ class AgentAuth:
|
|
|
175
245
|
"""
|
|
176
246
|
if connection.kind != "secret":
|
|
177
247
|
return None
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
248
|
+
agent_id = (self.context.agent_id or "").strip()
|
|
249
|
+
if not agent_id:
|
|
250
|
+
raise CredentialStoreError(
|
|
251
|
+
"connections are not configured: LANGSMITH_HOST_PROJECT_ID is not set"
|
|
252
|
+
)
|
|
253
|
+
from_agent = self._credential_id_for_owner(connection.connection_id, "agent", agent_id)
|
|
181
254
|
if from_agent is not None:
|
|
182
255
|
return from_agent
|
|
183
256
|
return self.find_credential_id(
|
|
184
257
|
owner_type="agent",
|
|
185
|
-
owner_id=
|
|
258
|
+
owner_id=agent_id,
|
|
186
259
|
kind="secret",
|
|
187
260
|
slug=slug,
|
|
188
261
|
)
|
|
@@ -277,8 +350,8 @@ class AgentAuth:
|
|
|
277
350
|
"/v1/agent-auth/oauth-authorization-sessions",
|
|
278
351
|
body={
|
|
279
352
|
"connection_id": connection.connection_id,
|
|
280
|
-
"
|
|
281
|
-
"
|
|
353
|
+
"owner_type": "user",
|
|
354
|
+
"owner_id": self.context.principal_id,
|
|
282
355
|
},
|
|
283
356
|
)
|
|
284
357
|
if status >= _HTTP_BAD_REQUEST:
|
|
Binary file
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
``{ type: "agent" }`` resolves material owned by the current deployment.
|
|
4
4
|
``{ type: "user" }`` resolves per-caller grants (OAuth or opaque). Resolution
|
|
5
|
-
reads Agent Auth on every await and never caches.
|
|
5
|
+
reads Agent Auth on every await and never caches material.
|
|
6
6
|
|
|
7
7
|
``mda dev`` substitutes local values for agent opaque secrets via ``MDA_DEV_*``.
|
|
8
|
-
User-owned connections
|
|
9
|
-
|
|
8
|
+
User-owned connections talk to Agent Auth in every environment (including
|
|
9
|
+
``mda dev``) after resolving the caller's Agent Auth principal.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
@@ -24,7 +24,9 @@ from managed_deepagents._agent_auth import (
|
|
|
24
24
|
AgentAuth,
|
|
25
25
|
AgentAuthContext,
|
|
26
26
|
CredentialAuthorizationRequiredError,
|
|
27
|
+
ResolvePrincipalIdentity,
|
|
27
28
|
WorkspaceConnection,
|
|
29
|
+
clear_principal_resolve_cache_for_tests,
|
|
28
30
|
)
|
|
29
31
|
from managed_deepagents._agent_auth_http import (
|
|
30
32
|
AGENT_AUTH_TIMEOUT_SECONDS,
|
|
@@ -49,10 +51,12 @@ __all__ = [
|
|
|
49
51
|
"CredentialStoreError",
|
|
50
52
|
"InvalidAgentAuthEndpointError",
|
|
51
53
|
"agent_connection_env_key",
|
|
54
|
+
"clear_principal_resolve_cache_for_tests",
|
|
52
55
|
"connections",
|
|
53
56
|
"is_connection_reference",
|
|
54
57
|
"resolve_agent_auth_base_url",
|
|
55
58
|
"resolve_agent_auth_context",
|
|
59
|
+
"resolve_credential_principal_id",
|
|
56
60
|
"resolve_owner_id",
|
|
57
61
|
]
|
|
58
62
|
|
|
@@ -108,19 +112,7 @@ class ConnectionNotFoundError(LookupError):
|
|
|
108
112
|
_SLUG_PATTERN = re.compile(r"[a-z0-9]+(?:-[a-z0-9]+)*")
|
|
109
113
|
_MAX_SLUG_LENGTH = 64
|
|
110
114
|
_DEV_CREDENTIAL_PREFIX = "MDA_DEV_"
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def _local_user_connection_unsupported(slug: str | None = None) -> str:
|
|
114
|
-
local_hint = (
|
|
115
|
-
f"set {agent_connection_env_key(slug)}"
|
|
116
|
-
if slug
|
|
117
|
-
else "set the corresponding MDA_DEV_<slug> variable"
|
|
118
|
-
)
|
|
119
|
-
return (
|
|
120
|
-
"user-owned connections need a deployment, so they do not resolve in `mda dev`: "
|
|
121
|
-
"deploy with `mda deploy` to exercise this connection, or declare it as "
|
|
122
|
-
f'{{"type": "agent"}} and {local_hint} for local runs'
|
|
123
|
-
)
|
|
115
|
+
_TRUSTED_BACKEND_PROVIDER = "trusted_backend"
|
|
124
116
|
|
|
125
117
|
|
|
126
118
|
#: Env var naming the LangSmith platform API that serves Agent Auth.
|
|
@@ -131,6 +123,8 @@ _DEFAULT_LANGSMITH_ENDPOINT = "https://api.smith.langchain.com"
|
|
|
131
123
|
DEPLOYMENT_ID_ENV = "LANGSMITH_HOST_PROJECT_ID"
|
|
132
124
|
_API_KEY_ENV = "LANGSMITH_API_KEY"
|
|
133
125
|
_WORKSPACE_ID_ENV = "LANGSMITH_WORKSPACE_ID"
|
|
126
|
+
_LOCAL_DEV_USER = "mda:local-dev"
|
|
127
|
+
_LOCAL_DEV_PRINCIPAL_ENV = "MDA_AGENT_AUTH_PRINCIPAL_ID"
|
|
134
128
|
|
|
135
129
|
|
|
136
130
|
class InvalidAgentAuthEndpointError(CredentialStoreError):
|
|
@@ -157,6 +151,7 @@ class _AgentAuthConfig:
|
|
|
157
151
|
api_key: str
|
|
158
152
|
deployment_id: str | None
|
|
159
153
|
workspace_id: str
|
|
154
|
+
local_dev_principal_id: str | None
|
|
160
155
|
|
|
161
156
|
|
|
162
157
|
class AgentAuthConnectionStore:
|
|
@@ -176,15 +171,29 @@ class AgentAuthConnectionStore:
|
|
|
176
171
|
async def fetch(self, request: ConnectionRequest) -> str | None:
|
|
177
172
|
owner = request["owner"]
|
|
178
173
|
resolved = os.environ if self._env is None else self._env
|
|
179
|
-
if resolved.get("MDA_LOCAL_DEV") == "1":
|
|
180
|
-
return
|
|
174
|
+
if resolved.get("MDA_LOCAL_DEV") == "1" and owner["type"] == "agent":
|
|
175
|
+
return _fetch_local_dev_agent_connection(resolved, request["slug"])
|
|
181
176
|
config = _resolve_agent_auth_config(self._env)
|
|
182
|
-
|
|
177
|
+
if owner["type"] == "agent" and not config.deployment_id:
|
|
178
|
+
raise CredentialStoreError(
|
|
179
|
+
f"connections are not configured: {DEPLOYMENT_ID_ENV} is not set"
|
|
180
|
+
)
|
|
181
|
+
# Source-identity match still uses JWT sub / channel actor.
|
|
182
|
+
resolve_owner_id(owner, request["identity"], config.deployment_id)
|
|
183
|
+
if owner["type"] == "user":
|
|
184
|
+
principal_id = resolve_credential_principal_id(
|
|
185
|
+
request["identity"],
|
|
186
|
+
config,
|
|
187
|
+
request_fn=self._bind_request(config),
|
|
188
|
+
timeout=self._timeout,
|
|
189
|
+
)
|
|
190
|
+
else:
|
|
191
|
+
principal_id = resolve_owner_id(owner, request["identity"], config.deployment_id)
|
|
183
192
|
client = AgentAuth(
|
|
184
193
|
AgentAuthContext(
|
|
185
194
|
base_url=config.base_url,
|
|
186
|
-
agent_id=config.deployment_id
|
|
187
|
-
principal_id=
|
|
195
|
+
agent_id=config.deployment_id,
|
|
196
|
+
principal_id=principal_id,
|
|
188
197
|
api_key=config.api_key,
|
|
189
198
|
workspace_id=config.workspace_id,
|
|
190
199
|
),
|
|
@@ -208,12 +217,14 @@ class AgentAuthConnectionStore:
|
|
|
208
217
|
*,
|
|
209
218
|
body: dict[str, object] | None = None,
|
|
210
219
|
) -> tuple[int, dict[str, object]]:
|
|
211
|
-
del method, body
|
|
212
220
|
url = f"{config.base_url}{path}"
|
|
213
221
|
headers = agent_auth_headers(
|
|
214
222
|
api_key=config.api_key,
|
|
215
223
|
workspace_id=config.workspace_id,
|
|
216
224
|
)
|
|
225
|
+
# Test injects only support GET-shaped responses; path still distinguishes
|
|
226
|
+
# principals vs connections for mock routers.
|
|
227
|
+
del method, body
|
|
217
228
|
response = http_get(url, headers, timeout)
|
|
218
229
|
status = response.get("status")
|
|
219
230
|
raw_body = response.get("body", b"")
|
|
@@ -227,13 +238,7 @@ class AgentAuthConnectionStore:
|
|
|
227
238
|
return request
|
|
228
239
|
|
|
229
240
|
|
|
230
|
-
def
|
|
231
|
-
resolved: Mapping[str, str],
|
|
232
|
-
slug: str,
|
|
233
|
-
owner: ConnectionOwner,
|
|
234
|
-
) -> str:
|
|
235
|
-
if owner["type"] == "user":
|
|
236
|
-
raise CredentialStoreError(_local_user_connection_unsupported(slug))
|
|
241
|
+
def _fetch_local_dev_agent_connection(resolved: Mapping[str, str], slug: str) -> str:
|
|
237
242
|
value = resolved.get(agent_connection_env_key(slug))
|
|
238
243
|
if value is not None and value.strip():
|
|
239
244
|
return value
|
|
@@ -410,24 +415,105 @@ def resolve_owner_id(
|
|
|
410
415
|
def resolve_agent_auth_context(
|
|
411
416
|
identity: RuntimeIdentity | None,
|
|
412
417
|
env: Mapping[str, str] | None = None,
|
|
418
|
+
*,
|
|
419
|
+
request_fn: Callable[..., tuple[int, dict[str, object]]] | None = None,
|
|
420
|
+
timeout: float = AGENT_AUTH_TIMEOUT_SECONDS,
|
|
413
421
|
) -> AgentAuthContext:
|
|
414
|
-
resolved = os.environ if env is None else env
|
|
415
|
-
if resolved.get("MDA_LOCAL_DEV") == "1":
|
|
416
|
-
raise CredentialStoreError(_local_user_connection_unsupported())
|
|
417
422
|
config = _resolve_agent_auth_config(env)
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
)
|
|
423
|
+
principal_id = resolve_credential_principal_id(
|
|
424
|
+
identity, config, request_fn=request_fn, timeout=timeout
|
|
425
|
+
)
|
|
422
426
|
return AgentAuthContext(
|
|
423
427
|
base_url=config.base_url,
|
|
424
428
|
agent_id=config.deployment_id,
|
|
425
|
-
principal_id=
|
|
429
|
+
principal_id=principal_id,
|
|
426
430
|
api_key=config.api_key,
|
|
427
431
|
workspace_id=config.workspace_id,
|
|
428
432
|
)
|
|
429
433
|
|
|
430
434
|
|
|
435
|
+
def resolve_credential_principal_id(
|
|
436
|
+
identity: RuntimeIdentity | None,
|
|
437
|
+
config: _AgentAuthConfig,
|
|
438
|
+
*,
|
|
439
|
+
request_fn: Callable[..., tuple[int, dict[str, object]]] | None = None,
|
|
440
|
+
timeout: float = AGENT_AUTH_TIMEOUT_SECONDS,
|
|
441
|
+
) -> str:
|
|
442
|
+
"""Map a runtime identity to the Agent Auth principal used for user grants."""
|
|
443
|
+
if identity is None:
|
|
444
|
+
raise CredentialStoreError("user connections require an authenticated user principal")
|
|
445
|
+
user = identity.get("user") or {}
|
|
446
|
+
user_id = user.get("id") if isinstance(user, Mapping) else None
|
|
447
|
+
if not isinstance(user_id, str) or not user_id.strip():
|
|
448
|
+
raise CredentialStoreError("user connections require an authenticated user principal")
|
|
449
|
+
kind = user.get("kind") if isinstance(user, Mapping) else None
|
|
450
|
+
if kind == "service":
|
|
451
|
+
if (
|
|
452
|
+
user_id.strip() == _LOCAL_DEV_USER
|
|
453
|
+
and config.local_dev_principal_id is not None
|
|
454
|
+
):
|
|
455
|
+
return config.local_dev_principal_id
|
|
456
|
+
raise CredentialStoreError(
|
|
457
|
+
"user-owned connections require a person principal; "
|
|
458
|
+
"Studio and other service callers cannot own OAuth grants"
|
|
459
|
+
)
|
|
460
|
+
stamped = identity.get("agent_auth_principal_id")
|
|
461
|
+
if isinstance(stamped, str) and stamped.strip():
|
|
462
|
+
return stamped.strip()
|
|
463
|
+
|
|
464
|
+
subject = user_id.strip()
|
|
465
|
+
external = _external_identity_for_resolve(identity, config.workspace_id)
|
|
466
|
+
if external is None:
|
|
467
|
+
raise CredentialStoreError(
|
|
468
|
+
"user-owned connections need a resolvable identity "
|
|
469
|
+
"(validated-token issuer/subject, channel identity_context, or trusted backend)"
|
|
470
|
+
)
|
|
471
|
+
bootstrap = AgentAuth(
|
|
472
|
+
AgentAuthContext(
|
|
473
|
+
base_url=config.base_url,
|
|
474
|
+
principal_id=subject,
|
|
475
|
+
api_key=config.api_key,
|
|
476
|
+
workspace_id=config.workspace_id,
|
|
477
|
+
agent_id=config.deployment_id,
|
|
478
|
+
),
|
|
479
|
+
request=request_fn,
|
|
480
|
+
timeout=timeout,
|
|
481
|
+
)
|
|
482
|
+
return bootstrap.resolve_principal(external)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _external_identity_for_resolve(
|
|
486
|
+
identity: RuntimeIdentity,
|
|
487
|
+
workspace_id: str | None,
|
|
488
|
+
) -> ResolvePrincipalIdentity | None:
|
|
489
|
+
user = identity.get("user") or {}
|
|
490
|
+
subject = user.get("id") if isinstance(user, Mapping) else None
|
|
491
|
+
if not isinstance(subject, str) or not subject.strip():
|
|
492
|
+
return None
|
|
493
|
+
subject = subject.strip()
|
|
494
|
+
provider = identity.get("identity_provider")
|
|
495
|
+
claims = identity.get("claims")
|
|
496
|
+
issuer = None
|
|
497
|
+
if isinstance(claims, Mapping):
|
|
498
|
+
raw_iss = claims.get("iss")
|
|
499
|
+
if isinstance(raw_iss, str) and raw_iss.strip():
|
|
500
|
+
issuer = raw_iss.strip()
|
|
501
|
+
if isinstance(provider, str) and provider.strip() and issuer:
|
|
502
|
+
return {
|
|
503
|
+
"provider": provider.strip(),
|
|
504
|
+
"issuer": issuer,
|
|
505
|
+
"subject": subject,
|
|
506
|
+
}
|
|
507
|
+
workspace = (workspace_id or "").strip()
|
|
508
|
+
if not workspace:
|
|
509
|
+
return None
|
|
510
|
+
return {
|
|
511
|
+
"provider": _TRUSTED_BACKEND_PROVIDER,
|
|
512
|
+
"issuer": workspace,
|
|
513
|
+
"subject": subject,
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
|
|
431
517
|
def _resolve_agent_auth_config(env: Mapping[str, str] | None) -> _AgentAuthConfig:
|
|
432
518
|
resolved = os.environ if env is None else env
|
|
433
519
|
api_key = (resolved.get(_API_KEY_ENV) or "").strip()
|
|
@@ -443,6 +529,11 @@ def _resolve_agent_auth_config(env: Mapping[str, str] | None) -> _AgentAuthConfi
|
|
|
443
529
|
api_key=api_key,
|
|
444
530
|
deployment_id=(resolved.get(DEPLOYMENT_ID_ENV) or "").strip() or None,
|
|
445
531
|
workspace_id=workspace_id,
|
|
532
|
+
local_dev_principal_id=(
|
|
533
|
+
(resolved.get(_LOCAL_DEV_PRINCIPAL_ENV) or "").strip() or None
|
|
534
|
+
if resolved.get("MDA_LOCAL_DEV") == "1"
|
|
535
|
+
else None
|
|
536
|
+
),
|
|
446
537
|
)
|
|
447
538
|
|
|
448
539
|
|
|
@@ -58,6 +58,10 @@ class ManagedUser(ManagedUserRequired, total=False):
|
|
|
58
58
|
mda_source_provider: str
|
|
59
59
|
mda_source_thread_id: str
|
|
60
60
|
mda_user_kind: Literal["user", "service"]
|
|
61
|
+
#: Validated-token provider id for Agent Auth principal resolve.
|
|
62
|
+
mda_identity_provider: str
|
|
63
|
+
#: Agent Auth principal UUID when Trigger ``identity_context`` already resolved one.
|
|
64
|
+
mda_agent_auth_principal_id: str
|
|
61
65
|
|
|
62
66
|
|
|
63
67
|
if TYPE_CHECKING:
|
|
@@ -88,6 +92,8 @@ INGRESS_SECRET_HEADER = "x-mda-ingress-secret" # noqa: S105 - header name, not
|
|
|
88
92
|
#: Internal handoff headers — accepted after a server API key or legacy ingress secret validates.
|
|
89
93
|
SOURCE_PROVIDER_HEADER = "x-mda-source-provider"
|
|
90
94
|
SOURCE_THREAD_HEADER = "x-mda-source-thread-id"
|
|
95
|
+
#: Trigger loopback stamp for a verified identity_context principal UUID.
|
|
96
|
+
AGENT_AUTH_PRINCIPAL_HEADER = "x-mda-agent-auth-principal-id"
|
|
91
97
|
|
|
92
98
|
#: Deployment env var carrying the shared secret the trusted backend presents.
|
|
93
99
|
INGRESS_SECRET_ENV = "MDA_INGRESS_SECRET" # noqa: S105 - env var name, not a secret
|
|
@@ -477,6 +483,9 @@ def _trusted_headers_user(headers: Mapping[str, object]) -> ManagedUser:
|
|
|
477
483
|
if source_provider is not None and source_thread is not None:
|
|
478
484
|
user["mda_source_provider"] = str(source_provider)
|
|
479
485
|
user["mda_source_thread_id"] = str(source_thread)
|
|
486
|
+
principal = str(headers.get(AGENT_AUTH_PRINCIPAL_HEADER) or "").strip()
|
|
487
|
+
if principal:
|
|
488
|
+
user["mda_agent_auth_principal_id"] = principal
|
|
480
489
|
return user
|
|
481
490
|
|
|
482
491
|
|
|
@@ -497,7 +506,13 @@ def _has_valid_handoff_secret(headers: Mapping[str, object]) -> bool:
|
|
|
497
506
|
|
|
498
507
|
def _has_handoff_headers(headers: Mapping[str, object]) -> bool:
|
|
499
508
|
return any(
|
|
500
|
-
header in headers
|
|
509
|
+
header in headers
|
|
510
|
+
for header in (
|
|
511
|
+
USER_HEADER,
|
|
512
|
+
SOURCE_PROVIDER_HEADER,
|
|
513
|
+
SOURCE_THREAD_HEADER,
|
|
514
|
+
AGENT_AUTH_PRINCIPAL_HEADER,
|
|
515
|
+
)
|
|
501
516
|
)
|
|
502
517
|
|
|
503
518
|
|
|
@@ -455,6 +455,12 @@ def build_runtime_identity(configurable: Mapping[str, object] | None) -> Runtime
|
|
|
455
455
|
claims = _field(user, "mda_claims")
|
|
456
456
|
if isinstance(claims, Mapping):
|
|
457
457
|
identity["claims"] = dict(cast("Mapping[str, object]", claims))
|
|
458
|
+
identity_provider = _field(user, "mda_identity_provider")
|
|
459
|
+
if isinstance(identity_provider, str) and identity_provider.strip():
|
|
460
|
+
identity["identity_provider"] = identity_provider.strip()
|
|
461
|
+
agent_auth_principal_id = _field(user, "mda_agent_auth_principal_id")
|
|
462
|
+
if isinstance(agent_auth_principal_id, str) and agent_auth_principal_id.strip():
|
|
463
|
+
identity["agent_auth_principal_id"] = agent_auth_principal_id.strip()
|
|
458
464
|
# Immutable: tools/middleware get a read-only view they cannot tamper with.
|
|
459
465
|
return cast("RuntimeIdentity", _freeze(identity))
|
|
460
466
|
|
managed_deepagents/_ingress.py
CHANGED
|
@@ -19,6 +19,7 @@ from managed_deepagents._channels.assistant_text import extract_assistant_text
|
|
|
19
19
|
from managed_deepagents._channels.binding import CHANNEL_BINDING_KEY, ChannelBinding
|
|
20
20
|
from managed_deepagents._channels.types import ChannelError
|
|
21
21
|
from managed_deepagents._identity_auth import (
|
|
22
|
+
AGENT_AUTH_PRINCIPAL_HEADER,
|
|
22
23
|
HANDOFF_SECRET_ENV,
|
|
23
24
|
HANDOFF_SECRET_HEADER,
|
|
24
25
|
INGRESS_SECRET_ENV,
|
|
@@ -355,6 +356,7 @@ def _create_trigger_ingress_loopback_client(
|
|
|
355
356
|
)
|
|
356
357
|
headers = {
|
|
357
358
|
USER_HEADER: user_id,
|
|
359
|
+
AGENT_AUTH_PRINCIPAL_HEADER: user_id,
|
|
358
360
|
SOURCE_PROVIDER_HEADER: source["provider"],
|
|
359
361
|
SOURCE_THREAD_HEADER: source["thread_key"],
|
|
360
362
|
}
|
|
@@ -263,6 +263,8 @@ def claims_to_user(provider: ValidatedTokenProvider, claims: Mapping[str, object
|
|
|
263
263
|
"permissions": [],
|
|
264
264
|
"mda_user_id": str(caller),
|
|
265
265
|
"mda_claims": dict(claims),
|
|
266
|
+
# Provider id drives Agent Auth POST /principals (provider, issuer, subject).
|
|
267
|
+
"mda_identity_provider": (provider.get("id") or "").strip() or "oidc",
|
|
266
268
|
}
|
|
267
269
|
groups = normalize_groups(claims.get(claim_map["groups"])) if claim_map.get("groups") else []
|
|
268
270
|
if groups:
|
|
@@ -177,6 +177,10 @@ class RuntimeIdentity(RuntimeIdentityRequired, total=False):
|
|
|
177
177
|
#: tools and middleware.
|
|
178
178
|
groups: tuple[str, ...]
|
|
179
179
|
claims: JsonObject
|
|
180
|
+
#: Validated-token provider id for Agent Auth principal resolve.
|
|
181
|
+
identity_provider: str
|
|
182
|
+
#: Agent Auth principal UUID when channel ingress already resolved one.
|
|
183
|
+
agent_auth_principal_id: str
|
|
180
184
|
|
|
181
185
|
|
|
182
186
|
class PrincipalRequired(TypedDict):
|
{managed_deepagents-0.6.2.dev45.dist-info → managed_deepagents-0.6.2.dev47.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: managed-deepagents
|
|
3
|
-
Version: 0.6.2.
|
|
3
|
+
Version: 0.6.2.dev47
|
|
4
4
|
Summary: Managed Deep Agents — the define_deep_agent authoring interface plus the CLI that compiles and deploys a code-first Deep Agent repository to a managed LangGraph runtime.
|
|
5
5
|
Author: LangChain
|
|
6
6
|
License: MIT
|
|
@@ -277,16 +277,19 @@ connector = connectors.mcp(
|
|
|
277
277
|
For this example, set `MDA_DEV_DOCS_TOKEN`.
|
|
278
278
|
|
|
279
279
|
A user-owned connection — `connections.get(slug, {"type": "user"})` — resolves
|
|
280
|
-
per caller (OAuth or opaque). The
|
|
281
|
-
|
|
282
|
-
a
|
|
283
|
-
|
|
280
|
+
per caller (OAuth or opaque). The runtime maps the verified ingress identity to
|
|
281
|
+
an Agent Auth principal, then the pre-run credential gate interrupts the run
|
|
282
|
+
when a grant is missing. That path works in `mda deploy` and `mda dev` when
|
|
283
|
+
`LANGSMITH_API_KEY` and `LANGSMITH_WORKSPACE_ID` are set and the workspace
|
|
284
|
+
connection rows exist — use a person principal (for example a Supabase JWT),
|
|
285
|
+
not Studio’s synthetic service user. OAuth completes on LangSmith’s platform
|
|
286
|
+
callback URL; the local UI must surface `credential_authorization_required`
|
|
287
|
+
and resume after the user connects.
|
|
284
288
|
|
|
285
289
|
Deploy the project first, then provision its connections with
|
|
286
290
|
`mda connections create`. Agent-owned opaque secrets are scoped to that
|
|
287
291
|
deployment, so the command refuses to create one before `mda deploy`.
|
|
288
|
-
`mda
|
|
289
|
-
workspace.
|
|
292
|
+
`mda connections check` verifies manifest slugs exist in the workspace.
|
|
290
293
|
|
|
291
294
|
Use `include_tools` or `exclude_tools` inside a server config to select a
|
|
292
295
|
subset. Tool names are raw MCP tool names before the managed `{server}__` prefix
|
{managed_deepagents-0.6.2.dev45.dist-info → managed_deepagents-0.6.2.dev47.dist-info}/RECORD
RENAMED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
managed_deepagents/__init__.py,sha256=RezkpgYxCcywj6_W7Nk3PN9TMBfYyFgIatxGrIeY8V4,1862
|
|
2
2
|
managed_deepagents/__main__.py,sha256=GcjmqN0Ea5v8OHKMxYToasm3eCX_doSLOlpEzAi1tAw,67
|
|
3
|
-
managed_deepagents/_agent_auth.py,sha256=
|
|
3
|
+
managed_deepagents/_agent_auth.py,sha256=TUZJgc1TyjEFNZX3E97VV30igM_bC9rDZ-pmKpUSExA,20010
|
|
4
4
|
managed_deepagents/_agent_auth_http.py,sha256=aCYn7Rrtw3cnK3r4ir4JWxwzFkb7cF7nBgPpNdydxDw,5108
|
|
5
5
|
managed_deepagents/_channel.py,sha256=-Og7QOM1yvV-yeTDOfv2CxXpR2HLY4xdX1bLrIsPi5k,5272
|
|
6
6
|
managed_deepagents/_cli.py,sha256=jVaw2i1ysdRoWziMdPFmRXtKry_baYGRspyzyIeGWoU,1688
|
|
7
|
-
managed_deepagents/_connections.py,sha256=
|
|
7
|
+
managed_deepagents/_connections.py,sha256=MlrmkVJTogZdDmh_vbj0aBzlZqliysTVTBKsYfEpqnU,19919
|
|
8
8
|
managed_deepagents/_connector.py,sha256=Syp0SRs1oFCUUltIUmZbSNi1Ys_PxObVdiAi-I9GBzs,8040
|
|
9
9
|
managed_deepagents/_cors.py,sha256=V96RPrqewdRWG5RpEMEBnPuRq9_zF8nf4vf99k5dkuI,7597
|
|
10
10
|
managed_deepagents/_credential_gate.py,sha256=SNxXunqmHgjBsZpiZu8yp49YkRIO0amkl0-7GHLGnIg,12280
|
|
11
11
|
managed_deepagents/_eval_filesystem.py,sha256=1nuD0yn3gh19JrWNocWyPFyeJW42E7Gse3XRWCUSTAA,1399
|
|
12
12
|
managed_deepagents/_extension.py,sha256=x2H1S8inkvTZQ_dr7kq0M8o8rre1FjzCDvMzItw4iyk,5318
|
|
13
|
-
managed_deepagents/_identity_auth.py,sha256=
|
|
13
|
+
managed_deepagents/_identity_auth.py,sha256=sykxsL2wzcJI7I0MFs4xkcoviQhfNQGIBFQ0rfP3cXU,33418
|
|
14
14
|
managed_deepagents/_identity_http.py,sha256=aVQfZr7UKQoXF1Dry3p9fzXIj1882sEa668rvhn37i0,16029
|
|
15
15
|
managed_deepagents/_identity_links.py,sha256=Z1H2Seo2KaI-vcdU3_PGM-F06wOz0IQqUP-28wqiQsA,9172
|
|
16
|
-
managed_deepagents/_identity_runtime.py,sha256=
|
|
17
|
-
managed_deepagents/_ingress.py,sha256=
|
|
16
|
+
managed_deepagents/_identity_runtime.py,sha256=FJeJkhCg0Wo8x8hwiLB-9MUS-VBsGVzBzk3JJaDqH0o,24847
|
|
17
|
+
managed_deepagents/_ingress.py,sha256=8vJqIuyKI3dxC5wbyRPL9ES71xsXgxiTWJqYPpG7Wt0,12402
|
|
18
18
|
managed_deepagents/_log.py,sha256=oYwOO0ZL4-INkge9YJvtLuCxL9zAtgjSPIggVbkcyc8,2932
|
|
19
19
|
managed_deepagents/_loopback.py,sha256=hQ1oNi77XpdUjzZURSBRoMvJ1mGCs-STobfBftGmXuE,2409
|
|
20
20
|
managed_deepagents/_managed_middleware.py,sha256=cfInVHTVjWoy_DBoFlnslfrDf2_n6llGnTGasta4AIo,10295
|
|
21
21
|
managed_deepagents/_managed_tools.py,sha256=KsFDULbv5fv0RY3Nj0zbv6z0Y80_o5YazhqeiQ9PnPk,9105
|
|
22
22
|
managed_deepagents/_memory_instructions.py,sha256=lyxKfNuXrWih574gmy3NHhIq_hIpJIM5zl3P8V69mUs,6678
|
|
23
|
-
managed_deepagents/_validated_token.py,sha256=
|
|
23
|
+
managed_deepagents/_validated_token.py,sha256=d13DiRReoxSK5Q2kIPTLeXCFx2tjCIHJOi5DZexr7fk,17397
|
|
24
24
|
managed_deepagents/define_deep_agent.py,sha256=17VfiiBINyhlqlATHrlJRPihKVlBsDGDP8K4-fxjxU8,5611
|
|
25
25
|
managed_deepagents/define_memory.py,sha256=n7Ya20dWGvwbZOkPIXoa68nTnJie8tpKEkRwfJQt_fU,5036
|
|
26
26
|
managed_deepagents/namespaces.py,sha256=GS76G19-V5Svfpbca-9xhQfJ6vel52jcJyfj6loLt_Y,4199
|
|
@@ -28,7 +28,7 @@ managed_deepagents/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
|
28
28
|
managed_deepagents/runtime.py,sha256=B4iKTJZD0zXx_MZ09pqQFOkVo07tqJDT3qvOBr-0iOk,65782
|
|
29
29
|
managed_deepagents/sandbox.py,sha256=xlpcWa6CoVX6GKip2nK-0Lw-HBCoFGFYm0r4x9eYwcY,2530
|
|
30
30
|
managed_deepagents/schedules.py,sha256=MtTQN4tlm1NizFDCjZZv_huGfbQx-dj9WP1dIBybCug,5237
|
|
31
|
-
managed_deepagents/_binaries/mda.exe,sha256=
|
|
31
|
+
managed_deepagents/_binaries/mda.exe,sha256=exWH4fDovFngR8cuJPmiHo96vrU436g1T1qsVfbVs7A,3912704
|
|
32
32
|
managed_deepagents/_channels/__init__.py,sha256=Z6eXmzaLxG09DSyJoY_CX34axcsBo9gSJ0G7pRm2D6M,1216
|
|
33
33
|
managed_deepagents/_channels/assistant_text.py,sha256=f-Z3J3PlQFXZHztgBxlRoqB2LigU1Hj1YpwoXBMTR18,2171
|
|
34
34
|
managed_deepagents/_channels/binding.py,sha256=6vAKbWpg-eyLErJ0SsozH4mZc50hA1NhquM63mVmWDQ,3423
|
|
@@ -65,13 +65,13 @@ managed_deepagents/_memory/mounts.py,sha256=b2KtaTX3apTkmoqQxG2ENtkO86avSt1Nvrv3
|
|
|
65
65
|
managed_deepagents/_memory/paths.py,sha256=gHI-9KyawUv1ftX1K0kjaBbM4IYLeP7S-_WCaS2gVBM,353
|
|
66
66
|
managed_deepagents/_memory/slices.py,sha256=ljL78ECmDZ27xc3ajzkLliOfUln_bNr-d1ycC1wlS38,3559
|
|
67
67
|
managed_deepagents/_memory/types.py,sha256=scSk6mH0k2OC59wzUCa67szvXylSjWDrfzWWRIU9tZA,4336
|
|
68
|
-
managed_deepagents/identity/__init__.py,sha256=
|
|
68
|
+
managed_deepagents/identity/__init__.py,sha256=3KHXJca8w9Xu99_QhJX9iV1xMHTa34NS1cRPKVRx408,16342
|
|
69
69
|
managed_deepagents/identity/claims.py,sha256=prby7r_8Lww3MmoBm8PX6PsSGE3GwSHkr8mVIz_08nU,1180
|
|
70
70
|
managed_deepagents/identity/options.py,sha256=x_yMbXEFR7zXyehEjDHG2qjAlukXjrRBcQLcM6IndLM,2550
|
|
71
71
|
managed_deepagents/identity/supabase.py,sha256=8uobl3WCrQL_ieSXG8KTLf1th7Qjim9liZT50HeaJE0,6026
|
|
72
|
-
managed_deepagents-0.6.2.
|
|
73
|
-
managed_deepagents-0.6.2.
|
|
74
|
-
managed_deepagents-0.6.2.
|
|
75
|
-
managed_deepagents-0.6.2.
|
|
76
|
-
managed_deepagents-0.6.2.
|
|
77
|
-
managed_deepagents-0.6.2.
|
|
72
|
+
managed_deepagents-0.6.2.dev47.dist-info/licenses/LICENSE,sha256=WnPZQQ608ojZUkXl7BW6VbKloIAibZiGm1K6cjeZOL8,1066
|
|
73
|
+
managed_deepagents-0.6.2.dev47.dist-info/METADATA,sha256=IGICCJVY2YhFYrT6Z6_7itwKWHc__S6dsNV_7aVF2uc,14039
|
|
74
|
+
managed_deepagents-0.6.2.dev47.dist-info/WHEEL,sha256=3I5VVWZdsFlU417aCS2bMRuerS8wsfvBq99MyatBV3A,97
|
|
75
|
+
managed_deepagents-0.6.2.dev47.dist-info/entry_points.txt,sha256=ziZqkKuaDO3qHCjKmBhrx9cm3ok8bZ9xcEDpUTj4unM,53
|
|
76
|
+
managed_deepagents-0.6.2.dev47.dist-info/top_level.txt,sha256=fdBo4O0Hlol_w022tTLB5ftZpamb4yJoQ3MUvjgTGfA,19
|
|
77
|
+
managed_deepagents-0.6.2.dev47.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{managed_deepagents-0.6.2.dev45.dist-info → managed_deepagents-0.6.2.dev47.dist-info}/top_level.txt
RENAMED
|
File without changes
|