managed-deepagents 0.6.2.dev36__py3-none-win_amd64.whl → 0.6.2.dev37__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.
- managed_deepagents/_agent_auth.py +39 -49
- managed_deepagents/_agent_auth_http.py +9 -41
- managed_deepagents/_binaries/mda.exe +0 -0
- managed_deepagents/_channels/trigger/http.py +6 -122
- managed_deepagents/_channels/trigger/types.py +2 -12
- managed_deepagents/_connections.py +12 -5
- managed_deepagents/_credential_gate.py +2 -2
- managed_deepagents/_identity_auth.py +7 -49
- managed_deepagents/_ingress.py +14 -46
- {managed_deepagents-0.6.2.dev36.dist-info → managed_deepagents-0.6.2.dev37.dist-info}/METADATA +2 -4
- {managed_deepagents-0.6.2.dev36.dist-info → managed_deepagents-0.6.2.dev37.dist-info}/RECORD +15 -16
- managed_deepagents/_channels/trigger/identity_context.py +0 -214
- {managed_deepagents-0.6.2.dev36.dist-info → managed_deepagents-0.6.2.dev37.dist-info}/WHEEL +0 -0
- {managed_deepagents-0.6.2.dev36.dist-info → managed_deepagents-0.6.2.dev37.dist-info}/entry_points.txt +0 -0
- {managed_deepagents-0.6.2.dev36.dist-info → managed_deepagents-0.6.2.dev37.dist-info}/licenses/LICENSE +0 -0
- {managed_deepagents-0.6.2.dev36.dist-info → managed_deepagents-0.6.2.dev37.dist-info}/top_level.txt +0 -0
|
@@ -23,7 +23,6 @@ AGENT_AUTH_PROVIDER_PAGE_SIZE = 100
|
|
|
23
23
|
AGENT_AUTH_MAX_PROVIDER_PAGES = 20
|
|
24
24
|
|
|
25
25
|
_HTTP_BAD_REQUEST = 400
|
|
26
|
-
_HTTP_FORBIDDEN = 403
|
|
27
26
|
_HTTP_NOT_FOUND = 404
|
|
28
27
|
_HTTP_CONFLICT = 409
|
|
29
28
|
|
|
@@ -47,6 +46,8 @@ class AgentAuthContext:
|
|
|
47
46
|
principal_id: str
|
|
48
47
|
api_key: str | None = None
|
|
49
48
|
workspace_id: str | None = None
|
|
49
|
+
#: Agent Builder service JWT for workspace-owned credential material.
|
|
50
|
+
service_key: str | None = None
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
class PendingAuthorization(TypedDict):
|
|
@@ -133,7 +134,7 @@ class AgentAuth:
|
|
|
133
134
|
f"/v1/agent-auth/connections?{urllib.parse.urlencode(query)}",
|
|
134
135
|
)
|
|
135
136
|
if status >= _HTTP_BAD_REQUEST:
|
|
136
|
-
raise CredentialStoreError("Agent Auth list connections failed", status
|
|
137
|
+
raise CredentialStoreError("Agent Auth list connections failed", status)
|
|
137
138
|
raw_items = body.get("items")
|
|
138
139
|
items = raw_items if isinstance(raw_items, list) else []
|
|
139
140
|
for item in items:
|
|
@@ -169,17 +170,28 @@ class AgentAuth:
|
|
|
169
170
|
def find_secret_credential_id(self, connection: WorkspaceConnection, slug: str) -> str | None:
|
|
170
171
|
"""Resolve the credential id for a secret connection row.
|
|
171
172
|
|
|
172
|
-
Agent
|
|
173
|
-
|
|
174
|
-
to deployments.
|
|
173
|
+
With an Agent Builder service key, prefer GET-for-owner / workspace list.
|
|
174
|
+
Personal API keys may only list agent-owned credentials.
|
|
175
175
|
"""
|
|
176
176
|
if connection.kind != "secret":
|
|
177
177
|
return None
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
+
)
|
|
183
195
|
return self.find_credential_id(
|
|
184
196
|
owner_type="agent",
|
|
185
197
|
owner_id=self.context.agent_id,
|
|
@@ -190,12 +202,6 @@ class AgentAuth:
|
|
|
190
202
|
def _credential_id_for_owner(
|
|
191
203
|
self, connection_id: str, owner_type: str, owner_id: str
|
|
192
204
|
) -> str | None:
|
|
193
|
-
"""Read credential_id from GET /connections/{id}?credential_owner_*.
|
|
194
|
-
|
|
195
|
-
Deployments authenticate with ``LANGSMITH_API_KEY`` (the minted
|
|
196
|
-
service-account key). User and workspace owners additionally require
|
|
197
|
-
workspace management permission.
|
|
198
|
-
"""
|
|
199
205
|
query = urllib.parse.urlencode(
|
|
200
206
|
{
|
|
201
207
|
"credential_owner_type": owner_type,
|
|
@@ -209,27 +215,16 @@ class AgentAuth:
|
|
|
209
215
|
if status == _HTTP_NOT_FOUND:
|
|
210
216
|
return None
|
|
211
217
|
if status >= _HTTP_BAD_REQUEST:
|
|
212
|
-
raise CredentialStoreError(
|
|
213
|
-
"Agent Auth get connection for owner failed"
|
|
214
|
-
+ (" (forbidden)" if status == _HTTP_FORBIDDEN else ""),
|
|
215
|
-
status,
|
|
216
|
-
body,
|
|
217
|
-
)
|
|
218
|
+
raise CredentialStoreError("Agent Auth get connection for owner failed", status)
|
|
218
219
|
return _connection_credential_id(body)
|
|
219
220
|
|
|
220
221
|
def read_user_secret(self, slug: str) -> str | None:
|
|
221
222
|
"""Read a caller-owned opaque secret grant for a workspace connection slug."""
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
def read_user_secret_for_connection(self, connection: WorkspaceConnection) -> str | None:
|
|
228
|
-
"""Read a caller-owned opaque secret when the connection row is already loaded."""
|
|
229
|
-
if connection.kind != "secret":
|
|
230
|
-
return None
|
|
231
|
-
credential_id = self._credential_id_for_owner(
|
|
232
|
-
connection.connection_id, "user", self.context.principal_id
|
|
223
|
+
credential_id = self.find_credential_id(
|
|
224
|
+
owner_type="user",
|
|
225
|
+
owner_id=self.context.principal_id,
|
|
226
|
+
kind="secret",
|
|
227
|
+
slug=slug,
|
|
233
228
|
)
|
|
234
229
|
if credential_id is None:
|
|
235
230
|
return None
|
|
@@ -249,17 +244,11 @@ class AgentAuth:
|
|
|
249
244
|
Returns:
|
|
250
245
|
The access token, or ``None`` when the caller must connect.
|
|
251
246
|
"""
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def read_user_access_token_for_connection(self, connection: WorkspaceConnection) -> str | None:
|
|
258
|
-
"""Read a caller OAuth token when the connection row is already loaded."""
|
|
259
|
-
if connection.kind != "oauth2":
|
|
260
|
-
return None
|
|
261
|
-
credential_id = self._credential_id_for_owner(
|
|
262
|
-
connection.connection_id, "user", self.context.principal_id
|
|
247
|
+
credential_id = self.find_credential_id(
|
|
248
|
+
owner_type="user",
|
|
249
|
+
owner_id=self.context.principal_id,
|
|
250
|
+
kind="oauth2",
|
|
251
|
+
slug=slug,
|
|
263
252
|
)
|
|
264
253
|
if credential_id is None:
|
|
265
254
|
return None
|
|
@@ -283,7 +272,7 @@ class AgentAuth:
|
|
|
283
272
|
)
|
|
284
273
|
if status >= _HTTP_BAD_REQUEST:
|
|
285
274
|
raise CredentialStoreError(
|
|
286
|
-
"Agent Auth create oauth-authorization-session failed", status
|
|
275
|
+
"Agent Auth create oauth-authorization-session failed", status
|
|
287
276
|
)
|
|
288
277
|
auth_id = str(body.get("id") or "")
|
|
289
278
|
connect_url = str(body.get("verification_url") or "")
|
|
@@ -304,7 +293,7 @@ class AgentAuth:
|
|
|
304
293
|
"GET", f"/v1/agent-auth/oauth-providers?{urllib.parse.urlencode(query)}"
|
|
305
294
|
)
|
|
306
295
|
if status >= _HTTP_BAD_REQUEST:
|
|
307
|
-
raise CredentialStoreError("Agent Auth list oauth-providers failed", status
|
|
296
|
+
raise CredentialStoreError("Agent Auth list oauth-providers failed", status)
|
|
308
297
|
raw_items = body.get("items")
|
|
309
298
|
for item in raw_items if isinstance(raw_items, list) else []:
|
|
310
299
|
if isinstance(item, Mapping) and item.get("slug") == slug and item.get("id"):
|
|
@@ -340,7 +329,7 @@ class AgentAuth:
|
|
|
340
329
|
)
|
|
341
330
|
status, body = self._request("GET", f"/v1/agent-auth/credentials?{query}")
|
|
342
331
|
if status >= _HTTP_BAD_REQUEST:
|
|
343
|
-
raise CredentialStoreError("Agent Auth list credentials failed", status
|
|
332
|
+
raise CredentialStoreError("Agent Auth list credentials failed", status)
|
|
344
333
|
raw_items = body.get("items")
|
|
345
334
|
items = raw_items if isinstance(raw_items, list) else []
|
|
346
335
|
for item in items:
|
|
@@ -363,7 +352,7 @@ class AgentAuth:
|
|
|
363
352
|
if status == _HTTP_NOT_FOUND:
|
|
364
353
|
return None
|
|
365
354
|
if status >= _HTTP_BAD_REQUEST:
|
|
366
|
-
raise CredentialStoreError("Agent Auth get secret failed", status
|
|
355
|
+
raise CredentialStoreError("Agent Auth get secret failed", status)
|
|
367
356
|
secret = body.get("secret")
|
|
368
357
|
return secret if isinstance(secret, str) and secret else None
|
|
369
358
|
|
|
@@ -375,7 +364,7 @@ class AgentAuth:
|
|
|
375
364
|
if _is_authorization_required(status, body):
|
|
376
365
|
return None
|
|
377
366
|
if status >= _HTTP_BAD_REQUEST:
|
|
378
|
-
raise CredentialStoreError("Agent Auth get oauth2 failed", status
|
|
367
|
+
raise CredentialStoreError("Agent Auth get oauth2 failed", status)
|
|
379
368
|
oauth2 = body.get("oauth2") if isinstance(body.get("oauth2"), dict) else body
|
|
380
369
|
token = oauth2.get("access_token") if isinstance(oauth2, dict) else None
|
|
381
370
|
return token if isinstance(token, str) and token else None
|
|
@@ -401,6 +390,7 @@ class AgentAuth:
|
|
|
401
390
|
return agent_auth_headers(
|
|
402
391
|
api_key=self.context.api_key,
|
|
403
392
|
workspace_id=self.context.workspace_id,
|
|
393
|
+
service_key=self.context.service_key,
|
|
404
394
|
)
|
|
405
395
|
|
|
406
396
|
|
|
@@ -11,68 +11,32 @@ import urllib.error
|
|
|
11
11
|
import urllib.parse
|
|
12
12
|
import urllib.request
|
|
13
13
|
from collections.abc import Mapping
|
|
14
|
-
from typing import TYPE_CHECKING
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
17
|
from http.client import HTTPMessage
|
|
18
18
|
from typing import IO
|
|
19
19
|
|
|
20
20
|
AGENT_AUTH_TIMEOUT_SECONDS = 10.0
|
|
21
|
-
AGENT_AUTH_PROBLEM_HINT_MAX_CHARS = 200
|
|
22
|
-
_PROBLEM_HINT_FIELDS: Final = ("detail", "remedy")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def agent_auth_problem_hint(body: Mapping[str, object] | None) -> str | None:
|
|
26
|
-
"""Return the caller-facing part of an Agent Auth problem+json body.
|
|
27
|
-
|
|
28
|
-
``detail`` names the failure and ``remedy`` names the fix. Nothing else
|
|
29
|
-
from a response body is read, because the rest can quote secret material.
|
|
30
|
-
Both are collapsed to one line and truncated so a long or multi-line body
|
|
31
|
-
cannot reshape an error message.
|
|
32
|
-
"""
|
|
33
|
-
seen: dict[str, None] = {}
|
|
34
|
-
for field in _PROBLEM_HINT_FIELDS:
|
|
35
|
-
value = None if body is None else body.get(field)
|
|
36
|
-
if not isinstance(value, str):
|
|
37
|
-
continue
|
|
38
|
-
text = " ".join(value.split())
|
|
39
|
-
if text:
|
|
40
|
-
seen[text] = None
|
|
41
|
-
if not seen:
|
|
42
|
-
return None
|
|
43
|
-
hint = " ".join(seen)
|
|
44
|
-
if len(hint) > AGENT_AUTH_PROBLEM_HINT_MAX_CHARS:
|
|
45
|
-
return hint[:AGENT_AUTH_PROBLEM_HINT_MAX_CHARS] + "..."
|
|
46
|
-
return hint
|
|
47
21
|
|
|
48
22
|
|
|
49
23
|
class CredentialStoreError(RuntimeError):
|
|
50
24
|
"""Agent Auth could not answer.
|
|
51
25
|
|
|
52
|
-
Callers pass a fixed message
|
|
53
|
-
this error, the logs, or the traceback chain
|
|
54
|
-
problem+json hint, since an arbitrary body can quote secret material.
|
|
26
|
+
Callers pass a fixed message: a response body can quote secret material, so
|
|
27
|
+
it never reaches this error, the logs, or the traceback chain.
|
|
55
28
|
"""
|
|
56
29
|
|
|
57
|
-
def __init__(
|
|
58
|
-
self,
|
|
59
|
-
message: str,
|
|
60
|
-
status: int | None = None,
|
|
61
|
-
problem: Mapping[str, object] | None = None,
|
|
62
|
-
) -> None:
|
|
30
|
+
def __init__(self, message: str, status: int | None = None) -> None:
|
|
63
31
|
self.status = status
|
|
64
|
-
|
|
65
|
-
hint = agent_auth_problem_hint(problem)
|
|
66
|
-
super().__init__(with_status if hint is None else f"{with_status}: {hint}")
|
|
32
|
+
super().__init__(message if status is None else f"{message} (status {status})")
|
|
67
33
|
|
|
68
34
|
|
|
69
35
|
__all__ = [
|
|
70
|
-
"AGENT_AUTH_PROBLEM_HINT_MAX_CHARS",
|
|
71
36
|
"AGENT_AUTH_TIMEOUT_SECONDS",
|
|
72
37
|
"CredentialStoreError",
|
|
73
38
|
"agent_auth_credential_query",
|
|
74
39
|
"agent_auth_headers",
|
|
75
|
-
"agent_auth_problem_hint",
|
|
76
40
|
"request_agent_auth_json",
|
|
77
41
|
]
|
|
78
42
|
|
|
@@ -106,12 +70,16 @@ def agent_auth_headers(
|
|
|
106
70
|
*,
|
|
107
71
|
api_key: str | None = None,
|
|
108
72
|
workspace_id: str | None = None,
|
|
73
|
+
service_key: str | None = None,
|
|
109
74
|
) -> dict[str, str]:
|
|
110
75
|
headers = {
|
|
111
76
|
"accept": "application/json",
|
|
112
77
|
"content-type": "application/json",
|
|
113
78
|
"x-langsmith-product": "mda",
|
|
114
79
|
}
|
|
80
|
+
resolved_service_key = (service_key or "").strip()
|
|
81
|
+
if resolved_service_key:
|
|
82
|
+
headers["x-service-key"] = resolved_service_key
|
|
115
83
|
resolved_api_key = (api_key or "").strip()
|
|
116
84
|
if resolved_api_key:
|
|
117
85
|
headers["x-api-key"] = resolved_api_key
|
|
Binary file
|
|
@@ -16,12 +16,6 @@ from managed_deepagents._channels.trigger.client import (
|
|
|
16
16
|
TriggerClientLike,
|
|
17
17
|
create_trigger_client,
|
|
18
18
|
)
|
|
19
|
-
from managed_deepagents._channels.trigger.identity_context import (
|
|
20
|
-
IdentityContextKey,
|
|
21
|
-
IdentityContextUnavailableError,
|
|
22
|
-
InvalidIdentityContextError,
|
|
23
|
-
principal_id_from_identity_context,
|
|
24
|
-
)
|
|
25
19
|
from managed_deepagents._channels.trigger.types import (
|
|
26
20
|
TRIGGER_SERVICE_KEY_HEADER,
|
|
27
21
|
TriggerChannelEvent,
|
|
@@ -58,7 +52,6 @@ class StartAgentFn(Protocol):
|
|
|
58
52
|
messages: list[dict[str, object]],
|
|
59
53
|
source_provider: str,
|
|
60
54
|
source_thread_key: str,
|
|
61
|
-
user_id: str,
|
|
62
55
|
) -> StartedIngressRun: ...
|
|
63
56
|
|
|
64
57
|
|
|
@@ -71,7 +64,6 @@ class ResumeAgentFn(Protocol):
|
|
|
71
64
|
resume: TriggerChannelResume,
|
|
72
65
|
source_provider: str,
|
|
73
66
|
source_thread_key: str,
|
|
74
|
-
user_id: str,
|
|
75
67
|
) -> StartedIngressRun: ...
|
|
76
68
|
|
|
77
69
|
|
|
@@ -82,7 +74,6 @@ async def _start_event_run(
|
|
|
82
74
|
source_provider: str,
|
|
83
75
|
start_agent: StartAgentFn | None,
|
|
84
76
|
resume_agent: ResumeAgentFn | None,
|
|
85
|
-
user_id: str,
|
|
86
77
|
) -> StartedIngressRun:
|
|
87
78
|
thread_id = _thread_id(event["source_thread_key"])
|
|
88
79
|
if event["type"] == "message":
|
|
@@ -93,7 +84,6 @@ async def _start_event_run(
|
|
|
93
84
|
messages=event["input"]["messages"],
|
|
94
85
|
source_provider=source_provider,
|
|
95
86
|
source_thread_key=event["source_thread_key"],
|
|
96
|
-
user_id=user_id,
|
|
97
87
|
)
|
|
98
88
|
resume = resume_agent or resume_ingress_agent
|
|
99
89
|
return await resume(
|
|
@@ -102,11 +92,10 @@ async def _start_event_run(
|
|
|
102
92
|
resume=event["interaction"]["resume"],
|
|
103
93
|
source_provider=source_provider,
|
|
104
94
|
source_thread_key=event["source_thread_key"],
|
|
105
|
-
user_id=user_id,
|
|
106
95
|
)
|
|
107
96
|
|
|
108
97
|
|
|
109
|
-
async def handle_trigger_channel_event_request(
|
|
98
|
+
async def handle_trigger_channel_event_request(
|
|
110
99
|
request: object,
|
|
111
100
|
*,
|
|
112
101
|
source_provider: str,
|
|
@@ -117,11 +106,9 @@ async def handle_trigger_channel_event_request( # noqa: C901, PLR0911
|
|
|
117
106
|
resume_agent: ResumeAgentFn | None = None,
|
|
118
107
|
observe_agent: ObserveAgentFn | None = None,
|
|
119
108
|
trigger_client: TriggerClientLike | None = None,
|
|
120
|
-
identity_context_key: IdentityContextKey | None = None,
|
|
121
|
-
identity_context_issuer: str | None = None,
|
|
122
109
|
) -> object:
|
|
123
110
|
"""Validate a Trigger message and enqueue its Agent Server run."""
|
|
124
|
-
# TODO: Validate
|
|
111
|
+
# TODO: Validate a per-deployment Trigger↔MDA shared secret.
|
|
125
112
|
if not _nonblank_header(request, TRIGGER_SERVICE_KEY_HEADER):
|
|
126
113
|
return _json_response({"error": "unauthorized"}, status_code=401)
|
|
127
114
|
|
|
@@ -130,32 +117,6 @@ async def handle_trigger_channel_event_request( # noqa: C901, PLR0911
|
|
|
130
117
|
except (TypeError, ValueError, UnicodeDecodeError):
|
|
131
118
|
logger.warning("invalid channel event")
|
|
132
119
|
return _json_response({"error": "invalid channel event"}, status_code=400)
|
|
133
|
-
|
|
134
|
-
try:
|
|
135
|
-
user_id = _principal_for_event(
|
|
136
|
-
event,
|
|
137
|
-
identity_context_key=identity_context_key,
|
|
138
|
-
identity_context_issuer=identity_context_issuer,
|
|
139
|
-
)
|
|
140
|
-
except _MissingIdentityContextError:
|
|
141
|
-
logger.warning("missing channel identity_context")
|
|
142
|
-
return _json_response({"error": "missing channel identity_context"}, status_code=400)
|
|
143
|
-
except IdentityContextUnavailableError as error:
|
|
144
|
-
logger.error(
|
|
145
|
-
"channel identity_context unavailable (%s)",
|
|
146
|
-
logger.safe_error_label(error),
|
|
147
|
-
)
|
|
148
|
-
return _json_response(
|
|
149
|
-
{"error": "identity_context verification unavailable"},
|
|
150
|
-
status_code=503,
|
|
151
|
-
)
|
|
152
|
-
except InvalidIdentityContextError as error:
|
|
153
|
-
logger.warning(
|
|
154
|
-
"invalid channel identity_context (%s)",
|
|
155
|
-
logger.safe_error_label(error),
|
|
156
|
-
)
|
|
157
|
-
return _json_response({"error": "invalid channel identity_context"}, status_code=400)
|
|
158
|
-
|
|
159
120
|
logger.info(
|
|
160
121
|
"Channel incoming event (type=%s, event_id=%s, delivery_id=%s, channel_id=%s)",
|
|
161
122
|
event["type"],
|
|
@@ -198,7 +159,6 @@ async def handle_trigger_channel_event_request( # noqa: C901, PLR0911
|
|
|
198
159
|
source_provider=source_provider,
|
|
199
160
|
start_agent=start_agent,
|
|
200
161
|
resume_agent=resume_agent,
|
|
201
|
-
user_id=user_id,
|
|
202
162
|
)
|
|
203
163
|
except Exception as error: # noqa: BLE001 - never expose Agent Server details in the response
|
|
204
164
|
logger.error(
|
|
@@ -213,9 +173,6 @@ async def handle_trigger_channel_event_request( # noqa: C901, PLR0911
|
|
|
213
173
|
started=started,
|
|
214
174
|
client=client,
|
|
215
175
|
observe_agent=observe_agent,
|
|
216
|
-
source_provider=source_provider,
|
|
217
|
-
source_thread_key=event["source_thread_key"],
|
|
218
|
-
user_id=user_id,
|
|
219
176
|
)
|
|
220
177
|
|
|
221
178
|
schedule_ingress_work(
|
|
@@ -235,9 +192,6 @@ async def _observe_accepted_run(
|
|
|
235
192
|
started: StartedIngressRun,
|
|
236
193
|
client: TriggerClientLike,
|
|
237
194
|
observe_agent: ObserveAgentFn | None,
|
|
238
|
-
source_provider: str | None = None,
|
|
239
|
-
source_thread_key: str | None = None,
|
|
240
|
-
user_id: str,
|
|
241
195
|
) -> None:
|
|
242
196
|
async def lifecycle(
|
|
243
197
|
event_type: TriggerChannelRuntimeEventType,
|
|
@@ -258,13 +212,11 @@ async def _observe_accepted_run(
|
|
|
258
212
|
)
|
|
259
213
|
|
|
260
214
|
await lifecycle("run.started")
|
|
215
|
+
observe = observe_agent or observe_ingress_agent
|
|
261
216
|
try:
|
|
262
|
-
observed = await
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
source_provider=source_provider,
|
|
266
|
-
source_thread_key=source_thread_key,
|
|
267
|
-
user_id=user_id,
|
|
217
|
+
observed = await observe(
|
|
218
|
+
thread_id=started["thread_id"],
|
|
219
|
+
run_id=started["run_id"],
|
|
268
220
|
)
|
|
269
221
|
except Exception: # noqa: BLE001 - observation details stay internal
|
|
270
222
|
await lifecycle("run.failed")
|
|
@@ -311,53 +263,6 @@ async def _observe_accepted_run(
|
|
|
311
263
|
await lifecycle("run.completed")
|
|
312
264
|
|
|
313
265
|
|
|
314
|
-
async def _observe_run(
|
|
315
|
-
*,
|
|
316
|
-
started: StartedIngressRun,
|
|
317
|
-
observe_agent: ObserveAgentFn | None,
|
|
318
|
-
source_provider: str | None,
|
|
319
|
-
source_thread_key: str | None,
|
|
320
|
-
user_id: str,
|
|
321
|
-
) -> ObservedIngressRun:
|
|
322
|
-
if observe_agent is not None:
|
|
323
|
-
return await observe_agent(
|
|
324
|
-
**_observation_kwargs(
|
|
325
|
-
started=started,
|
|
326
|
-
source_provider=source_provider,
|
|
327
|
-
source_thread_key=source_thread_key,
|
|
328
|
-
user_id=user_id,
|
|
329
|
-
)
|
|
330
|
-
)
|
|
331
|
-
if source_provider is None or source_thread_key is None:
|
|
332
|
-
raise RuntimeError("channel observation context is not configured")
|
|
333
|
-
return await observe_ingress_agent(
|
|
334
|
-
thread_id=started["thread_id"],
|
|
335
|
-
run_id=started["run_id"],
|
|
336
|
-
source_provider=source_provider,
|
|
337
|
-
source_thread_key=source_thread_key,
|
|
338
|
-
user_id=user_id,
|
|
339
|
-
)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
def _observation_kwargs(
|
|
343
|
-
*,
|
|
344
|
-
started: StartedIngressRun,
|
|
345
|
-
source_provider: str | None,
|
|
346
|
-
source_thread_key: str | None,
|
|
347
|
-
user_id: str,
|
|
348
|
-
) -> dict[str, object]:
|
|
349
|
-
kwargs: dict[str, object] = {
|
|
350
|
-
"thread_id": started["thread_id"],
|
|
351
|
-
"run_id": started["run_id"],
|
|
352
|
-
"user_id": user_id,
|
|
353
|
-
}
|
|
354
|
-
if source_provider is None or source_thread_key is None:
|
|
355
|
-
return kwargs
|
|
356
|
-
kwargs["source_provider"] = source_provider
|
|
357
|
-
kwargs["source_thread_key"] = source_thread_key
|
|
358
|
-
return kwargs
|
|
359
|
-
|
|
360
|
-
|
|
361
266
|
def _is_interrupted_run(observed: ObservedIngressRun) -> bool:
|
|
362
267
|
# Agent Server can report status "success" after join while values still
|
|
363
268
|
# carry `__interrupt__`. Trust either signal.
|
|
@@ -410,27 +315,6 @@ def _thread_id(source_thread_key: str) -> str:
|
|
|
410
315
|
return str(uuid.uuid5(uuid.NAMESPACE_URL, source_thread_key))
|
|
411
316
|
|
|
412
317
|
|
|
413
|
-
class _MissingIdentityContextError(ValueError):
|
|
414
|
-
"""A message event arrived without a Trigger identity_context token."""
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
def _principal_for_event(
|
|
418
|
-
event: TriggerChannelEvent,
|
|
419
|
-
*,
|
|
420
|
-
identity_context_key: IdentityContextKey | None,
|
|
421
|
-
identity_context_issuer: str | None,
|
|
422
|
-
) -> str:
|
|
423
|
-
"""Resolve the verified principal required on every Trigger delivery."""
|
|
424
|
-
raw_identity_context = event.get("identity_context")
|
|
425
|
-
if not isinstance(raw_identity_context, str) or not raw_identity_context.strip():
|
|
426
|
-
raise _MissingIdentityContextError
|
|
427
|
-
return principal_id_from_identity_context(
|
|
428
|
-
raw_identity_context,
|
|
429
|
-
key=identity_context_key,
|
|
430
|
-
issuer=identity_context_issuer,
|
|
431
|
-
)
|
|
432
|
-
|
|
433
|
-
|
|
434
318
|
def _resolve_assistant_id(override: str | None) -> str:
|
|
435
319
|
if override is not None and (explicit := override.strip()):
|
|
436
320
|
return explicit
|
|
@@ -25,7 +25,7 @@ class TriggerChannelMessageInput(TypedDict):
|
|
|
25
25
|
messages: list[dict[str, object]]
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class
|
|
28
|
+
class TriggerChannelMessageEvent(TypedDict):
|
|
29
29
|
version: Literal["1"]
|
|
30
30
|
type: Literal["message"]
|
|
31
31
|
event_id: str
|
|
@@ -37,10 +37,6 @@ class TriggerChannelMessageEventRequired(TypedDict):
|
|
|
37
37
|
input: TriggerChannelMessageInput
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
class TriggerChannelMessageEvent(TriggerChannelMessageEventRequired, total=False):
|
|
41
|
-
identity_context: str | None
|
|
42
|
-
|
|
43
|
-
|
|
44
40
|
# Keyed LangGraph resume: `{interrupt_id: value}`. Values are opaque
|
|
45
41
|
# (`interrupt()` results or HITL `{decisions}`).
|
|
46
42
|
TriggerChannelResume: TypeAlias = dict[str, object]
|
|
@@ -51,7 +47,7 @@ class TriggerChannelInterruptResumeInteraction(TypedDict):
|
|
|
51
47
|
resume: TriggerChannelResume
|
|
52
48
|
|
|
53
49
|
|
|
54
|
-
class
|
|
50
|
+
class TriggerChannelInterruptResumeEvent(TypedDict):
|
|
55
51
|
version: Literal["1"]
|
|
56
52
|
type: Literal["interrupt_resume"]
|
|
57
53
|
event_id: str
|
|
@@ -63,10 +59,6 @@ class TriggerChannelInterruptResumeEventRequired(TypedDict):
|
|
|
63
59
|
interaction: TriggerChannelInterruptResumeInteraction
|
|
64
60
|
|
|
65
61
|
|
|
66
|
-
class TriggerChannelInterruptResumeEvent(TriggerChannelInterruptResumeEventRequired, total=False):
|
|
67
|
-
identity_context: str | None
|
|
68
|
-
|
|
69
|
-
|
|
70
62
|
TriggerChannelEvent = TriggerChannelMessageEvent | TriggerChannelInterruptResumeEvent
|
|
71
63
|
|
|
72
64
|
|
|
@@ -178,7 +170,6 @@ class _MessageEventModel(_WireModel):
|
|
|
178
170
|
source_thread_key: _NonBlankString
|
|
179
171
|
address: SkipValidation[dict[str, object]]
|
|
180
172
|
input: _InputModel
|
|
181
|
-
identity_context: str | None = None
|
|
182
173
|
|
|
183
174
|
@field_validator("address", mode="before")
|
|
184
175
|
@classmethod
|
|
@@ -206,7 +197,6 @@ class _InterruptResumeEventModel(_WireModel):
|
|
|
206
197
|
source_thread_key: _NonBlankString
|
|
207
198
|
address: SkipValidation[dict[str, object]]
|
|
208
199
|
interaction: _InterruptResumeInteractionModel
|
|
209
|
-
identity_context: str | None = None
|
|
210
200
|
|
|
211
201
|
@field_validator("address", mode="before")
|
|
212
202
|
@classmethod
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""``connections.get(...)`` — read a managed connection from a tool.
|
|
2
2
|
|
|
3
|
-
``{ type: "agent" }`` resolves material
|
|
3
|
+
``{ type: "agent" }`` resolves shared workspace material on the connection row.
|
|
4
4
|
``{ type: "user" }`` resolves per-caller grants (OAuth or opaque). Resolution
|
|
5
5
|
reads Agent Auth on every await and never caches.
|
|
6
6
|
|
|
@@ -157,10 +157,11 @@ class _AgentAuthConfig:
|
|
|
157
157
|
api_key: str
|
|
158
158
|
deployment_id: str | None
|
|
159
159
|
workspace_id: str
|
|
160
|
+
service_key: str | None = None
|
|
160
161
|
|
|
161
162
|
|
|
162
163
|
class AgentAuthConnectionStore:
|
|
163
|
-
"""Store backed by connection rows
|
|
164
|
+
"""Store backed by workspace connection rows in Agent Auth."""
|
|
164
165
|
|
|
165
166
|
def __init__(
|
|
166
167
|
self,
|
|
@@ -187,6 +188,7 @@ class AgentAuthConnectionStore:
|
|
|
187
188
|
principal_id=owner_id,
|
|
188
189
|
api_key=config.api_key,
|
|
189
190
|
workspace_id=config.workspace_id,
|
|
191
|
+
service_key=config.service_key,
|
|
190
192
|
),
|
|
191
193
|
request=self._bind_request(config),
|
|
192
194
|
timeout=self._timeout,
|
|
@@ -213,6 +215,7 @@ class AgentAuthConnectionStore:
|
|
|
213
215
|
headers = agent_auth_headers(
|
|
214
216
|
api_key=config.api_key,
|
|
215
217
|
workspace_id=config.workspace_id,
|
|
218
|
+
service_key=config.service_key,
|
|
216
219
|
)
|
|
217
220
|
response = http_get(url, headers, timeout)
|
|
218
221
|
status = response.get("status")
|
|
@@ -251,18 +254,19 @@ def _lookup_workspace_connection(
|
|
|
251
254
|
if row is None:
|
|
252
255
|
return None
|
|
253
256
|
if owner["type"] == "user":
|
|
254
|
-
return _lookup_user_connection_material(client, row)
|
|
257
|
+
return _lookup_user_connection_material(client, slug, row)
|
|
255
258
|
return _lookup_agent_connection_material(client, slug, row)
|
|
256
259
|
|
|
257
260
|
|
|
258
261
|
def _lookup_user_connection_material(
|
|
259
262
|
client: AgentAuth,
|
|
263
|
+
slug: str,
|
|
260
264
|
row: WorkspaceConnection,
|
|
261
265
|
) -> str | None:
|
|
262
266
|
if row.kind == "secret":
|
|
263
|
-
return client.
|
|
267
|
+
return client.read_user_secret(slug)
|
|
264
268
|
if row.kind == "oauth2":
|
|
265
|
-
return client.
|
|
269
|
+
return client.read_user_access_token(slug)
|
|
266
270
|
return None
|
|
267
271
|
|
|
268
272
|
|
|
@@ -425,6 +429,7 @@ def resolve_agent_auth_context(
|
|
|
425
429
|
principal_id=resolve_owner_id({"type": "user"}, identity, config.deployment_id),
|
|
426
430
|
api_key=config.api_key,
|
|
427
431
|
workspace_id=config.workspace_id,
|
|
432
|
+
service_key=config.service_key,
|
|
428
433
|
)
|
|
429
434
|
|
|
430
435
|
|
|
@@ -438,11 +443,13 @@ def _resolve_agent_auth_config(env: Mapping[str, str] | None) -> _AgentAuthConfi
|
|
|
438
443
|
raise CredentialStoreError(
|
|
439
444
|
f"connections are not configured: {_WORKSPACE_ID_ENV} is not set"
|
|
440
445
|
)
|
|
446
|
+
service_key = (resolved.get("MDA_AGENT_AUTH_SERVICE_KEY") or "").strip() or None
|
|
441
447
|
return _AgentAuthConfig(
|
|
442
448
|
base_url=resolve_agent_auth_base_url(resolved),
|
|
443
449
|
api_key=api_key,
|
|
444
450
|
deployment_id=(resolved.get(DEPLOYMENT_ID_ENV) or "").strip() or None,
|
|
445
451
|
workspace_id=workspace_id,
|
|
452
|
+
service_key=service_key,
|
|
446
453
|
)
|
|
447
454
|
|
|
448
455
|
|
|
@@ -190,9 +190,9 @@ def _read_user_connection_grants(
|
|
|
190
190
|
raise CredentialStoreError(f"workspace connection {slug!r} is not configured")
|
|
191
191
|
kind: Literal["oauth2", "secret"] = "secret" if row.kind == "secret" else "oauth2"
|
|
192
192
|
token = (
|
|
193
|
-
agent_auth.
|
|
193
|
+
agent_auth.read_user_secret(slug)
|
|
194
194
|
if kind == "secret"
|
|
195
|
-
else agent_auth.
|
|
195
|
+
else agent_auth.read_user_access_token(slug)
|
|
196
196
|
)
|
|
197
197
|
grants.append(_UserConnectionGrant(slug=slug, kind=kind, token=token))
|
|
198
198
|
return grants
|
|
@@ -124,9 +124,6 @@ CONNECTOR_EVENTS_PATH_SUFFIX = "/events"
|
|
|
124
124
|
CHANNEL_EVENTS_USER = "mda:channel-events"
|
|
125
125
|
CHANNEL_SERVICE_USER = "mda:channel"
|
|
126
126
|
|
|
127
|
-
#: Owner-key namespace for threads owned by a channel source conversation.
|
|
128
|
-
CHANNEL_THREAD_OWNER_PREFIX = "mda:source-thread:"
|
|
129
|
-
|
|
130
127
|
_BUILTIN_CHANNEL_SOURCE_PROVIDERS = frozenset({"slack", "github"})
|
|
131
128
|
|
|
132
129
|
|
|
@@ -206,20 +203,16 @@ def build_managed_auth(identity: IdentityDefinitionLike) -> AuthInstance:
|
|
|
206
203
|
return await _authenticate(cfg, http, auth_cls, headers, authorization, path, method)
|
|
207
204
|
|
|
208
205
|
async def scope_threads(ctx: object, value: object) -> Mapping[str, str] | None:
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
#
|
|
212
|
-
#
|
|
213
|
-
#
|
|
206
|
+
# Studio is deliberately not exempt from thread ownership. Studio
|
|
207
|
+
# identity is reachable by any holder of a workspace LangSmith API key
|
|
208
|
+
# (the Agent Server honours ``x-auth-scheme: langsmith``), so unscoping
|
|
209
|
+
# it would let that key read and write every caller's threads. Under
|
|
210
|
+
# ``mda dev`` the generated ``langgraph.json`` disables the Studio
|
|
211
|
+
# scheme, so local Studio arrives as the local-dev service principal and
|
|
212
|
+
# stays unscoped through the check below.
|
|
214
213
|
user = getattr(ctx, "user", ctx)
|
|
215
214
|
if is_local_dev_unscoped_user(user):
|
|
216
215
|
return None
|
|
217
|
-
if (
|
|
218
|
-
is_studio_user is not None
|
|
219
|
-
and is_studio_user(user)
|
|
220
|
-
and getattr(ctx, "action", "") in {"read", "search"}
|
|
221
|
-
):
|
|
222
|
-
return None
|
|
223
216
|
key = _thread_owner_key(user)
|
|
224
217
|
if isinstance(value, dict):
|
|
225
218
|
payload = cast("JsonObject", value)
|
|
@@ -667,50 +660,15 @@ def _has_valid_ingress_secret(headers: Mapping[str, object]) -> bool:
|
|
|
667
660
|
def _thread_owner_key(user: object) -> str:
|
|
668
661
|
"""Derive the owner value ``@auth.on.threads`` filters on.
|
|
669
662
|
|
|
670
|
-
A managed channel thread is owned by its source conversation; every other
|
|
671
|
-
caller owns its threads by user id. Channel threads are addressed by
|
|
672
|
-
``source_thread_key`` and the deliveries that reach one can legitimately
|
|
673
|
-
carry different principals: the Trigger Server does not attach
|
|
674
|
-
``identity_context`` to every envelope, and any participant may reply in a
|
|
675
|
-
shared thread or answer an interrupt someone else raised. Keying such a
|
|
676
|
-
thread on the delivering principal denies every later delivery that does
|
|
677
|
-
not match.
|
|
678
|
-
|
|
679
|
-
Only managed ingress sets both source stamps, and only over a trusted
|
|
680
|
-
loopback credential — a client presenting them is rejected in
|
|
681
|
-
:func:`_reject_untrusted_handoff_headers`. ``mda_user_id`` still carries the
|
|
682
|
-
principal, so credential resolution and store scoping stay per caller.
|
|
683
|
-
|
|
684
663
|
Principals resolved by ``authenticate`` always carry ``mda_user_id``. A
|
|
685
664
|
Studio user does not: the Agent Server builds it for
|
|
686
665
|
``x-auth-scheme: langsmith`` without consulting managed auth, so it exposes
|
|
687
666
|
only ``identity``. Fall back to that rather than bucketing every such caller
|
|
688
667
|
under an empty owner.
|
|
689
668
|
"""
|
|
690
|
-
conversation = _channel_conversation_owner_key(user)
|
|
691
|
-
if conversation is not None:
|
|
692
|
-
return conversation
|
|
693
669
|
return str(_field(user, "mda_user_id") or _field(user, "identity") or "")
|
|
694
670
|
|
|
695
671
|
|
|
696
|
-
def _channel_conversation_owner_key(user: object) -> str | None:
|
|
697
|
-
"""Owner key for a channel ingress caller, or ``None`` for anyone else.
|
|
698
|
-
|
|
699
|
-
The prefix keeps conversation owners in a namespace no ``mda_user_id`` can
|
|
700
|
-
reach (those are UUIDs, emails, or ``mda:*`` service ids), so a source
|
|
701
|
-
thread key can never collide with a caller.
|
|
702
|
-
"""
|
|
703
|
-
provider = _trimmed_stamp(_field(user, "mda_source_provider"))
|
|
704
|
-
thread_key = _trimmed_stamp(_field(user, "mda_source_thread_id"))
|
|
705
|
-
if not provider or not thread_key:
|
|
706
|
-
return None
|
|
707
|
-
return f"{CHANNEL_THREAD_OWNER_PREFIX}{provider}:{thread_key}"
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
def _trimmed_stamp(value: object) -> str:
|
|
711
|
-
return value.strip() if isinstance(value, str) else ""
|
|
712
|
-
|
|
713
|
-
|
|
714
672
|
#: LangGraph Store namespace prefix for Slack ↔ actor account links.
|
|
715
673
|
IDENTITY_LINKS_STORE_PREFIX = "mda_identity_links"
|
|
716
674
|
|
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
|
+
CHANNEL_SERVICE_USER,
|
|
22
23
|
HANDOFF_SECRET_ENV,
|
|
23
24
|
HANDOFF_SECRET_HEADER,
|
|
24
25
|
INGRESS_SECRET_ENV,
|
|
@@ -72,7 +73,6 @@ class ObservedIngressRun(TypedDict):
|
|
|
72
73
|
class _IngressSource(TypedDict):
|
|
73
74
|
provider: str
|
|
74
75
|
thread_key: str
|
|
75
|
-
user_id: str
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
class _ThreadsClient(Protocol):
|
|
@@ -165,8 +165,8 @@ async def ingress_source_thread_exists(
|
|
|
165
165
|
) -> bool:
|
|
166
166
|
"""True when an MDA thread already exists for this source-thread key.
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
Call with the **resolved** actor (post identity remap) so ownership matches
|
|
169
|
+
the thread created on the first run.
|
|
170
170
|
"""
|
|
171
171
|
if not _has_loopback_credential():
|
|
172
172
|
return False
|
|
@@ -236,18 +236,10 @@ async def start_ingress_agent(
|
|
|
236
236
|
messages: list[dict[str, object]],
|
|
237
237
|
source_provider: str,
|
|
238
238
|
source_thread_key: str,
|
|
239
|
-
user_id: str,
|
|
240
239
|
) -> StartedIngressRun:
|
|
241
|
-
"""Create a Trigger-addressed thread and enqueue its run.
|
|
242
|
-
|
|
243
|
-
``user_id`` is the Agent Auth principal UUID from Trigger ``identity_context``.
|
|
244
|
-
"""
|
|
240
|
+
"""Create a Trigger-addressed thread and enqueue its run."""
|
|
245
241
|
client = _create_trigger_ingress_loopback_client(
|
|
246
|
-
{
|
|
247
|
-
"provider": source_provider,
|
|
248
|
-
"thread_key": source_thread_key,
|
|
249
|
-
"user_id": user_id,
|
|
250
|
-
}
|
|
242
|
+
{"provider": source_provider, "thread_key": source_thread_key}
|
|
251
243
|
)
|
|
252
244
|
await client.threads.create(
|
|
253
245
|
thread_id=thread_id,
|
|
@@ -276,18 +268,10 @@ async def resume_ingress_agent(
|
|
|
276
268
|
resume: dict[str, object],
|
|
277
269
|
source_provider: str,
|
|
278
270
|
source_thread_key: str,
|
|
279
|
-
user_id: str,
|
|
280
271
|
) -> StartedIngressRun:
|
|
281
|
-
"""Resume a Trigger-addressed thread with a keyed interrupt response.
|
|
282
|
-
|
|
283
|
-
``user_id`` is the Agent Auth principal UUID from Trigger ``identity_context``.
|
|
284
|
-
"""
|
|
272
|
+
"""Resume a Trigger-addressed thread with a keyed interrupt response."""
|
|
285
273
|
client = _create_trigger_ingress_loopback_client(
|
|
286
|
-
{
|
|
287
|
-
"provider": source_provider,
|
|
288
|
-
"thread_key": source_thread_key,
|
|
289
|
-
"user_id": user_id,
|
|
290
|
-
}
|
|
274
|
+
{"provider": source_provider, "thread_key": source_thread_key}
|
|
291
275
|
)
|
|
292
276
|
run = await client.runs.create(
|
|
293
277
|
thread_id,
|
|
@@ -308,18 +292,9 @@ async def observe_ingress_agent(
|
|
|
308
292
|
*,
|
|
309
293
|
thread_id: str,
|
|
310
294
|
run_id: str,
|
|
311
|
-
source_provider: str,
|
|
312
|
-
source_thread_key: str,
|
|
313
|
-
user_id: str,
|
|
314
295
|
) -> ObservedIngressRun:
|
|
315
|
-
"""Join an accepted run
|
|
316
|
-
client = _create_trigger_ingress_loopback_client(
|
|
317
|
-
{
|
|
318
|
-
"provider": source_provider,
|
|
319
|
-
"thread_key": source_thread_key,
|
|
320
|
-
"user_id": user_id,
|
|
321
|
-
}
|
|
322
|
-
)
|
|
296
|
+
"""Join an accepted run and read its terminal status."""
|
|
297
|
+
client = _create_trigger_ingress_loopback_client()
|
|
323
298
|
values = await client.runs.join(thread_id, run_id)
|
|
324
299
|
run = await client.runs.get(thread_id, run_id)
|
|
325
300
|
status = run.get("status") if isinstance(run, Mapping) else None
|
|
@@ -337,7 +312,7 @@ async def observe_ingress_agent(
|
|
|
337
312
|
|
|
338
313
|
|
|
339
314
|
def _create_trigger_ingress_loopback_client(
|
|
340
|
-
source: _IngressSource,
|
|
315
|
+
source: _IngressSource | None = None,
|
|
341
316
|
) -> _IngressLoopbackClient:
|
|
342
317
|
api_key = os.environ.get(LANGCHAIN_API_KEY_ENV, "").strip()
|
|
343
318
|
if not api_key:
|
|
@@ -347,17 +322,10 @@ def _create_trigger_ingress_loopback_client(
|
|
|
347
322
|
)
|
|
348
323
|
module = import_module("langgraph_sdk")
|
|
349
324
|
get_client = cast("_GetClient", module.get_client)
|
|
350
|
-
|
|
351
|
-
if not
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
"channel run requires an identity_context principal",
|
|
355
|
-
)
|
|
356
|
-
headers = {
|
|
357
|
-
USER_HEADER: user_id,
|
|
358
|
-
SOURCE_PROVIDER_HEADER: source["provider"],
|
|
359
|
-
SOURCE_THREAD_HEADER: source["thread_key"],
|
|
360
|
-
}
|
|
325
|
+
headers = {USER_HEADER: CHANNEL_SERVICE_USER}
|
|
326
|
+
if source is not None:
|
|
327
|
+
headers[SOURCE_PROVIDER_HEADER] = source["provider"]
|
|
328
|
+
headers[SOURCE_THREAD_HEADER] = source["thread_key"]
|
|
361
329
|
return get_client(
|
|
362
330
|
url=resolve_loopback_api_url(),
|
|
363
331
|
api_key=api_key,
|
{managed_deepagents-0.6.2.dev36.dist-info → managed_deepagents-0.6.2.dev37.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.dev37
|
|
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
|
|
@@ -282,9 +282,7 @@ when a grant is missing. `mda dev` has no deployment to hold user grants, so
|
|
|
282
282
|
a user-owned connection fails there with an actionable error; deploy the agent
|
|
283
283
|
to exercise one.
|
|
284
284
|
|
|
285
|
-
|
|
286
|
-
`mda connections create`. Agent-owned opaque secrets are scoped to that
|
|
287
|
-
deployment, so the command refuses to create one before `mda deploy`.
|
|
285
|
+
Provision workspace connections with `mda connections create` before deploy.
|
|
288
286
|
`mda connections check` verifies manifest slugs exist in the workspace.
|
|
289
287
|
|
|
290
288
|
Use `include_tools` or `exclude_tools` inside a server config to select a
|
{managed_deepagents-0.6.2.dev36.dist-info → managed_deepagents-0.6.2.dev37.dist-info}/RECORD
RENAMED
|
@@ -1,20 +1,20 @@
|
|
|
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=
|
|
4
|
-
managed_deepagents/_agent_auth_http.py,sha256=
|
|
3
|
+
managed_deepagents/_agent_auth.py,sha256=3rA39j_3PFROrtsIr5OHkAO_FgAHfASM-ee0vGhPGu8,16650
|
|
4
|
+
managed_deepagents/_agent_auth_http.py,sha256=DtspEFmWHTtpp3uqELw1-wuHiLbAx_ers9JW18J3Coc,3924
|
|
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=N6luj1YMpBPjpH-WnGc6AIGNTE9DnZoBWAlWcFRSo2I,16518
|
|
8
8
|
managed_deepagents/_connector.py,sha256=Syp0SRs1oFCUUltIUmZbSNi1Ys_PxObVdiAi-I9GBzs,8040
|
|
9
9
|
managed_deepagents/_cors.py,sha256=V96RPrqewdRWG5RpEMEBnPuRq9_zF8nf4vf99k5dkuI,7597
|
|
10
|
-
managed_deepagents/_credential_gate.py,sha256=
|
|
10
|
+
managed_deepagents/_credential_gate.py,sha256=WlryhbWHtg1FN5IdadiOvgT3JX3G78DtWVGW0wLWvz4,12252
|
|
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=BydhEhqxkb9mUg04hBZlGeyxzlKAogrMfu-r4X_bzdU,30983
|
|
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
16
|
managed_deepagents/_identity_runtime.py,sha256=vsshj0TNWd-rXUhVsPAqXIkANunQT7jI94CWgzq-1cY,24409
|
|
17
|
-
managed_deepagents/_ingress.py,sha256=
|
|
17
|
+
managed_deepagents/_ingress.py,sha256=mPIb1o60G_pCg4Qotwt-SUloCxY1xXfUTSyN_BPZNts,11581
|
|
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
|
|
@@ -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=riwU4PKA6DzRCX00K8ObfnFNO2lSlx_Zx1Lj9yOCe3c,3871744
|
|
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
|
|
@@ -50,9 +50,8 @@ managed_deepagents/_channels/slack/transport.py,sha256=_xQmzK2hBpPOoY3zKFhzc3vvl
|
|
|
50
50
|
managed_deepagents/_channels/trigger/__init__.py,sha256=bWj66XoPbDdrztPUCKBuSJBBS2pBocn4EA8FDE3WeBA,48
|
|
51
51
|
managed_deepagents/_channels/trigger/channel.py,sha256=rKSvOABOZX58ZIvZ3hKcYxvTx9npQ0YXPQWk9JEy6XE,5108
|
|
52
52
|
managed_deepagents/_channels/trigger/client.py,sha256=cM97gzYWUTFC9o3uoveFe9-HxZc6_VqiqC8u4fKqjXc,8572
|
|
53
|
-
managed_deepagents/_channels/trigger/http.py,sha256=
|
|
54
|
-
managed_deepagents/_channels/trigger/
|
|
55
|
-
managed_deepagents/_channels/trigger/types.py,sha256=42JMOFXP9MfqwQ66SFlAiwC7q_AnNIJXBts3sAujxac,6625
|
|
53
|
+
managed_deepagents/_channels/trigger/http.py,sha256=L5ILylDH0BXO5MAfHpXGF742vfCxnRhGLNbLn5kQUCY,12303
|
|
54
|
+
managed_deepagents/_channels/trigger/types.py,sha256=G4kW1zKV2JlNyV7evGra-lSrZ_ZpVsRQm2iSx0iLEGE,6277
|
|
56
55
|
managed_deepagents/_connectors/__init__.py,sha256=c_SdH3T7T6bgX7-I5jxAqDzHojmqVUCX0AXRI7g5Ovs,575
|
|
57
56
|
managed_deepagents/_connectors/tool_selection.py,sha256=xMvohEvg_rguQvziUXzxKn4V140mmearZHel97F8tVs,2978
|
|
58
57
|
managed_deepagents/_connectors/mcp/__init__.py,sha256=3AL429g5djYEYuiuSqsxj2VESpTkyU2NeBIJsHlWVk4,11162
|
|
@@ -69,9 +68,9 @@ managed_deepagents/identity/__init__.py,sha256=Slm-oEqKxbaZEQsMqHfD3LbG5M4vH2lKU
|
|
|
69
68
|
managed_deepagents/identity/claims.py,sha256=prby7r_8Lww3MmoBm8PX6PsSGE3GwSHkr8mVIz_08nU,1180
|
|
70
69
|
managed_deepagents/identity/options.py,sha256=x_yMbXEFR7zXyehEjDHG2qjAlukXjrRBcQLcM6IndLM,2550
|
|
71
70
|
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.
|
|
71
|
+
managed_deepagents-0.6.2.dev37.dist-info/licenses/LICENSE,sha256=WnPZQQ608ojZUkXl7BW6VbKloIAibZiGm1K6cjeZOL8,1066
|
|
72
|
+
managed_deepagents-0.6.2.dev37.dist-info/METADATA,sha256=iOVDEIhlqrDnlDJbX7MW9LrTY1u0ExCd_4V0dWCHP38,13582
|
|
73
|
+
managed_deepagents-0.6.2.dev37.dist-info/WHEEL,sha256=3I5VVWZdsFlU417aCS2bMRuerS8wsfvBq99MyatBV3A,97
|
|
74
|
+
managed_deepagents-0.6.2.dev37.dist-info/entry_points.txt,sha256=ziZqkKuaDO3qHCjKmBhrx9cm3ok8bZ9xcEDpUTj4unM,53
|
|
75
|
+
managed_deepagents-0.6.2.dev37.dist-info/top_level.txt,sha256=fdBo4O0Hlol_w022tTLB5ftZpamb4yJoQ3MUvjgTGfA,19
|
|
76
|
+
managed_deepagents-0.6.2.dev37.dist-info/RECORD,,
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
"""Verify Trigger Server ``identity_context`` JWTs into Agent Auth principal ids.
|
|
2
|
-
|
|
3
|
-
Agent Auth signs these with the LangSmith EdDSA JWKS published at
|
|
4
|
-
``{LANGSMITH_ENDPOINT}/.well-known/jwks.json``. MDA verifies the signature
|
|
5
|
-
(and typ/version/aud/iss/exp/principal UUID) before stamping ``x-mda-user-id``.
|
|
6
|
-
Delivery still requires Trigger's ``x-service-key`` header; signature verify is
|
|
7
|
-
what stops a forged principal when that gate is weak.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
import os
|
|
13
|
-
from collections.abc import Mapping
|
|
14
|
-
from uuid import UUID
|
|
15
|
-
|
|
16
|
-
import jwt
|
|
17
|
-
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
18
|
-
from jwt import PyJWK, PyJWKClient
|
|
19
|
-
|
|
20
|
-
IdentityContextKey = Ed25519PublicKey | PyJWK
|
|
21
|
-
|
|
22
|
-
IDENTITY_CONTEXT_TOKEN_TYP = "identity-context+jwt" # noqa: S105
|
|
23
|
-
IDENTITY_CONTEXT_AUDIENCE = "mda"
|
|
24
|
-
IDENTITY_CONTEXT_VERSION = "1"
|
|
25
|
-
IDENTITY_CONTEXT_ALGORITHMS = ("EdDSA",)
|
|
26
|
-
_DEFAULT_LANGSMITH_ENDPOINT = "https://api.smith.langchain.com"
|
|
27
|
-
_JWKS_HTTP_TIMEOUT_S = 5
|
|
28
|
-
|
|
29
|
-
_jwks_clients: dict[str, PyJWKClient] = {}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class InvalidIdentityContextError(ValueError):
|
|
33
|
-
"""Raised when a Trigger ``identity_context`` token is unusable."""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class IdentityContextUnavailableError(RuntimeError):
|
|
37
|
-
"""JWKS / network failure while verifying identity_context. Callers should 5xx."""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def resolve_identity_context_issuer(
|
|
41
|
-
env: Mapping[str, str] | None = None,
|
|
42
|
-
) -> str:
|
|
43
|
-
"""LangSmith API origin used as identity-context ``iss`` and JWKS host."""
|
|
44
|
-
source = env if env is not None else os.environ
|
|
45
|
-
raw = (
|
|
46
|
-
(source.get("LANGSMITH_ENDPOINT") or "").strip()
|
|
47
|
-
or (source.get("LANGCHAIN_ENDPOINT") or "").strip()
|
|
48
|
-
or _DEFAULT_LANGSMITH_ENDPOINT
|
|
49
|
-
)
|
|
50
|
-
return raw.rstrip("/")
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def resolve_identity_context_jwks_url(
|
|
54
|
-
env: Mapping[str, str] | None = None,
|
|
55
|
-
) -> str:
|
|
56
|
-
"""Public JWKS URL for Agent Auth identity-context signatures."""
|
|
57
|
-
return f"{resolve_identity_context_issuer(env)}/.well-known/jwks.json"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def principal_id_from_identity_context(
|
|
61
|
-
token: str,
|
|
62
|
-
*,
|
|
63
|
-
key: IdentityContextKey | None = None,
|
|
64
|
-
issuer: str | None = None,
|
|
65
|
-
env: Mapping[str, str] | None = None,
|
|
66
|
-
) -> str:
|
|
67
|
-
"""Extract the Agent Auth principal UUID from a Trigger identity-context JWT.
|
|
68
|
-
|
|
69
|
-
Raises:
|
|
70
|
-
InvalidIdentityContextError: when the token is missing, malformed, or
|
|
71
|
-
fails signature / claim checks.
|
|
72
|
-
"""
|
|
73
|
-
value = token.strip()
|
|
74
|
-
if not value:
|
|
75
|
-
raise InvalidIdentityContextError("identity_context is blank")
|
|
76
|
-
|
|
77
|
-
_require_identity_context_header(value)
|
|
78
|
-
claims = _verify_identity_context_claims(
|
|
79
|
-
value,
|
|
80
|
-
key=key,
|
|
81
|
-
issuer=issuer or resolve_identity_context_issuer(env),
|
|
82
|
-
env=env,
|
|
83
|
-
)
|
|
84
|
-
if claims.get("version") != IDENTITY_CONTEXT_VERSION:
|
|
85
|
-
raise InvalidIdentityContextError(
|
|
86
|
-
f"identity_context version must be {IDENTITY_CONTEXT_VERSION}"
|
|
87
|
-
)
|
|
88
|
-
return _principal_id_from_claims(claims)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def optional_principal_id_from_identity_context(
|
|
92
|
-
token: object | None,
|
|
93
|
-
*,
|
|
94
|
-
key: IdentityContextKey | None = None,
|
|
95
|
-
issuer: str | None = None,
|
|
96
|
-
env: Mapping[str, str] | None = None,
|
|
97
|
-
) -> str | None:
|
|
98
|
-
"""Resolve a principal id from an optional event field; ``None`` when absent."""
|
|
99
|
-
if token is None:
|
|
100
|
-
return None
|
|
101
|
-
if not isinstance(token, str):
|
|
102
|
-
raise InvalidIdentityContextError("identity_context must be a string")
|
|
103
|
-
if not token.strip():
|
|
104
|
-
return None
|
|
105
|
-
return principal_id_from_identity_context(token, key=key, issuer=issuer, env=env)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def _require_identity_context_header(token: str) -> None:
|
|
109
|
-
try:
|
|
110
|
-
header = jwt.get_unverified_header(token)
|
|
111
|
-
except jwt.exceptions.DecodeError as error:
|
|
112
|
-
raise InvalidIdentityContextError("identity_context is not a JWT") from error
|
|
113
|
-
if header.get("typ") != IDENTITY_CONTEXT_TOKEN_TYP:
|
|
114
|
-
raise InvalidIdentityContextError(
|
|
115
|
-
f"identity_context typ must be {IDENTITY_CONTEXT_TOKEN_TYP}"
|
|
116
|
-
)
|
|
117
|
-
if header.get("alg") != "EdDSA":
|
|
118
|
-
raise InvalidIdentityContextError("identity_context alg must be EdDSA")
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def _verify_identity_context_claims(
|
|
122
|
-
token: str,
|
|
123
|
-
*,
|
|
124
|
-
key: IdentityContextKey | None,
|
|
125
|
-
issuer: str,
|
|
126
|
-
env: Mapping[str, str] | None,
|
|
127
|
-
) -> dict[str, object]:
|
|
128
|
-
algorithms = list(IDENTITY_CONTEXT_ALGORITHMS)
|
|
129
|
-
try:
|
|
130
|
-
verify_key = _jwks_client(env).get_signing_key_from_jwt(token) if key is None else key
|
|
131
|
-
decoded = jwt.decode(
|
|
132
|
-
token,
|
|
133
|
-
key=verify_key,
|
|
134
|
-
algorithms=algorithms,
|
|
135
|
-
audience=IDENTITY_CONTEXT_AUDIENCE,
|
|
136
|
-
issuer=issuer,
|
|
137
|
-
options={"require": ["exp"], "verify_exp": True},
|
|
138
|
-
)
|
|
139
|
-
except jwt.exceptions.ExpiredSignatureError as error:
|
|
140
|
-
raise InvalidIdentityContextError("identity_context is expired") from error
|
|
141
|
-
except jwt.exceptions.MissingRequiredClaimError as error:
|
|
142
|
-
raise InvalidIdentityContextError("identity_context is missing exp") from error
|
|
143
|
-
except jwt.exceptions.PyJWKClientConnectionError as error:
|
|
144
|
-
raise IdentityContextUnavailableError(
|
|
145
|
-
"identity_context JWKS verification is unavailable"
|
|
146
|
-
) from error
|
|
147
|
-
except TimeoutError as error:
|
|
148
|
-
raise IdentityContextUnavailableError(
|
|
149
|
-
"identity_context JWKS verification is unavailable"
|
|
150
|
-
) from error
|
|
151
|
-
except OSError as error:
|
|
152
|
-
raise IdentityContextUnavailableError(
|
|
153
|
-
"identity_context JWKS verification is unavailable"
|
|
154
|
-
) from error
|
|
155
|
-
except jwt.exceptions.PyJWTError as error:
|
|
156
|
-
raise InvalidIdentityContextError(
|
|
157
|
-
"identity_context signature verification failed"
|
|
158
|
-
) from error
|
|
159
|
-
if not isinstance(decoded, dict):
|
|
160
|
-
raise InvalidIdentityContextError("identity_context claims must be an object")
|
|
161
|
-
claims = {str(name): value for name, value in decoded.items()}
|
|
162
|
-
if not isinstance(claims.get("exp"), (int, float)) or isinstance(claims.get("exp"), bool):
|
|
163
|
-
raise InvalidIdentityContextError("identity_context is missing exp")
|
|
164
|
-
return claims
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def _jwks_client(env: Mapping[str, str] | None) -> PyJWKClient:
|
|
168
|
-
uri = resolve_identity_context_jwks_url(env)
|
|
169
|
-
client = _jwks_clients.get(uri)
|
|
170
|
-
if client is None:
|
|
171
|
-
client = PyJWKClient(uri, timeout=_JWKS_HTTP_TIMEOUT_S)
|
|
172
|
-
_jwks_clients[uri] = client
|
|
173
|
-
return client
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def _principal_id_from_claims(claims: Mapping[str, object]) -> str:
|
|
177
|
-
from_principal = _principal_id_claim(claims.get("principal"))
|
|
178
|
-
raw_subject = claims.get("sub")
|
|
179
|
-
from_subject = _normalize_uuid(raw_subject) if isinstance(raw_subject, str) else None
|
|
180
|
-
if from_principal and from_subject and from_principal != from_subject:
|
|
181
|
-
raise InvalidIdentityContextError("identity_context principal.id and sub disagree")
|
|
182
|
-
principal_id = from_principal or from_subject
|
|
183
|
-
if not principal_id:
|
|
184
|
-
raise InvalidIdentityContextError("identity_context is missing a principal UUID")
|
|
185
|
-
return principal_id
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def _principal_id_claim(value: object) -> str | None:
|
|
189
|
-
if not isinstance(value, Mapping):
|
|
190
|
-
return None
|
|
191
|
-
raw_id = value.get("id")
|
|
192
|
-
return _normalize_uuid(raw_id) if isinstance(raw_id, str) else None
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def _normalize_uuid(value: str) -> str | None:
|
|
196
|
-
try:
|
|
197
|
-
return str(UUID(value))
|
|
198
|
-
except ValueError:
|
|
199
|
-
return None
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
__all__ = [
|
|
203
|
-
"IDENTITY_CONTEXT_ALGORITHMS",
|
|
204
|
-
"IDENTITY_CONTEXT_AUDIENCE",
|
|
205
|
-
"IDENTITY_CONTEXT_TOKEN_TYP",
|
|
206
|
-
"IDENTITY_CONTEXT_VERSION",
|
|
207
|
-
"IdentityContextKey",
|
|
208
|
-
"IdentityContextUnavailableError",
|
|
209
|
-
"InvalidIdentityContextError",
|
|
210
|
-
"optional_principal_id_from_identity_context",
|
|
211
|
-
"principal_id_from_identity_context",
|
|
212
|
-
"resolve_identity_context_issuer",
|
|
213
|
-
"resolve_identity_context_jwks_url",
|
|
214
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{managed_deepagents-0.6.2.dev36.dist-info → managed_deepagents-0.6.2.dev37.dist-info}/top_level.txt
RENAMED
|
File without changes
|