macp-sdk-python 0.2.1__py3-none-any.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.
@@ -0,0 +1,169 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from collections.abc import Iterable, Mapping
5
+ from typing import Any, ClassVar
6
+
7
+ from macp.v1 import core_pb2, envelope_pb2
8
+
9
+ from ._logging import logger
10
+ from .auth import AuthConfig
11
+ from .base_projection import BaseProjection
12
+ from .client import MacpClient, MacpStream
13
+ from .constants import (
14
+ DEFAULT_CONFIGURATION_VERSION,
15
+ DEFAULT_MODE_VERSION,
16
+ DEFAULT_POLICY_VERSION,
17
+ )
18
+ from .envelope import (
19
+ build_commitment_payload,
20
+ build_envelope,
21
+ build_session_start_payload,
22
+ new_session_id,
23
+ serialize_message,
24
+ )
25
+ from .errors import MacpIdentityMismatchError
26
+ from .validation import validate_participant_count, validate_session_id
27
+
28
+
29
+ class BaseSession(ABC):
30
+ """Abstract base for mode session helpers.
31
+
32
+ Provides shared logic for session lifecycle (start, commit, cancel,
33
+ metadata, streaming) so that mode-specific subclasses only need to
34
+ implement their own action methods.
35
+ """
36
+
37
+ MODE: ClassVar[str]
38
+
39
+ def __init__(
40
+ self,
41
+ client: MacpClient,
42
+ *,
43
+ session_id: str | None = None,
44
+ mode_version: str = DEFAULT_MODE_VERSION,
45
+ configuration_version: str = DEFAULT_CONFIGURATION_VERSION,
46
+ policy_version: str = DEFAULT_POLICY_VERSION,
47
+ auth: AuthConfig | None = None,
48
+ ) -> None:
49
+ self.client = client
50
+ self.session_id = session_id or new_session_id()
51
+ if session_id:
52
+ validate_session_id(session_id)
53
+ self.mode_version = mode_version
54
+ self.configuration_version = configuration_version
55
+ self.policy_version = policy_version
56
+ self.auth = auth
57
+ self.projection = self._create_projection()
58
+
59
+ @abstractmethod
60
+ def _create_projection(self) -> BaseProjection:
61
+ """Return a new projection instance for this mode."""
62
+
63
+ def _sender_for(self, sender: str | None, *, auth: AuthConfig | None = None) -> str:
64
+ auth_cfg = auth or self.auth or self.client.auth
65
+ expected = auth_cfg.expected_sender if auth_cfg else None
66
+ if sender:
67
+ if expected is not None and sender != expected:
68
+ raise MacpIdentityMismatchError(expected=expected, actual=sender)
69
+ return sender
70
+ return auth_cfg.sender or "" if auth_cfg else ""
71
+
72
+ def _send_and_track(
73
+ self,
74
+ envelope: envelope_pb2.Envelope,
75
+ *,
76
+ auth: AuthConfig | None = None,
77
+ ) -> envelope_pb2.Ack:
78
+ logger.debug(
79
+ "send session=%s type=%s sender=%s",
80
+ envelope.session_id,
81
+ envelope.message_type,
82
+ envelope.sender,
83
+ )
84
+ ack = self.client.send(envelope, auth=auth or self.auth)
85
+ if ack.ok:
86
+ self.projection.apply_envelope(envelope)
87
+ else:
88
+ logger.warning(
89
+ "nack session=%s type=%s code=%s",
90
+ envelope.session_id,
91
+ envelope.message_type,
92
+ getattr(ack, "error", None),
93
+ )
94
+ return ack
95
+
96
+ def start(
97
+ self,
98
+ *,
99
+ intent: str,
100
+ participants: list[str],
101
+ ttl_ms: int,
102
+ context: bytes | str | Mapping[str, object] | None = None,
103
+ roots: Iterable[Any] | None = None,
104
+ sender: str | None = None,
105
+ auth: AuthConfig | None = None,
106
+ ) -> envelope_pb2.Ack:
107
+ """Send SessionStart and begin tracking via the projection."""
108
+ validate_participant_count(len(participants))
109
+ payload = build_session_start_payload(
110
+ intent=intent,
111
+ participants=participants,
112
+ ttl_ms=ttl_ms,
113
+ mode_version=self.mode_version,
114
+ configuration_version=self.configuration_version,
115
+ policy_version=self.policy_version,
116
+ context=context,
117
+ roots=roots,
118
+ )
119
+ envelope = build_envelope(
120
+ mode=self.MODE,
121
+ message_type="SessionStart",
122
+ session_id=self.session_id,
123
+ sender=self._sender_for(sender, auth=auth),
124
+ payload=serialize_message(payload),
125
+ )
126
+ return self._send_and_track(envelope, auth=auth or self.auth)
127
+
128
+ def commit(
129
+ self,
130
+ *,
131
+ action: str,
132
+ authority_scope: str,
133
+ reason: str,
134
+ commitment_id: str | None = None,
135
+ outcome_positive: bool | None = None,
136
+ sender: str | None = None,
137
+ auth: AuthConfig | None = None,
138
+ ) -> envelope_pb2.Ack:
139
+ """Send Commitment to resolve the session."""
140
+ payload = build_commitment_payload(
141
+ action=action,
142
+ authority_scope=authority_scope,
143
+ reason=reason,
144
+ commitment_id=commitment_id,
145
+ mode_version=self.mode_version,
146
+ configuration_version=self.configuration_version,
147
+ policy_version=self.policy_version,
148
+ outcome_positive=outcome_positive,
149
+ )
150
+ envelope = build_envelope(
151
+ mode=self.MODE,
152
+ message_type="Commitment",
153
+ session_id=self.session_id,
154
+ sender=self._sender_for(sender, auth=auth),
155
+ payload=serialize_message(payload),
156
+ )
157
+ return self._send_and_track(envelope, auth=auth)
158
+
159
+ def metadata(self, *, auth: AuthConfig | None = None) -> core_pb2.GetSessionResponse:
160
+ """Query session metadata from the runtime."""
161
+ return self.client.get_session(self.session_id, auth=auth or self.auth)
162
+
163
+ def cancel(self, *, reason: str = "", auth: AuthConfig | None = None) -> envelope_pb2.Ack:
164
+ """Cancel the session."""
165
+ return self.client.cancel_session(self.session_id, reason=reason, auth=auth or self.auth)
166
+
167
+ def open_stream(self, *, auth: AuthConfig | None = None) -> MacpStream:
168
+ """Open a bidirectional stream for this session."""
169
+ return self.client.open_stream(auth=auth or self.auth)