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.
@@ -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()
@@ -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
+ )
@@ -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-]+)?$"