macp-sdk-python 0.2.1__tar.gz → 0.2.2__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.
- {macp_sdk_python-0.2.1/src/macp_sdk_python.egg-info → macp_sdk_python-0.2.2}/PKG-INFO +2 -2
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/pyproject.toml +4 -5
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/agent/participant.py +80 -4
- macp_sdk_python-0.2.2/src/macp_sdk/agent/runner.py +140 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/agent/transports.py +17 -6
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/base_session.py +4 -2
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/client.py +23 -15
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/envelope.py +4 -13
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2/src/macp_sdk_python.egg-info}/PKG-INFO +2 -2
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk_python.egg-info/requires.txt +1 -1
- macp_sdk_python-0.2.1/src/macp_sdk/agent/runner.py +0 -100
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/LICENSE +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/README.md +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/setup.cfg +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/__init__.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/_logging.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/agent/__init__.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/agent/dispatcher.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/agent/strategies.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/agent/types.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/auth.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/base_projection.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/constants.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/decision.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/errors.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/handoff.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/policy.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/projections.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/proposal.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/proto_registry.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/py.typed +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/quorum.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/retry.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/task.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/validation.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk/watchers.py +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk_python.egg-info/SOURCES.txt +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk_python.egg-info/dependency_links.txt +0 -0
- {macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk_python.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: macp-sdk-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Python SDK for the MACP Rust runtime
|
|
5
5
|
Author-email: OpenAI <support@openai.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -17,7 +17,7 @@ Description-Content-Type: text/markdown
|
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: grpcio>=1.74.0
|
|
19
19
|
Requires-Dist: protobuf>=5.27.0
|
|
20
|
-
Requires-Dist: macp-proto<0.2.0,>=0.1.
|
|
20
|
+
Requires-Dist: macp-proto<0.2.0,>=0.1.1
|
|
21
21
|
Provides-Extra: dev
|
|
22
22
|
Requires-Dist: grpcio-tools>=1.74.0; extra == "dev"
|
|
23
23
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "macp-sdk-python"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "Python SDK for the MACP Rust runtime"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -25,10 +25,9 @@ classifiers = [
|
|
|
25
25
|
dependencies = [
|
|
26
26
|
"grpcio>=1.74.0",
|
|
27
27
|
"protobuf>=5.27.0",
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
"macp-proto>=0.1.0,<0.2.0",
|
|
28
|
+
# Requires 0.1.1+ for context_id / extensions fields on SessionStartPayload.
|
|
29
|
+
# See docs/contributing.md "Bumping macp-proto".
|
|
30
|
+
"macp-proto>=0.1.1,<0.2.0",
|
|
32
31
|
]
|
|
33
32
|
|
|
34
33
|
[project.optional-dependencies]
|
|
@@ -70,6 +70,48 @@ class ParticipantActions:
|
|
|
70
70
|
"""Cancel the session."""
|
|
71
71
|
return self._client.cancel_session(self._session_id, reason=reason, auth=self._auth)
|
|
72
72
|
|
|
73
|
+
def start_session(
|
|
74
|
+
self,
|
|
75
|
+
intent: str,
|
|
76
|
+
participants: list[str],
|
|
77
|
+
ttl_ms: int,
|
|
78
|
+
context_id: str = "",
|
|
79
|
+
extensions: dict[str, bytes] | None = None,
|
|
80
|
+
mode_version: str | None = None,
|
|
81
|
+
configuration_version: str | None = None,
|
|
82
|
+
policy_version: str | None = None,
|
|
83
|
+
) -> Any:
|
|
84
|
+
"""Send a SessionStart envelope to open the session."""
|
|
85
|
+
from ..constants import (
|
|
86
|
+
DEFAULT_CONFIGURATION_VERSION,
|
|
87
|
+
DEFAULT_MODE_VERSION,
|
|
88
|
+
DEFAULT_POLICY_VERSION,
|
|
89
|
+
)
|
|
90
|
+
from ..envelope import (
|
|
91
|
+
build_envelope,
|
|
92
|
+
build_session_start_payload,
|
|
93
|
+
serialize_message,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
payload = build_session_start_payload(
|
|
97
|
+
intent=intent,
|
|
98
|
+
participants=participants,
|
|
99
|
+
ttl_ms=ttl_ms,
|
|
100
|
+
context_id=context_id,
|
|
101
|
+
extensions=extensions,
|
|
102
|
+
mode_version=mode_version or DEFAULT_MODE_VERSION,
|
|
103
|
+
configuration_version=configuration_version or DEFAULT_CONFIGURATION_VERSION,
|
|
104
|
+
policy_version=policy_version or DEFAULT_POLICY_VERSION,
|
|
105
|
+
)
|
|
106
|
+
envelope = build_envelope(
|
|
107
|
+
mode=self._mode,
|
|
108
|
+
message_type="SessionStart",
|
|
109
|
+
session_id=self._session_id,
|
|
110
|
+
sender=self._participant_id,
|
|
111
|
+
payload=serialize_message(payload),
|
|
112
|
+
)
|
|
113
|
+
return self.send_envelope(envelope)
|
|
114
|
+
|
|
73
115
|
def evaluate(
|
|
74
116
|
self,
|
|
75
117
|
proposal_id: str,
|
|
@@ -222,6 +264,7 @@ class Participant:
|
|
|
222
264
|
configuration_version: str | None = None,
|
|
223
265
|
policy_version: str | None = None,
|
|
224
266
|
transport: TransportAdapter | None = None,
|
|
267
|
+
initiator_config: Any | None = None,
|
|
225
268
|
) -> None:
|
|
226
269
|
self._participant_id = participant_id
|
|
227
270
|
self._session_id = session_id
|
|
@@ -229,6 +272,7 @@ class Participant:
|
|
|
229
272
|
self._client = client
|
|
230
273
|
self._auth = auth
|
|
231
274
|
self._stopped = False
|
|
275
|
+
self._initiator_config = initiator_config
|
|
232
276
|
|
|
233
277
|
self._dispatcher = Dispatcher()
|
|
234
278
|
self._session = SessionInfo(
|
|
@@ -365,19 +409,23 @@ class Participant:
|
|
|
365
409
|
def run(self) -> None:
|
|
366
410
|
"""Enter the blocking event loop.
|
|
367
411
|
|
|
368
|
-
If
|
|
369
|
-
|
|
412
|
+
If this participant is the initiator (``initiator_config`` is set),
|
|
413
|
+
emits SessionStart + kickoff before opening the stream.
|
|
370
414
|
|
|
371
415
|
Dispatches received events to registered handlers until the session
|
|
372
416
|
reaches a terminal state or ``stop()`` is called.
|
|
373
417
|
"""
|
|
374
418
|
logger.info(
|
|
375
|
-
"participant %s joining session %s (mode=%s)",
|
|
419
|
+
"participant %s joining session %s (mode=%s, initiator=%s)",
|
|
376
420
|
self._participant_id,
|
|
377
421
|
self._session_id,
|
|
378
422
|
self._mode,
|
|
423
|
+
self._initiator_config is not None,
|
|
379
424
|
)
|
|
380
425
|
|
|
426
|
+
if self._initiator_config is not None:
|
|
427
|
+
self._emit_initiator_envelopes()
|
|
428
|
+
|
|
381
429
|
transport = self._transport or GrpcTransportAdapter(
|
|
382
430
|
self._client,
|
|
383
431
|
self._session_id,
|
|
@@ -387,7 +435,6 @@ class Participant:
|
|
|
387
435
|
for message in transport.start():
|
|
388
436
|
if self._stopped:
|
|
389
437
|
break
|
|
390
|
-
# If the transport yields raw envelopes (gRPC), process as envelope
|
|
391
438
|
if message.raw is not None:
|
|
392
439
|
self._process_envelope(message.raw)
|
|
393
440
|
else:
|
|
@@ -395,6 +442,35 @@ class Participant:
|
|
|
395
442
|
finally:
|
|
396
443
|
transport.stop()
|
|
397
444
|
|
|
445
|
+
def _emit_initiator_envelopes(self) -> None:
|
|
446
|
+
"""Emit SessionStart + kickoff envelope as the initiator."""
|
|
447
|
+
cfg = self._initiator_config
|
|
448
|
+
if cfg is None:
|
|
449
|
+
return
|
|
450
|
+
|
|
451
|
+
self._actions.start_session(
|
|
452
|
+
intent=cfg.intent,
|
|
453
|
+
participants=cfg.participants,
|
|
454
|
+
ttl_ms=cfg.ttl_ms,
|
|
455
|
+
context_id=cfg.context_id,
|
|
456
|
+
mode_version=cfg.mode_version,
|
|
457
|
+
configuration_version=cfg.configuration_version,
|
|
458
|
+
policy_version=cfg.policy_version,
|
|
459
|
+
)
|
|
460
|
+
logger.info("SessionStart emitted (session=%s)", self._session_id)
|
|
461
|
+
|
|
462
|
+
if cfg.kickoff_message_type == "Proposal":
|
|
463
|
+
payload = cfg.kickoff_payload or {}
|
|
464
|
+
proposal_id = str(
|
|
465
|
+
payload.get("proposalId")
|
|
466
|
+
or payload.get("proposal_id")
|
|
467
|
+
or f"{self._session_id}-kickoff"
|
|
468
|
+
)
|
|
469
|
+
option = str(payload.get("option", "decide"))
|
|
470
|
+
rationale = str(payload.get("rationale", ""))
|
|
471
|
+
self._actions.propose(proposal_id, option, rationale=rationale)
|
|
472
|
+
logger.info("Kickoff proposal emitted (proposalId=%s)", proposal_id)
|
|
473
|
+
|
|
398
474
|
def process_event(self, envelope: Any) -> None:
|
|
399
475
|
"""Manually process a single envelope (for testing or polling transports)."""
|
|
400
476
|
self._process_envelope(envelope)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..auth import AuthConfig
|
|
9
|
+
from ..client import MacpClient
|
|
10
|
+
from ..constants import DEFAULT_POLICY_VERSION
|
|
11
|
+
from .participant import Participant
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class InitiatorConfig:
|
|
16
|
+
"""Configuration for the initiator agent's SessionStart + kickoff."""
|
|
17
|
+
|
|
18
|
+
intent: str
|
|
19
|
+
participants: list[str]
|
|
20
|
+
ttl_ms: int
|
|
21
|
+
context_id: str = ""
|
|
22
|
+
roots: list[dict[str, str]] | None = None
|
|
23
|
+
mode_version: str | None = None
|
|
24
|
+
configuration_version: str | None = None
|
|
25
|
+
policy_version: str | None = None
|
|
26
|
+
kickoff_message_type: str | None = None
|
|
27
|
+
kickoff_payload: dict[str, Any] = field(default_factory=dict)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def from_bootstrap(bootstrap_path: str | None = None) -> Participant:
|
|
31
|
+
"""Create a Participant from a bootstrap context file.
|
|
32
|
+
|
|
33
|
+
Reads the flat bootstrap format produced by the examples-service::
|
|
34
|
+
|
|
35
|
+
{
|
|
36
|
+
"participant_id": "...",
|
|
37
|
+
"session_id": "...",
|
|
38
|
+
"mode": "macp.mode.decision.v1",
|
|
39
|
+
"runtime_url": "localhost:50051",
|
|
40
|
+
"auth_token": "...",
|
|
41
|
+
"participants": ["agent-a", "agent-b"],
|
|
42
|
+
"secure": false,
|
|
43
|
+
"allow_insecure": true,
|
|
44
|
+
"initiator": { ... }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Also accepts ``auth.bearer_token`` for backwards compatibility.
|
|
48
|
+
"""
|
|
49
|
+
path = bootstrap_path or os.environ.get("MACP_BOOTSTRAP_FILE")
|
|
50
|
+
if not path:
|
|
51
|
+
raise ValueError("No bootstrap path provided and MACP_BOOTSTRAP_FILE not set")
|
|
52
|
+
|
|
53
|
+
with open(path) as f:
|
|
54
|
+
ctx: dict[str, Any] = json.load(f)
|
|
55
|
+
|
|
56
|
+
participant_id = str(ctx["participant_id"])
|
|
57
|
+
session_id = str(ctx["session_id"])
|
|
58
|
+
mode = str(ctx["mode"])
|
|
59
|
+
runtime_url = str(ctx.get("runtime_url") or ctx.get("runtime_address") or "localhost:50051")
|
|
60
|
+
secure = bool(ctx.get("secure", True))
|
|
61
|
+
allow_insecure = bool(ctx.get("allow_insecure", False))
|
|
62
|
+
|
|
63
|
+
auth: AuthConfig | None = None
|
|
64
|
+
auth_token = ctx.get("auth_token")
|
|
65
|
+
agent_id = ctx.get("agent_id")
|
|
66
|
+
auth_data = ctx.get("auth")
|
|
67
|
+
|
|
68
|
+
if auth_token:
|
|
69
|
+
auth = AuthConfig.for_bearer(
|
|
70
|
+
str(auth_token),
|
|
71
|
+
sender_hint=participant_id,
|
|
72
|
+
expected_sender=participant_id,
|
|
73
|
+
)
|
|
74
|
+
elif isinstance(auth_data, dict) and auth_data.get("bearer_token"):
|
|
75
|
+
auth = AuthConfig.for_bearer(
|
|
76
|
+
str(auth_data["bearer_token"]),
|
|
77
|
+
sender_hint=participant_id,
|
|
78
|
+
expected_sender=str(auth_data.get("expected_sender") or participant_id),
|
|
79
|
+
)
|
|
80
|
+
elif agent_id:
|
|
81
|
+
auth = AuthConfig.for_dev_agent(str(agent_id), expected_sender=participant_id)
|
|
82
|
+
elif isinstance(auth_data, dict) and auth_data.get("agent_id"):
|
|
83
|
+
auth = AuthConfig.for_dev_agent(str(auth_data["agent_id"]), expected_sender=participant_id)
|
|
84
|
+
|
|
85
|
+
client = MacpClient(
|
|
86
|
+
target=runtime_url,
|
|
87
|
+
secure=secure,
|
|
88
|
+
allow_insecure=allow_insecure,
|
|
89
|
+
auth=auth,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
raw_participants = ctx.get("participants")
|
|
93
|
+
participants: list[str] = (
|
|
94
|
+
[str(p) for p in raw_participants] if isinstance(raw_participants, list) else []
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
mode_version = ctx.get("mode_version")
|
|
98
|
+
configuration_version = ctx.get("configuration_version")
|
|
99
|
+
policy_version = ctx.get("policy_version")
|
|
100
|
+
|
|
101
|
+
initiator_config: InitiatorConfig | None = None
|
|
102
|
+
initiator_data = ctx.get("initiator")
|
|
103
|
+
if isinstance(initiator_data, dict):
|
|
104
|
+
ss = initiator_data.get("session_start", {})
|
|
105
|
+
kickoff = initiator_data.get("kickoff")
|
|
106
|
+
|
|
107
|
+
def _str_or(key: str, fallback: object) -> str | None:
|
|
108
|
+
"""Pick value from session_start, then fallback, coercing to str."""
|
|
109
|
+
val = ss.get(key)
|
|
110
|
+
if val is not None:
|
|
111
|
+
return str(val)
|
|
112
|
+
return str(fallback) if fallback else None
|
|
113
|
+
|
|
114
|
+
initiator_config = InitiatorConfig(
|
|
115
|
+
intent=str(ss.get("intent", "")),
|
|
116
|
+
participants=[str(p) for p in ss.get("participants", participants)],
|
|
117
|
+
ttl_ms=int(ss.get("ttl_ms", 300000)),
|
|
118
|
+
context_id=str(ss.get("context_id", "")),
|
|
119
|
+
roots=ss.get("roots"),
|
|
120
|
+
mode_version=_str_or("mode_version", mode_version),
|
|
121
|
+
configuration_version=_str_or("configuration_version", configuration_version),
|
|
122
|
+
policy_version=_str_or("policy_version", policy_version),
|
|
123
|
+
kickoff_message_type=(
|
|
124
|
+
str(kickoff["message_type"]) if kickoff and "message_type" in kickoff else None
|
|
125
|
+
),
|
|
126
|
+
kickoff_payload=kickoff.get("payload", {}) if kickoff else {},
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return Participant(
|
|
130
|
+
participant_id=participant_id,
|
|
131
|
+
session_id=session_id,
|
|
132
|
+
mode=mode,
|
|
133
|
+
client=client,
|
|
134
|
+
auth=auth,
|
|
135
|
+
participants=participants,
|
|
136
|
+
mode_version=str(mode_version) if mode_version else None,
|
|
137
|
+
configuration_version=str(configuration_version) if configuration_version else None,
|
|
138
|
+
policy_version=str(policy_version) if policy_version else DEFAULT_POLICY_VERSION,
|
|
139
|
+
initiator_config=initiator_config,
|
|
140
|
+
)
|
|
@@ -133,16 +133,27 @@ class HttpTransportAdapter:
|
|
|
133
133
|
|
|
134
134
|
def _envelope_to_message(envelope: Any) -> IncomingMessage:
|
|
135
135
|
"""Convert a protobuf Envelope to an IncomingMessage."""
|
|
136
|
+
from ..proto_registry import ProtoRegistry
|
|
137
|
+
|
|
136
138
|
payload_dict: dict[str, Any] = {}
|
|
137
139
|
if envelope.payload:
|
|
138
140
|
try:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
registry = ProtoRegistry()
|
|
142
|
+
decoded = registry.decode_known_payload(
|
|
143
|
+
envelope.mode, envelope.message_type, envelope.payload
|
|
144
|
+
)
|
|
145
|
+
payload_dict = decoded if decoded is not None else json.loads(envelope.payload)
|
|
146
|
+
except Exception:
|
|
147
|
+
try:
|
|
148
|
+
payload_dict = json.loads(envelope.payload)
|
|
149
|
+
except Exception:
|
|
150
|
+
payload_dict = {}
|
|
142
151
|
|
|
143
|
-
proposal_id: str | None =
|
|
144
|
-
|
|
145
|
-
|
|
152
|
+
proposal_id: str | None = (
|
|
153
|
+
payload_dict.get("proposal_id") or payload_dict.get("proposalId") or None
|
|
154
|
+
)
|
|
155
|
+
if proposal_id is not None:
|
|
156
|
+
proposal_id = str(proposal_id)
|
|
146
157
|
|
|
147
158
|
return IncomingMessage(
|
|
148
159
|
message_type=envelope.message_type,
|
|
@@ -99,7 +99,8 @@ class BaseSession(ABC):
|
|
|
99
99
|
intent: str,
|
|
100
100
|
participants: list[str],
|
|
101
101
|
ttl_ms: int,
|
|
102
|
-
|
|
102
|
+
context_id: str = "",
|
|
103
|
+
extensions: Mapping[str, bytes] | None = None,
|
|
103
104
|
roots: Iterable[Any] | None = None,
|
|
104
105
|
sender: str | None = None,
|
|
105
106
|
auth: AuthConfig | None = None,
|
|
@@ -113,7 +114,8 @@ class BaseSession(ABC):
|
|
|
113
114
|
mode_version=self.mode_version,
|
|
114
115
|
configuration_version=self.configuration_version,
|
|
115
116
|
policy_version=self.policy_version,
|
|
116
|
-
|
|
117
|
+
context_id=context_id,
|
|
118
|
+
extensions=extensions,
|
|
117
119
|
roots=roots,
|
|
118
120
|
)
|
|
119
121
|
envelope = build_envelope(
|
|
@@ -107,24 +107,32 @@ class MacpStream:
|
|
|
107
107
|
def _pump_responses(self) -> None:
|
|
108
108
|
try:
|
|
109
109
|
for response in self._call:
|
|
110
|
-
#
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
# StreamSessionResponse has envelope + error at the top level.
|
|
111
|
+
envelope = getattr(response, "envelope", None)
|
|
112
|
+
error = getattr(response, "error", None)
|
|
113
|
+
|
|
114
|
+
if error is not None and hasattr(error, "ByteSize") and error.ByteSize() > 0:
|
|
115
|
+
for cb in self._inline_error_callbacks:
|
|
116
|
+
cb(error)
|
|
117
|
+
logger.warning("inline stream error: %s", error)
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
if envelope is not None and envelope.ByteSize() > 0:
|
|
121
|
+
self._responses.put(envelope)
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
# Fallback: try a nested .response wrapper (legacy proto shape).
|
|
113
125
|
inner = getattr(response, "response", None)
|
|
114
126
|
if inner is not None and hasattr(inner, "ByteSize") and inner.ByteSize() > 0:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if
|
|
118
|
-
self._responses.put(
|
|
119
|
-
elif
|
|
120
|
-
# Inline application-level error — notify callbacks, keep stream open
|
|
127
|
+
inner_env = getattr(inner, "envelope", None)
|
|
128
|
+
inner_err = getattr(inner, "error", None)
|
|
129
|
+
if inner_env is not None and inner_env.ByteSize() > 0:
|
|
130
|
+
self._responses.put(inner_env)
|
|
131
|
+
elif inner_err is not None:
|
|
121
132
|
for cb in self._inline_error_callbacks:
|
|
122
|
-
cb(
|
|
123
|
-
logger.warning("inline stream error: %s",
|
|
133
|
+
cb(inner_err)
|
|
134
|
+
logger.warning("inline stream error: %s", inner_err)
|
|
124
135
|
continue
|
|
125
|
-
else:
|
|
126
|
-
# Flat format: response.envelope
|
|
127
|
-
self._responses.put(response.envelope)
|
|
128
136
|
except grpc.RpcError as exc:
|
|
129
137
|
self._responses.put(exc)
|
|
130
138
|
finally:
|
|
@@ -182,7 +190,7 @@ class MacpClient:
|
|
|
182
190
|
root_certificates: bytes | None = None,
|
|
183
191
|
default_timeout: float | None = None,
|
|
184
192
|
client_name: str = "macp-sdk-python",
|
|
185
|
-
client_version: str = "0.2.
|
|
193
|
+
client_version: str = "0.2.2",
|
|
186
194
|
) -> None:
|
|
187
195
|
if secure is None:
|
|
188
196
|
secure = not allow_insecure
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
import time
|
|
5
4
|
import uuid
|
|
6
5
|
from collections.abc import Iterable, Mapping, Sequence
|
|
@@ -47,16 +46,6 @@ def now_unix_ms() -> int:
|
|
|
47
46
|
return int(time.time() * 1000)
|
|
48
47
|
|
|
49
48
|
|
|
50
|
-
def encode_context(context: bytes | str | Mapping[str, object] | None) -> bytes:
|
|
51
|
-
if context is None:
|
|
52
|
-
return b""
|
|
53
|
-
if isinstance(context, bytes):
|
|
54
|
-
return context
|
|
55
|
-
if isinstance(context, str):
|
|
56
|
-
return context.encode("utf-8")
|
|
57
|
-
return json.dumps(context).encode("utf-8")
|
|
58
|
-
|
|
59
|
-
|
|
60
49
|
def build_root(uri: str, name: str = "") -> core_pb2.Root:
|
|
61
50
|
return core_pb2.Root(uri=uri, name=name)
|
|
62
51
|
|
|
@@ -69,7 +58,8 @@ def build_session_start_payload(
|
|
|
69
58
|
mode_version: str = DEFAULT_MODE_VERSION,
|
|
70
59
|
configuration_version: str = DEFAULT_CONFIGURATION_VERSION,
|
|
71
60
|
policy_version: str = DEFAULT_POLICY_VERSION,
|
|
72
|
-
|
|
61
|
+
context_id: str = "",
|
|
62
|
+
extensions: Mapping[str, bytes] | None = None,
|
|
73
63
|
roots: Iterable[core_pb2.Root] | None = None,
|
|
74
64
|
) -> core_pb2.SessionStartPayload:
|
|
75
65
|
return core_pb2.SessionStartPayload(
|
|
@@ -79,7 +69,8 @@ def build_session_start_payload(
|
|
|
79
69
|
configuration_version=configuration_version,
|
|
80
70
|
policy_version=policy_version,
|
|
81
71
|
ttl_ms=ttl_ms,
|
|
82
|
-
|
|
72
|
+
context_id=context_id,
|
|
73
|
+
extensions=dict(extensions) if extensions else {},
|
|
83
74
|
roots=list(roots or []),
|
|
84
75
|
)
|
|
85
76
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: macp-sdk-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Python SDK for the MACP Rust runtime
|
|
5
5
|
Author-email: OpenAI <support@openai.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -17,7 +17,7 @@ Description-Content-Type: text/markdown
|
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: grpcio>=1.74.0
|
|
19
19
|
Requires-Dist: protobuf>=5.27.0
|
|
20
|
-
Requires-Dist: macp-proto<0.2.0,>=0.1.
|
|
20
|
+
Requires-Dist: macp-proto<0.2.0,>=0.1.1
|
|
21
21
|
Provides-Extra: dev
|
|
22
22
|
Requires-Dist: grpcio-tools>=1.74.0; extra == "dev"
|
|
23
23
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
|
|
6
|
-
from ..auth import AuthConfig
|
|
7
|
-
from ..client import MacpClient
|
|
8
|
-
from ..constants import DEFAULT_POLICY_VERSION
|
|
9
|
-
from .participant import Participant
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def from_bootstrap(bootstrap_path: str | None = None) -> Participant:
|
|
13
|
-
"""Create a Participant from a bootstrap context file.
|
|
14
|
-
|
|
15
|
-
The bootstrap file is a JSON document produced by the MACP hosting
|
|
16
|
-
infrastructure containing the information needed to connect and
|
|
17
|
-
participate in a session.
|
|
18
|
-
|
|
19
|
-
If ``bootstrap_path`` is not provided, the ``MACP_BOOTSTRAP_FILE``
|
|
20
|
-
environment variable is used.
|
|
21
|
-
|
|
22
|
-
Expected bootstrap JSON structure::
|
|
23
|
-
|
|
24
|
-
{
|
|
25
|
-
"participant_id": "...",
|
|
26
|
-
"session_id": "...",
|
|
27
|
-
"mode": "macp.mode.decision.v1",
|
|
28
|
-
"runtime_url": "localhost:50051",
|
|
29
|
-
"auth": {
|
|
30
|
-
"bearer_token": "...", // or "agent_id": "..."
|
|
31
|
-
},
|
|
32
|
-
"participants": ["agent-a", "agent-b"],
|
|
33
|
-
"policy_version": "policy.default",
|
|
34
|
-
"secure": true, // default; set false only for local dev
|
|
35
|
-
"allow_insecure": false // required if secure=false
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
Transport security follows RFC-MACP-0006 §3: ``secure`` defaults to
|
|
39
|
-
``true``. Setting ``secure: false`` additionally requires
|
|
40
|
-
``allow_insecure: true`` in the bootstrap (opt-in for local dev only).
|
|
41
|
-
"""
|
|
42
|
-
path = bootstrap_path or os.environ.get("MACP_BOOTSTRAP_FILE")
|
|
43
|
-
if not path:
|
|
44
|
-
raise ValueError("No bootstrap path provided and MACP_BOOTSTRAP_FILE not set")
|
|
45
|
-
|
|
46
|
-
with open(path) as f:
|
|
47
|
-
ctx: dict[str, object] = json.load(f)
|
|
48
|
-
|
|
49
|
-
participant_id = str(ctx["participant_id"])
|
|
50
|
-
session_id = str(ctx["session_id"])
|
|
51
|
-
mode = str(ctx["mode"])
|
|
52
|
-
runtime_url = str(ctx.get("runtime_url", "localhost:50051"))
|
|
53
|
-
secure = bool(ctx.get("secure", True))
|
|
54
|
-
allow_insecure = bool(ctx.get("allow_insecure", False))
|
|
55
|
-
|
|
56
|
-
# Build auth config
|
|
57
|
-
auth_data = ctx.get("auth")
|
|
58
|
-
auth: AuthConfig | None = None
|
|
59
|
-
if isinstance(auth_data, dict):
|
|
60
|
-
bearer = auth_data.get("bearer_token")
|
|
61
|
-
agent_id = auth_data.get("agent_id")
|
|
62
|
-
expected_sender = auth_data.get("expected_sender") or participant_id
|
|
63
|
-
if bearer:
|
|
64
|
-
auth = AuthConfig.for_bearer(
|
|
65
|
-
str(bearer),
|
|
66
|
-
sender_hint=participant_id,
|
|
67
|
-
expected_sender=str(expected_sender),
|
|
68
|
-
)
|
|
69
|
-
elif agent_id:
|
|
70
|
-
auth = AuthConfig.for_dev_agent(str(agent_id), expected_sender=str(expected_sender))
|
|
71
|
-
|
|
72
|
-
# Build client
|
|
73
|
-
client = MacpClient(
|
|
74
|
-
target=runtime_url,
|
|
75
|
-
secure=secure,
|
|
76
|
-
allow_insecure=allow_insecure,
|
|
77
|
-
auth=auth,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
# Extract optional fields
|
|
81
|
-
raw_participants = ctx.get("participants")
|
|
82
|
-
participants: list[str] = []
|
|
83
|
-
if isinstance(raw_participants, list):
|
|
84
|
-
participants = [str(p) for p in raw_participants]
|
|
85
|
-
|
|
86
|
-
mode_version = ctx.get("mode_version")
|
|
87
|
-
configuration_version = ctx.get("configuration_version")
|
|
88
|
-
policy_version = ctx.get("policy_version")
|
|
89
|
-
|
|
90
|
-
return Participant(
|
|
91
|
-
participant_id=participant_id,
|
|
92
|
-
session_id=session_id,
|
|
93
|
-
mode=mode,
|
|
94
|
-
client=client,
|
|
95
|
-
auth=auth,
|
|
96
|
-
participants=participants,
|
|
97
|
-
mode_version=str(mode_version) if mode_version else None,
|
|
98
|
-
configuration_version=str(configuration_version) if configuration_version else None,
|
|
99
|
-
policy_version=str(policy_version) if policy_version else DEFAULT_POLICY_VERSION,
|
|
100
|
-
)
|
|
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
|
{macp_sdk_python-0.2.1 → macp_sdk_python-0.2.2}/src/macp_sdk_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|