ms-tau-sdk 2.0.3.dev36__tar.gz → 2.0.3.dev37__tar.gz
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.
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/CHANGELOG.md +13 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/PKG-INFO +3 -2
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/README.md +2 -1
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/pyproject.toml +1 -1
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +3 -2
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +3 -3
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/a2a.py +1 -1
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/request_identity.py +91 -18
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/assertions.py +3 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/client.py +8 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/models.py +15 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/routes.py +5 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/manager.py +1 -1
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/.gitignore +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/app.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/cli.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/config.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/env_file.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/logs.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/proxy.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/state.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/static/BULMA-LICENSE.txt +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/static/app.js +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/static/board.css +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/static/bulma.min.css +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/static/index.html +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/agent_skills/tau_local_development/SKILL.md +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/chat.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/conversations.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/dependencies.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/health.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/inspection.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/local_chat.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/models.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/api/sessions.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/app.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/application.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/auth.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/local.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/backend/mcp.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/cli.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/errors.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/logging.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/protocols/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/protocols/a2a_failure.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/protocols/assistant_ui.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/protocols/chat_history.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/providers/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/providers/definitions.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/providers/factory.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/providers/tau_compat.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/resources/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/resources/loader.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/events.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/extensions.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/failures.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/live_turns.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/observability.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/provenance.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/requester.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/secret_entry.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/session.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/task_context.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/sessions/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/sessions/storage.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/settings.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/skills.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/tools/__init__.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/tools/mainsequence_mcp.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/tools/secret_entry.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/tools/skill_read.py +0 -0
- {ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/tools/task_control.py +0 -0
|
@@ -51,6 +51,19 @@
|
|
|
51
51
|
`GET /api/chat/model-providers`. The Environment is now sent as the `organization_environment_uid`
|
|
52
52
|
query parameter, as the other list calls send theirs; the call without an Environment is
|
|
53
53
|
unchanged ([issue #68](https://github.com/mainsequence-sdk/ms-tau-sdk/issues/68)).
|
|
54
|
+
- A hosted runtime admits the Agent that delegated to a child session. When Agent A delegates to
|
|
55
|
+
Agent B through `a2a.send_message`, the platform creates B's child session for the person who
|
|
56
|
+
owns A's session, and A addresses it with its own credential, so the caller assertion names A's
|
|
57
|
+
workload User. B's runtime refused every such request with 403. It now also admits that caller
|
|
58
|
+
when the session's `parent_session_agent_uid` names an Agent and the platform's directory
|
|
59
|
+
(`GET /api/v1/users/<uid>/`, read with the runtime credential) shows the caller as a workload
|
|
60
|
+
User whose `agent_uid` is that Agent: Message send and stream, JSON-RPC, and Task continuation,
|
|
61
|
+
reads, list and cancel. The `X-Caller-*` headers never admit a caller. A session without a
|
|
62
|
+
parent, another Agent's workload, and a person who is not the owner still get 403, and a lookup
|
|
63
|
+
that fails refuses the request. One request looks a User up at most once, and nothing is kept
|
|
64
|
+
after it. The admitted Agent is never a requester: `current_requester()` stays `None` in its
|
|
65
|
+
turns and in the Task attempts its requests run. See the 2026-10-06 amendment of ADR 0019
|
|
66
|
+
([issue #67](https://github.com/mainsequence-sdk/ms-tau-sdk/issues/67)).
|
|
54
67
|
|
|
55
68
|
## 2.0.2 — 2026-10-06
|
|
56
69
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ms-tau-sdk
|
|
3
|
-
Version: 2.0.3.
|
|
3
|
+
Version: 2.0.3.dev37
|
|
4
4
|
Summary: Workspace-bound Tau application primitives for Main Sequence projects
|
|
5
5
|
Project-URL: Changelog, https://github.com/mainsequence-sdk/ms-tau-sdk/blob/development/CHANGELOG.md
|
|
6
6
|
Project-URL: Documentation, https://github.com/mainsequence-sdk/ms-tau-sdk/tree/development/docs
|
|
@@ -126,7 +126,8 @@ app = create_app()
|
|
|
126
126
|
|
|
127
127
|
When Main Sequence hosts the runtime, `create_app()` admits a request only with the platform's
|
|
128
128
|
signed assertion in `X-MainSequence-Caller-Assertion`, never on gateway identity headers, and lets
|
|
129
|
-
only a session's owner
|
|
129
|
+
only a session's owner, an Organization admin, or the Agent that delegated to it address that
|
|
130
|
+
session. It declares this request
|
|
130
131
|
identity to the platform launcher in `app.state.mainsequence_request_identity`; the launcher serves
|
|
131
132
|
only an application that declares it, so serve the application `create_app()` returns. Outside
|
|
132
133
|
hosting, requests are handled as before. See
|
|
@@ -93,7 +93,8 @@ app = create_app()
|
|
|
93
93
|
|
|
94
94
|
When Main Sequence hosts the runtime, `create_app()` admits a request only with the platform's
|
|
95
95
|
signed assertion in `X-MainSequence-Caller-Assertion`, never on gateway identity headers, and lets
|
|
96
|
-
only a session's owner
|
|
96
|
+
only a session's owner, an Organization admin, or the Agent that delegated to it address that
|
|
97
|
+
session. It declares this request
|
|
97
98
|
identity to the platform launcher in `app.state.mainsequence_request_identity`; the launcher serves
|
|
98
99
|
only an application that declares it, so serve the application `create_app()` returns. Outside
|
|
99
100
|
hosting, requests are handled as before. See
|
|
@@ -26,8 +26,9 @@ The SDK owns:
|
|
|
26
26
|
- privately attaching active caller-session proof when a tool advertises
|
|
27
27
|
`mainsequence.ai/requires-caller-session-proof/v1: true`;
|
|
28
28
|
- verifying the platform's signed caller or platform assertion on every inbound request of a
|
|
29
|
-
hosted runtime, and admitting a request to a session or its Tasks only for the session's owner
|
|
30
|
-
|
|
29
|
+
hosted runtime, and admitting a request to a session or its Tasks only for the session's owner,
|
|
30
|
+
an Organization admin, or the workload User of the Agent that delegated to that child session,
|
|
31
|
+
which the platform's own records establish, never the `X-Caller-*` headers;
|
|
31
32
|
- presenting the verified caller assertion of the request that starts a chat or A2A Message turn
|
|
32
33
|
only when it marks that turn active, so that the platform can record the turn's requester, and
|
|
33
34
|
giving extension tools that requester and requester-bound calls through `current_requester()`
|
|
@@ -46,9 +46,9 @@ TAU image, overlay, or SDK-owned deployment.
|
|
|
46
46
|
|
|
47
47
|
`create_app()` installs the runtime's request identity. When Main Sequence hosts the runtime, the
|
|
48
48
|
application admits only requests that carry the platform's signed assertion, lets only a session's
|
|
49
|
-
owner
|
|
50
|
-
`app.state.mainsequence_request_identity`. The platform launcher serves an application
|
|
51
|
-
that declaration, read from the object it serves. Therefore:
|
|
49
|
+
owner, an Organization admin, or the Agent that delegated to it address that session, and declares
|
|
50
|
+
all this in `app.state.mainsequence_request_identity`. The platform launcher serves an application
|
|
51
|
+
only with that declaration, read from the object it serves. Therefore:
|
|
52
52
|
|
|
53
53
|
- export the object `create_app()` returns as the module's `app`. An application that mounts it
|
|
54
54
|
inside another one declares nothing, and the launcher refuses it;
|
|
@@ -930,7 +930,7 @@ def _requester_named_by_task(
|
|
|
930
930
|
answer must name the same User, as a person, as ``requester_user_uid``.
|
|
931
931
|
"""
|
|
932
932
|
|
|
933
|
-
if caller_assertion is None:
|
|
933
|
+
if caller_assertion is None or caller_assertion.caller_is_workload:
|
|
934
934
|
return None
|
|
935
935
|
caller = caller_assertion.caller
|
|
936
936
|
if task.requester_identity_type != "human" or task.requester_user_uid != caller.uid:
|
|
@@ -8,8 +8,9 @@ the kind its route requires: a platform assertion on ``/internal/*``, a caller a
|
|
|
8
8
|
other route. Handlers read the verified caller with ``current_caller()``, or from
|
|
9
9
|
``request.state.user`` and ``request.state.user_uid``.
|
|
10
10
|
|
|
11
|
-
A request that addresses an existing session must also come from the session's owner
|
|
12
|
-
Organization admin
|
|
11
|
+
A request that addresses an existing session must also come from the session's owner, from an
|
|
12
|
+
Organization admin, or, for a delegated child session, from the workload User of the Agent that
|
|
13
|
+
delegated to it; see ``require_session_access()``. Outside hosted mode nothing here changes how
|
|
13
14
|
requests are handled.
|
|
14
15
|
|
|
15
16
|
The raw caller assertion stays private to the request: ``request_caller_assertion()`` hands it
|
|
@@ -18,11 +19,13 @@ only to the turn that request starts, which presents it when it marks the turn a
|
|
|
18
19
|
|
|
19
20
|
from __future__ import annotations
|
|
20
21
|
|
|
22
|
+
import dataclasses
|
|
21
23
|
from collections.abc import Iterable
|
|
22
24
|
from contextvars import ContextVar
|
|
23
25
|
|
|
24
26
|
import structlog
|
|
25
27
|
from fastapi import FastAPI, HTTPException, Request
|
|
28
|
+
from pydantic import ValidationError
|
|
26
29
|
from starlette.responses import JSONResponse
|
|
27
30
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
|
28
31
|
from starlette.websockets import WebSocketClose
|
|
@@ -37,8 +40,8 @@ from ms_tau_sdk.backend.assertions import (
|
|
|
37
40
|
VerifiedCaller,
|
|
38
41
|
)
|
|
39
42
|
from ms_tau_sdk.backend.client import MainSequenceClient
|
|
40
|
-
from ms_tau_sdk.backend.models import AgentSession
|
|
41
|
-
from ms_tau_sdk.errors import SessionNotFoundError
|
|
43
|
+
from ms_tau_sdk.backend.models import AgentSession, DirectoryUser
|
|
44
|
+
from ms_tau_sdk.errors import SessionNotFoundError, TauSDKError
|
|
42
45
|
from ms_tau_sdk.logging import bind_request_identity_log_fields
|
|
43
46
|
from ms_tau_sdk.runtime.provenance import CallerIdentityError
|
|
44
47
|
from ms_tau_sdk.settings import TauSDKSettings
|
|
@@ -66,6 +69,12 @@ _current_caller: ContextVar[VerifiedCaller | None] = ContextVar(
|
|
|
66
69
|
# The verified raw caller assertion is kept in the request's own scope, not in request.state and
|
|
67
70
|
# not in a context variable, so that only the code that starts the request's turn reads it.
|
|
68
71
|
_CALLER_ASSERTION_SCOPE_KEY = "ms_tau_sdk.caller_assertion"
|
|
72
|
+
# The workload Users that one request looked up, by User UID: the workload, or None for a User
|
|
73
|
+
# that is not a workload or whose lookup failed. It lives only as long as its request.
|
|
74
|
+
_workload_lookups: ContextVar[dict[str, DirectoryUser | None] | None] = ContextVar(
|
|
75
|
+
"ms_tau_sdk_workload_lookups",
|
|
76
|
+
default=None,
|
|
77
|
+
)
|
|
69
78
|
|
|
70
79
|
|
|
71
80
|
class SessionAccessDeniedError(HTTPException):
|
|
@@ -85,15 +94,24 @@ def current_caller() -> VerifiedCaller | None:
|
|
|
85
94
|
|
|
86
95
|
|
|
87
96
|
def request_caller_assertion(request: Request) -> CallerAssertion | None:
|
|
88
|
-
"""Return the verified caller assertion of ``request`` for the turn it starts.
|
|
97
|
+
"""Return the verified caller assertion of ``request`` for the turn or Task it starts.
|
|
89
98
|
|
|
90
|
-
It is None outside hosted mode and on the platform's own calls. Only the chat and A2A
|
|
91
|
-
|
|
92
|
-
or passed to the model, a tool, a stream, or history.
|
|
99
|
+
It is None outside hosted mode and on the platform's own calls. Only the chat and A2A routes
|
|
100
|
+
read it, to present it when they mark their turn active or create or continue a Task; it is
|
|
101
|
+
never logged, persisted, or passed to the model, a tool, a stream, or history. A caller this
|
|
102
|
+
request admitted as the Agent that delegated to the session comes back marked as a workload,
|
|
103
|
+
which is never a requester.
|
|
93
104
|
"""
|
|
94
105
|
|
|
95
106
|
value = request.scope.get(_CALLER_ASSERTION_SCOPE_KEY)
|
|
96
|
-
|
|
107
|
+
if not isinstance(value, CallerAssertion):
|
|
108
|
+
return None
|
|
109
|
+
lookups = _workload_lookups.get()
|
|
110
|
+
if lookups is not None and lookups.get(value.caller.uid) is not None:
|
|
111
|
+
# The request was admitted as the Agent that delegated to the session: its assertion names
|
|
112
|
+
# a workload, never a requester.
|
|
113
|
+
return dataclasses.replace(value, caller_is_workload=True)
|
|
114
|
+
return value
|
|
97
115
|
|
|
98
116
|
|
|
99
117
|
def verified_user_uid(settings: TauSDKSettings) -> str | None:
|
|
@@ -111,15 +129,63 @@ def verified_user_uid(settings: TauSDKSettings) -> str | None:
|
|
|
111
129
|
return caller.uid
|
|
112
130
|
|
|
113
131
|
|
|
132
|
+
async def _workload_of(client: MainSequenceClient, user_uid: str) -> DirectoryUser | None:
|
|
133
|
+
"""Return the workload User ``user_uid`` names, as the platform's directory shows it, or None.
|
|
134
|
+
|
|
135
|
+
A User that is not a workload, and a lookup that fails, give None, so the check fails closed.
|
|
136
|
+
The answer is kept for the rest of the request only.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
lookups = _workload_lookups.get()
|
|
140
|
+
if lookups is not None and user_uid in lookups:
|
|
141
|
+
return lookups[user_uid]
|
|
142
|
+
workload: DirectoryUser | None = None
|
|
143
|
+
try:
|
|
144
|
+
found = await client.get_user(user_uid)
|
|
145
|
+
except (TauSDKError, ValidationError) as error:
|
|
146
|
+
logger.warning(
|
|
147
|
+
"request_identity.caller_lookup.failed",
|
|
148
|
+
message="The caller's User could not be looked up; the session is refused",
|
|
149
|
+
error_type=type(error).__name__,
|
|
150
|
+
)
|
|
151
|
+
else:
|
|
152
|
+
if found.uid == user_uid and found.identity_type == "workload" and found.agent_uid:
|
|
153
|
+
workload = found
|
|
154
|
+
if lookups is not None:
|
|
155
|
+
lookups[user_uid] = workload
|
|
156
|
+
return workload
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
async def _delegated_to_caller(
|
|
160
|
+
client: MainSequenceClient,
|
|
161
|
+
session: AgentSession,
|
|
162
|
+
caller: VerifiedCaller,
|
|
163
|
+
) -> bool:
|
|
164
|
+
"""Whether ``session`` is a child session that the caller's own Agent delegated to.
|
|
165
|
+
|
|
166
|
+
The session must name its parent session's Agent, the caller must be a workload User as the
|
|
167
|
+
platform's directory shows it, and that workload must serve the same Agent. No request header
|
|
168
|
+
is read.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
parent_agent_uid = session.parent_session_agent_uid
|
|
172
|
+
if not parent_agent_uid:
|
|
173
|
+
return False
|
|
174
|
+
workload = await _workload_of(client, caller.uid)
|
|
175
|
+
return workload is not None and workload.agent_uid == parent_agent_uid
|
|
176
|
+
|
|
177
|
+
|
|
114
178
|
async def require_session_access(
|
|
115
179
|
client: MainSequenceClient,
|
|
116
180
|
session_uid: str,
|
|
117
181
|
) -> AgentSession | None:
|
|
118
|
-
"""Admit the current request to ``session_uid`` only for
|
|
182
|
+
"""Admit the current request to ``session_uid`` only for a caller allowed to address it.
|
|
119
183
|
|
|
120
|
-
|
|
121
|
-
``created_by_user_uid
|
|
122
|
-
|
|
184
|
+
That is the session's owner, the User the platform recorded as its creator
|
|
185
|
+
(``created_by_user_uid``); an Organization admin; or, for a delegated child session, the
|
|
186
|
+
workload User of the Agent that delegated to it (``parent_session_agent_uid``). The check
|
|
187
|
+
applies in hosted mode only and raises ``SessionAccessDeniedError`` (403). It returns the
|
|
188
|
+
session when it had to read it.
|
|
123
189
|
"""
|
|
124
190
|
|
|
125
191
|
if client.settings.request_identity_mode != "assertion":
|
|
@@ -130,16 +196,19 @@ async def require_session_access(
|
|
|
130
196
|
if caller.is_organization_admin:
|
|
131
197
|
return None
|
|
132
198
|
session = await client.get_session(session_uid)
|
|
133
|
-
if session.created_by_user_uid
|
|
134
|
-
|
|
135
|
-
|
|
199
|
+
if session.created_by_user_uid == caller.uid:
|
|
200
|
+
return session
|
|
201
|
+
if await _delegated_to_caller(client, session, caller):
|
|
202
|
+
return session
|
|
203
|
+
raise SessionAccessDeniedError()
|
|
136
204
|
|
|
137
205
|
|
|
138
206
|
async def sessions_the_caller_may_address(
|
|
139
207
|
client: MainSequenceClient,
|
|
140
208
|
session_uids: Iterable[str],
|
|
141
209
|
) -> set[str] | None:
|
|
142
|
-
"""Return which of ``session_uids`` the current caller may address
|
|
210
|
+
"""Return which of ``session_uids`` the current caller may address, by the rule of
|
|
211
|
+
``require_session_access()``.
|
|
143
212
|
|
|
144
213
|
None means every session: outside hosted mode, or for an Organization admin. A session that
|
|
145
214
|
no longer exists is left out.
|
|
@@ -160,7 +229,9 @@ async def sessions_the_caller_may_address(
|
|
|
160
229
|
session = await client.get_session(session_uid)
|
|
161
230
|
except SessionNotFoundError:
|
|
162
231
|
continue
|
|
163
|
-
if session.created_by_user_uid == caller.uid
|
|
232
|
+
if session.created_by_user_uid == caller.uid or await _delegated_to_caller(
|
|
233
|
+
client, session, caller
|
|
234
|
+
):
|
|
164
235
|
allowed.add(session_uid)
|
|
165
236
|
return allowed
|
|
166
237
|
|
|
@@ -240,9 +311,11 @@ class RequestIdentityMiddleware:
|
|
|
240
311
|
token=token,
|
|
241
312
|
)
|
|
242
313
|
caller_token = _current_caller.set(caller)
|
|
314
|
+
lookups_token = _workload_lookups.set({})
|
|
243
315
|
try:
|
|
244
316
|
await self.app(scope, receive, send)
|
|
245
317
|
finally:
|
|
318
|
+
_workload_lookups.reset(lookups_token)
|
|
246
319
|
_current_caller.reset(caller_token)
|
|
247
320
|
scope.pop(_CALLER_ASSERTION_SCOPE_KEY, None)
|
|
248
321
|
|
|
@@ -113,6 +113,9 @@ class CallerAssertion:
|
|
|
113
113
|
caller: VerifiedCaller
|
|
114
114
|
expires_at: int
|
|
115
115
|
token: str = field(repr=False)
|
|
116
|
+
# True when the platform's directory showed the caller to be a workload User. A workload is
|
|
117
|
+
# never a turn's or a Task's requester.
|
|
118
|
+
caller_is_workload: bool = False
|
|
116
119
|
|
|
117
120
|
def unexpired_token(self, *, now: float | None = None) -> str | None:
|
|
118
121
|
"""Return the raw assertion while it is valid, and None once it has expired."""
|
|
@@ -35,6 +35,7 @@ from .models import (
|
|
|
35
35
|
AgentTaskEventPage,
|
|
36
36
|
AgentTaskExecutionAttempt,
|
|
37
37
|
AgentTaskSnapshot,
|
|
38
|
+
DirectoryUser,
|
|
38
39
|
LocalChatSessionPage,
|
|
39
40
|
LocalChatTranscript,
|
|
40
41
|
LocalConversationMessagePage,
|
|
@@ -77,6 +78,7 @@ from .routes import (
|
|
|
77
78
|
agent_task_operation,
|
|
78
79
|
model_provider_credentials,
|
|
79
80
|
resource_release_runtime_access,
|
|
81
|
+
user,
|
|
80
82
|
)
|
|
81
83
|
|
|
82
84
|
T = TypeVar("T")
|
|
@@ -404,6 +406,12 @@ class MainSequenceClient:
|
|
|
404
406
|
detail=error.errors(include_input=False),
|
|
405
407
|
) from error
|
|
406
408
|
|
|
409
|
+
async def get_user(self, user_uid: str) -> DirectoryUser:
|
|
410
|
+
"""Look a User of the Organization up, as the platform's directory shows it."""
|
|
411
|
+
|
|
412
|
+
data = await self._request("GET", user(user_uid), idempotent=True)
|
|
413
|
+
return DirectoryUser.model_validate(data)
|
|
414
|
+
|
|
407
415
|
async def list_model_providers(
|
|
408
416
|
self, *, organization_environment_uid: str | None = None
|
|
409
417
|
) -> dict[str, Any]:
|
|
@@ -74,6 +74,9 @@ class AgentSession(BackendModel):
|
|
|
74
74
|
)
|
|
75
75
|
status: str | None = None
|
|
76
76
|
created_by_user_uid: str | None = None
|
|
77
|
+
# The Agent of the parent session, or None for a session without a parent. The Agent that
|
|
78
|
+
# delegated to a child session addresses it with its own workload User.
|
|
79
|
+
parent_session_agent_uid: str | None = None
|
|
77
80
|
runtime_capabilities: dict[str, str] = Field(default_factory=dict)
|
|
78
81
|
|
|
79
82
|
@model_validator(mode="after")
|
|
@@ -87,6 +90,18 @@ class AgentSession(BackendModel):
|
|
|
87
90
|
return self
|
|
88
91
|
|
|
89
92
|
|
|
93
|
+
class DirectoryUser(BackendModel):
|
|
94
|
+
"""A User of the Organization as the platform's directory shows it to the runtime.
|
|
95
|
+
|
|
96
|
+
A workload User's row carries ``identity_type`` ``workload`` and, for the workload of an
|
|
97
|
+
Agent's release, ``agent_uid``.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
uid: str
|
|
101
|
+
identity_type: str | None = None
|
|
102
|
+
agent_uid: str | None = None
|
|
103
|
+
|
|
104
|
+
|
|
90
105
|
class AgentCardEnvelope(BackendModel):
|
|
91
106
|
agent_session_uid: str
|
|
92
107
|
agent_uid: str
|
|
@@ -12,6 +12,7 @@ MODEL_PROVIDER_CREDENTIALS = f"{API_V1_PREFIX}/model-provider-credentials/"
|
|
|
12
12
|
MODEL_PROVIDERS = f"{API_V1_PREFIX}/model-providers/"
|
|
13
13
|
AGENT_TASKS = f"{API_V1_PREFIX}/agent-tasks/"
|
|
14
14
|
RESOURCE_RELEASES = f"{API_V1_PREFIX}/resource-releases/"
|
|
15
|
+
USERS = f"{API_V1_PREFIX}/users/"
|
|
15
16
|
|
|
16
17
|
type RuntimeLeaseOperation = Literal["acquire", "renew", "release"]
|
|
17
18
|
type ModelProviderCredentialOperation = Literal["hydrate", "status", "flush", "revoke"]
|
|
@@ -92,3 +93,7 @@ def agent_task_operation(task_uid: str, operation: AgentTaskOperation) -> str:
|
|
|
92
93
|
|
|
93
94
|
def resource_release_runtime_access(release_uid: str) -> str:
|
|
94
95
|
return f"{RESOURCE_RELEASES}{release_uid}/resolve-runtime-access/"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def user(user_uid: str) -> str:
|
|
99
|
+
return f"{USERS}{user_uid}/"
|
|
@@ -1729,7 +1729,7 @@ class SessionRuntimeManager:
|
|
|
1729
1729
|
return None
|
|
1730
1730
|
if task_context is not None:
|
|
1731
1731
|
return task_context.requester
|
|
1732
|
-
if caller_assertion is None:
|
|
1732
|
+
if caller_assertion is None or caller_assertion.caller_is_workload:
|
|
1733
1733
|
return None
|
|
1734
1734
|
caller = caller_assertion.caller
|
|
1735
1735
|
if runtime.requester_user_uid != caller.uid:
|
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/__init__.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/app.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/cli.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/config.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/env_file.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/logs.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/proxy.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/state.py
RENAMED
|
File without changes
|
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/packages/tau-board/src/ms_tau_board/static/app.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ms_tau_sdk-2.0.3.dev36 → ms_tau_sdk-2.0.3.dev37}/src/ms_tau_sdk/runtime/deployment_health.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|