asap-protocol 0.1.0__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.
- asap/__init__.py +7 -0
- asap/cli.py +220 -0
- asap/errors.py +150 -0
- asap/examples/README.md +25 -0
- asap/examples/__init__.py +1 -0
- asap/examples/coordinator.py +184 -0
- asap/examples/echo_agent.py +100 -0
- asap/examples/run_demo.py +120 -0
- asap/models/__init__.py +146 -0
- asap/models/base.py +55 -0
- asap/models/constants.py +14 -0
- asap/models/entities.py +410 -0
- asap/models/enums.py +71 -0
- asap/models/envelope.py +94 -0
- asap/models/ids.py +55 -0
- asap/models/parts.py +207 -0
- asap/models/payloads.py +423 -0
- asap/models/types.py +39 -0
- asap/observability/__init__.py +43 -0
- asap/observability/logging.py +216 -0
- asap/observability/metrics.py +399 -0
- asap/schemas.py +203 -0
- asap/state/__init__.py +22 -0
- asap/state/machine.py +86 -0
- asap/state/snapshot.py +265 -0
- asap/transport/__init__.py +84 -0
- asap/transport/client.py +399 -0
- asap/transport/handlers.py +444 -0
- asap/transport/jsonrpc.py +190 -0
- asap/transport/middleware.py +359 -0
- asap/transport/server.py +739 -0
- asap_protocol-0.1.0.dist-info/METADATA +251 -0
- asap_protocol-0.1.0.dist-info/RECORD +36 -0
- asap_protocol-0.1.0.dist-info/WHEEL +4 -0
- asap_protocol-0.1.0.dist-info/entry_points.txt +2 -0
- asap_protocol-0.1.0.dist-info/licenses/LICENSE +190 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Run the ASAP two-agent demo.
|
|
2
|
+
|
|
3
|
+
This script starts the echo agent as a subprocess, then demonstrates
|
|
4
|
+
communication by sending a task request from the coordinator logic.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import signal
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
from typing import Sequence
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
16
|
+
from asap.examples.coordinator import dispatch_task
|
|
17
|
+
from asap.observability import get_logger
|
|
18
|
+
|
|
19
|
+
ECHO_MANIFEST_URL = "http://127.0.0.1:8001/.well-known/asap/manifest.json"
|
|
20
|
+
ECHO_BASE_URL = "http://127.0.0.1:8001"
|
|
21
|
+
READY_TIMEOUT_SECONDS = 10.0
|
|
22
|
+
READY_POLL_INTERVAL_SECONDS = 0.5
|
|
23
|
+
|
|
24
|
+
ECHO_AGENT_MODULE = "asap.examples.echo_agent"
|
|
25
|
+
|
|
26
|
+
logger = get_logger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def start_process(command: Sequence[str]) -> subprocess.Popen[str]:
|
|
30
|
+
"""Start a subprocess and return its handle.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
command: Command to execute as a sequence.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Started subprocess handle.
|
|
37
|
+
"""
|
|
38
|
+
return subprocess.Popen(command, text=True)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def wait_for_ready(url: str, timeout_seconds: float) -> None:
|
|
42
|
+
"""Wait for the given URL to respond with HTTP 200.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
url: The URL to poll for readiness.
|
|
46
|
+
timeout_seconds: Maximum time to wait before failing.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
RuntimeError: If the service does not become ready in time.
|
|
50
|
+
"""
|
|
51
|
+
deadline = time.monotonic() + timeout_seconds
|
|
52
|
+
while time.monotonic() < deadline:
|
|
53
|
+
try:
|
|
54
|
+
response = httpx.get(url, timeout=1.0)
|
|
55
|
+
if response.status_code == 200:
|
|
56
|
+
return
|
|
57
|
+
except httpx.HTTPError:
|
|
58
|
+
pass
|
|
59
|
+
time.sleep(READY_POLL_INTERVAL_SECONDS)
|
|
60
|
+
raise RuntimeError(f"Service not ready after {timeout_seconds:.1f}s: {url}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _terminate_process(process: subprocess.Popen[str] | None) -> None:
|
|
64
|
+
"""Terminate a subprocess if it is still running."""
|
|
65
|
+
if process is None or process.poll() is not None:
|
|
66
|
+
return
|
|
67
|
+
process.terminate()
|
|
68
|
+
try:
|
|
69
|
+
process.wait(timeout=3)
|
|
70
|
+
except subprocess.TimeoutExpired:
|
|
71
|
+
process.kill()
|
|
72
|
+
process.wait(timeout=3)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def main() -> None:
|
|
76
|
+
"""Start echo agent and demonstrate communication via coordinator logic."""
|
|
77
|
+
echo_command = [sys.executable, "-m", ECHO_AGENT_MODULE]
|
|
78
|
+
echo_process = None
|
|
79
|
+
|
|
80
|
+
def handle_signal(signum: int, _frame: object) -> None:
|
|
81
|
+
"""Handle shutdown signals by terminating child processes."""
|
|
82
|
+
signal_name = signal.Signals(signum).name
|
|
83
|
+
raise RuntimeError(f"Shutdown requested ({signal_name})")
|
|
84
|
+
|
|
85
|
+
signal.signal(signal.SIGINT, handle_signal)
|
|
86
|
+
signal.signal(signal.SIGTERM, handle_signal)
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
# Start echo agent server
|
|
90
|
+
echo_process = start_process(echo_command)
|
|
91
|
+
wait_for_ready(ECHO_MANIFEST_URL, READY_TIMEOUT_SECONDS)
|
|
92
|
+
logger.info("asap.demo.echo_ready", url=ECHO_MANIFEST_URL)
|
|
93
|
+
|
|
94
|
+
# Demonstrate communication using coordinator dispatch logic
|
|
95
|
+
logger.info("asap.demo.starting_communication")
|
|
96
|
+
try:
|
|
97
|
+
response = asyncio.run(
|
|
98
|
+
dispatch_task(
|
|
99
|
+
payload={"message": "Hello from demo runner!"},
|
|
100
|
+
echo_base_url=ECHO_BASE_URL,
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
logger.info(
|
|
104
|
+
"asap.demo.communication_success",
|
|
105
|
+
request_id=response.correlation_id,
|
|
106
|
+
response_id=response.id,
|
|
107
|
+
payload_type=response.payload_type,
|
|
108
|
+
)
|
|
109
|
+
print(f"\n✅ Demo successful! Response: {response.payload}\n")
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.exception("asap.demo.communication_failed", error=str(e))
|
|
112
|
+
print(f"\n❌ Demo failed: {e}\n")
|
|
113
|
+
raise
|
|
114
|
+
|
|
115
|
+
finally:
|
|
116
|
+
_terminate_process(echo_process)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
main()
|
asap/models/__init__.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""ASAP Protocol Models.
|
|
2
|
+
|
|
3
|
+
This module provides all the Pydantic models for the ASAP protocol,
|
|
4
|
+
including entities, parts, payloads, and the envelope wrapper.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Base models
|
|
8
|
+
from asap.models.base import ASAPBaseModel
|
|
9
|
+
|
|
10
|
+
# Constants
|
|
11
|
+
from asap.models.constants import (
|
|
12
|
+
AGENT_URN_PATTERN,
|
|
13
|
+
ASAP_PROTOCOL_VERSION,
|
|
14
|
+
DEFAULT_TIMEOUT_SECONDS,
|
|
15
|
+
MAX_TASK_DEPTH,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Enums
|
|
19
|
+
from asap.models.enums import MessageRole, TaskStatus, UpdateType
|
|
20
|
+
|
|
21
|
+
# Type aliases
|
|
22
|
+
from asap.models.types import (
|
|
23
|
+
AgentURN,
|
|
24
|
+
ArtifactID,
|
|
25
|
+
ConversationID,
|
|
26
|
+
MessageID,
|
|
27
|
+
MIMEType,
|
|
28
|
+
PartID,
|
|
29
|
+
SemanticVersion,
|
|
30
|
+
SnapshotID,
|
|
31
|
+
TaskID,
|
|
32
|
+
URI,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# ID utilities
|
|
36
|
+
from asap.models.ids import extract_timestamp, generate_id
|
|
37
|
+
|
|
38
|
+
# Entities
|
|
39
|
+
from asap.models.entities import (
|
|
40
|
+
Agent,
|
|
41
|
+
Artifact,
|
|
42
|
+
AuthScheme,
|
|
43
|
+
Capability,
|
|
44
|
+
Conversation,
|
|
45
|
+
Endpoint,
|
|
46
|
+
Manifest,
|
|
47
|
+
Message,
|
|
48
|
+
Skill,
|
|
49
|
+
StateSnapshot,
|
|
50
|
+
Task,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Parts
|
|
54
|
+
from asap.models.parts import (
|
|
55
|
+
DataPart,
|
|
56
|
+
FilePart,
|
|
57
|
+
Part,
|
|
58
|
+
PartType,
|
|
59
|
+
ResourcePart,
|
|
60
|
+
TemplatePart,
|
|
61
|
+
TextPart,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Payloads
|
|
65
|
+
from asap.models.payloads import (
|
|
66
|
+
ArtifactNotify,
|
|
67
|
+
McpResourceData,
|
|
68
|
+
McpResourceFetch,
|
|
69
|
+
McpToolCall,
|
|
70
|
+
McpToolResult,
|
|
71
|
+
MessageSend,
|
|
72
|
+
PayloadType,
|
|
73
|
+
StateQuery,
|
|
74
|
+
StateRestore,
|
|
75
|
+
TaskCancel,
|
|
76
|
+
TaskRequest,
|
|
77
|
+
TaskResponse,
|
|
78
|
+
TaskUpdate,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Envelope
|
|
82
|
+
from asap.models.envelope import Envelope
|
|
83
|
+
|
|
84
|
+
__all__ = [
|
|
85
|
+
# Base
|
|
86
|
+
"ASAPBaseModel",
|
|
87
|
+
# Constants
|
|
88
|
+
"AGENT_URN_PATTERN",
|
|
89
|
+
"ASAP_PROTOCOL_VERSION",
|
|
90
|
+
"DEFAULT_TIMEOUT_SECONDS",
|
|
91
|
+
"MAX_TASK_DEPTH",
|
|
92
|
+
# Enums
|
|
93
|
+
"MessageRole",
|
|
94
|
+
"TaskStatus",
|
|
95
|
+
"UpdateType",
|
|
96
|
+
# Type aliases
|
|
97
|
+
"AgentURN",
|
|
98
|
+
"ArtifactID",
|
|
99
|
+
"ConversationID",
|
|
100
|
+
"MessageID",
|
|
101
|
+
"MIMEType",
|
|
102
|
+
"PartID",
|
|
103
|
+
"SemanticVersion",
|
|
104
|
+
"SnapshotID",
|
|
105
|
+
"TaskID",
|
|
106
|
+
"URI",
|
|
107
|
+
# IDs
|
|
108
|
+
"generate_id",
|
|
109
|
+
"extract_timestamp",
|
|
110
|
+
# Entities
|
|
111
|
+
"Agent",
|
|
112
|
+
"Artifact",
|
|
113
|
+
"AuthScheme",
|
|
114
|
+
"Capability",
|
|
115
|
+
"Conversation",
|
|
116
|
+
"Endpoint",
|
|
117
|
+
"Manifest",
|
|
118
|
+
"Message",
|
|
119
|
+
"Skill",
|
|
120
|
+
"StateSnapshot",
|
|
121
|
+
"Task",
|
|
122
|
+
# Parts
|
|
123
|
+
"DataPart",
|
|
124
|
+
"FilePart",
|
|
125
|
+
"Part",
|
|
126
|
+
"PartType",
|
|
127
|
+
"ResourcePart",
|
|
128
|
+
"TemplatePart",
|
|
129
|
+
"TextPart",
|
|
130
|
+
# Payloads
|
|
131
|
+
"ArtifactNotify",
|
|
132
|
+
"McpResourceData",
|
|
133
|
+
"McpResourceFetch",
|
|
134
|
+
"McpToolCall",
|
|
135
|
+
"McpToolResult",
|
|
136
|
+
"MessageSend",
|
|
137
|
+
"PayloadType",
|
|
138
|
+
"StateQuery",
|
|
139
|
+
"StateRestore",
|
|
140
|
+
"TaskCancel",
|
|
141
|
+
"TaskRequest",
|
|
142
|
+
"TaskResponse",
|
|
143
|
+
"TaskUpdate",
|
|
144
|
+
# Envelope
|
|
145
|
+
"Envelope",
|
|
146
|
+
]
|
asap/models/base.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Base Pydantic model configuration for ASAP protocol models.
|
|
2
|
+
|
|
3
|
+
All ASAP models inherit from ASAPBaseModel to ensure consistent behavior:
|
|
4
|
+
- Immutability (frozen=True) for thread-safety and predictability
|
|
5
|
+
- Strict validation (extra="forbid") to catch typos and invalid fields
|
|
6
|
+
- Flexible field naming (populate_by_name=True) for alias support
|
|
7
|
+
- JSON Schema generation with proper metadata
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, ConfigDict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ASAPBaseModel(BaseModel):
|
|
14
|
+
"""Base model for all ASAP protocol entities.
|
|
15
|
+
|
|
16
|
+
This base class provides:
|
|
17
|
+
- **Immutability**: Models are frozen after creation (thread-safe)
|
|
18
|
+
- **Strict validation**: Extra fields are forbidden (catches errors early)
|
|
19
|
+
- **Flexible naming**: Fields can be populated by name or alias
|
|
20
|
+
- **JSON Schema**: Proper schema generation for interoperability
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
>>> from pydantic import Field
|
|
24
|
+
>>> class MyModel(ASAPBaseModel):
|
|
25
|
+
... name: str
|
|
26
|
+
... count: int = Field(default=0, ge=0)
|
|
27
|
+
>>>
|
|
28
|
+
>>> obj = MyModel(name="test", count=5)
|
|
29
|
+
>>> obj.name
|
|
30
|
+
'test'
|
|
31
|
+
>>> obj.count = 10 # Raises ValidationError (frozen)
|
|
32
|
+
Traceback (most recent call last):
|
|
33
|
+
...
|
|
34
|
+
pydantic_core._pydantic_core.ValidationError: ...
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
model_config = ConfigDict(
|
|
38
|
+
# Immutability: prevents accidental mutations after creation
|
|
39
|
+
frozen=True,
|
|
40
|
+
# Strict validation: reject unknown fields to catch typos
|
|
41
|
+
extra="forbid",
|
|
42
|
+
# Allow populating fields by both name and alias
|
|
43
|
+
populate_by_name=True,
|
|
44
|
+
# JSON Schema configuration
|
|
45
|
+
# Use enum values (not names) in schema
|
|
46
|
+
use_enum_values=False,
|
|
47
|
+
# Validate default values
|
|
48
|
+
validate_default=True,
|
|
49
|
+
# Validate assignments (only relevant if frozen=False)
|
|
50
|
+
validate_assignment=True,
|
|
51
|
+
# JSON Schema extras for better documentation
|
|
52
|
+
json_schema_extra={
|
|
53
|
+
"additionalProperties": False,
|
|
54
|
+
},
|
|
55
|
+
)
|
asap/models/constants.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Constants for ASAP protocol.
|
|
2
|
+
|
|
3
|
+
This module defines protocol-wide constants used across the codebase.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Protocol version
|
|
7
|
+
ASAP_PROTOCOL_VERSION = "0.1"
|
|
8
|
+
|
|
9
|
+
# Default configuration values
|
|
10
|
+
DEFAULT_TIMEOUT_SECONDS = 600
|
|
11
|
+
MAX_TASK_DEPTH = 10 # Maximum nesting level for subtasks
|
|
12
|
+
|
|
13
|
+
# URN patterns
|
|
14
|
+
AGENT_URN_PATTERN = r"^urn:asap:agent:[a-z0-9-]+(?::[a-z0-9-]+)?$"
|