lockstock-integrations 1.0.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,23 @@
1
+ """
2
+ LockStock A2A Protocol Adapter
3
+ -------------------------------
4
+ Adapts LockStock Agent Cards to the A2A (Agent-to-Agent) Protocol.
5
+
6
+ The A2A protocol, developed by Google and now under the Linux Foundation,
7
+ provides a standard for agent interoperability. This adapter ensures
8
+ LockStock agents can participate in A2A ecosystems.
9
+
10
+ See: https://a2a-protocol.org/
11
+ """
12
+
13
+ from .adapter import A2AAdapter
14
+ from .agent_card import A2AAgentCard, A2ACapability, A2ASecurity
15
+ from .task_handler import A2ATaskHandler
16
+
17
+ __all__ = [
18
+ "A2AAdapter",
19
+ "A2AAgentCard",
20
+ "A2ACapability",
21
+ "A2ASecurity",
22
+ "A2ATaskHandler"
23
+ ]
@@ -0,0 +1,216 @@
1
+ """
2
+ LockStock A2A Protocol Adapter
3
+ -------------------------------
4
+ Bidirectional adapter between LockStock and A2A protocols.
5
+ """
6
+
7
+ from typing import Dict, Any, Optional, List
8
+ import json
9
+
10
+ from lockstock_core import LockStockClient, AgentCard
11
+ from .agent_card import A2AAgentCard, A2ACapability, A2ASecurity, A2AAuthType
12
+
13
+
14
+ class A2AAdapter:
15
+ """
16
+ Adapter for converting between LockStock and A2A protocols.
17
+
18
+ Enables LockStock agents to participate in A2A ecosystems
19
+ and A2A agents to be verified through LockStock.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ endpoint: str = "https://lockstock-api-i9kp.onrender.com",
25
+ api_key: Optional[str] = None
26
+ ):
27
+ """
28
+ Initialize A2A adapter.
29
+
30
+ Args:
31
+ endpoint: LockStock API endpoint
32
+ api_key: Admin API key for management operations
33
+ """
34
+ self.endpoint = endpoint.rstrip("/")
35
+ self.api_key = api_key
36
+
37
+ def to_a2a_card(self, lockstock_card: Dict[str, Any]) -> A2AAgentCard:
38
+ """
39
+ Convert a LockStock agent card to A2A format.
40
+
41
+ Args:
42
+ lockstock_card: LockStock internal agent card
43
+
44
+ Returns:
45
+ A2A-compatible agent card
46
+ """
47
+ return A2AAgentCard.from_lockstock_card(
48
+ lockstock_card,
49
+ endpoint=self.endpoint
50
+ )
51
+
52
+ def from_a2a_card(self, a2a_card: Dict[str, Any]) -> Dict[str, Any]:
53
+ """
54
+ Convert an A2A agent card to LockStock format.
55
+
56
+ Args:
57
+ a2a_card: A2A protocol agent card
58
+
59
+ Returns:
60
+ LockStock-compatible agent card
61
+ """
62
+ metadata = a2a_card.get("metadata", {})
63
+ capabilities = a2a_card.get("capabilities", [])
64
+
65
+ return {
66
+ "agent_id": metadata.get("id", "unknown"),
67
+ "display_name": metadata.get("name", "Unknown Agent"),
68
+ "description": metadata.get("description"),
69
+ "capabilities": [cap.get("name") for cap in capabilities],
70
+ "status": "active",
71
+ "source": "a2a_import",
72
+ "a2a_metadata": {
73
+ "version": a2a_card.get("version"),
74
+ "provider": metadata.get("provider"),
75
+ "security": a2a_card.get("security"),
76
+ "endpoints": a2a_card.get("endpoints")
77
+ }
78
+ }
79
+
80
+ def to_a2a_json(self, lockstock_card: Dict[str, Any]) -> str:
81
+ """
82
+ Convert LockStock card to A2A JSON string.
83
+
84
+ Args:
85
+ lockstock_card: LockStock agent card
86
+
87
+ Returns:
88
+ JSON string in A2A format
89
+ """
90
+ a2a_card = self.to_a2a_card(lockstock_card)
91
+ return a2a_card.to_json()
92
+
93
+ def capability_mapping(self) -> Dict[str, str]:
94
+ """
95
+ Get mapping between LockStock capabilities and A2A capability types.
96
+
97
+ Returns:
98
+ Dictionary mapping LockStock to A2A capability names
99
+ """
100
+ return {
101
+ # LockStock -> A2A standard capability names
102
+ "DEPLOY": "code_execution",
103
+ "RESTART": "service_management",
104
+ "BACKUP": "data_management",
105
+ "ROLLBACK": "state_management",
106
+ "HEARTBEAT": "health_monitoring",
107
+ "CHECKPOINT": "state_checkpoint",
108
+ "TELEPORT": "agent_migration",
109
+
110
+ # Tool-based capabilities
111
+ "FILE_READ": "file_operations",
112
+ "FILE_WRITE": "file_operations",
113
+ "NETWORK": "network_access",
114
+ "SHELL_EXEC": "code_execution"
115
+ }
116
+
117
+ def generate_well_known_agent_card(
118
+ self,
119
+ agent_id: str,
120
+ name: str,
121
+ capabilities: List[str],
122
+ description: Optional[str] = None
123
+ ) -> str:
124
+ """
125
+ Generate a well-known agent card for A2A discovery.
126
+
127
+ In A2A, agents advertise themselves at:
128
+ https://your-domain.com/.well-known/agent.json
129
+
130
+ Args:
131
+ agent_id: Agent identifier
132
+ name: Agent display name
133
+ capabilities: List of capabilities
134
+ description: Optional description
135
+
136
+ Returns:
137
+ JSON string suitable for /.well-known/agent.json
138
+ """
139
+ a2a_capabilities = [
140
+ A2ACapability(
141
+ name=cap,
142
+ type="lockstock_verified",
143
+ description=f"LockStock-verified capability: {cap}"
144
+ )
145
+ for cap in capabilities
146
+ ]
147
+
148
+ security = A2ASecurity(
149
+ protocol="LockStock-v1",
150
+ auth_type=A2AAuthType.LOCKSTOCK,
151
+ verification_endpoint=f"{self.endpoint}/verify",
152
+ signing_supported=True
153
+ )
154
+
155
+ card = A2AAgentCard(
156
+ agent_id=agent_id,
157
+ name=name,
158
+ description=description or f"LockStock-secured agent: {name}",
159
+ capabilities=a2a_capabilities,
160
+ security=security,
161
+ task_endpoint=f"{self.endpoint}/a2a/task",
162
+ status_endpoint=f"{self.endpoint}/a2a/status/{agent_id}"
163
+ )
164
+
165
+ return card.to_json()
166
+
167
+ async def verify_a2a_agent(
168
+ self,
169
+ a2a_card: Dict[str, Any],
170
+ capability: str
171
+ ) -> Dict[str, Any]:
172
+ """
173
+ Verify an A2A agent has a claimed capability.
174
+
175
+ When an external A2A agent claims to have a capability,
176
+ this verifies it through their declared verification endpoint.
177
+
178
+ Args:
179
+ a2a_card: The A2A agent card
180
+ capability: Capability to verify
181
+
182
+ Returns:
183
+ Verification result
184
+ """
185
+ security = a2a_card.get("security", {})
186
+ protocol = security.get("protocol", "")
187
+
188
+ if protocol == "LockStock-v1":
189
+ # This is a LockStock-secured agent, verify through our system
190
+ verification_endpoint = security.get("verification_endpoint")
191
+ agent_id = a2a_card.get("metadata", {}).get("id")
192
+
193
+ if verification_endpoint and agent_id:
194
+ # Create client to verify
195
+ client = LockStockClient(
196
+ agent_id=agent_id,
197
+ endpoint=self.endpoint
198
+ )
199
+
200
+ result = await client.verify(task=capability)
201
+ await client.close()
202
+
203
+ return {
204
+ "verified": result.authorized,
205
+ "protocol": "LockStock-v1",
206
+ "agent_id": agent_id,
207
+ "capability": capability,
208
+ "state_hash": result.state_hash
209
+ }
210
+
211
+ # Unknown protocol, cannot verify
212
+ return {
213
+ "verified": False,
214
+ "protocol": protocol,
215
+ "reason": f"Unknown verification protocol: {protocol}"
216
+ }
@@ -0,0 +1,169 @@
1
+ """
2
+ A2A Agent Card Types
3
+ ---------------------
4
+ Type definitions matching the A2A Protocol Agent Card specification.
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+ from typing import List, Optional, Dict, Any
9
+ from enum import Enum
10
+ import json
11
+
12
+
13
+ class A2AAuthType(str, Enum):
14
+ """A2A authentication types."""
15
+ NONE = "none"
16
+ API_KEY = "api_key"
17
+ OAUTH2 = "oauth2"
18
+ BEARER = "bearer"
19
+ LOCKSTOCK = "lockstock" # Our custom auth type
20
+
21
+
22
+ @dataclass
23
+ class A2ASecurity:
24
+ """A2A security configuration."""
25
+ protocol: str = "LockStock-v1"
26
+ auth_type: A2AAuthType = A2AAuthType.LOCKSTOCK
27
+ verification_endpoint: Optional[str] = None
28
+ public_key: Optional[str] = None
29
+ signing_supported: bool = True
30
+
31
+ def to_dict(self) -> Dict[str, Any]:
32
+ return {
33
+ "protocol": self.protocol,
34
+ "auth_type": self.auth_type.value,
35
+ "verification_endpoint": self.verification_endpoint,
36
+ "public_key": self.public_key,
37
+ "signing_supported": self.signing_supported
38
+ }
39
+
40
+
41
+ @dataclass
42
+ class A2ACapability:
43
+ """A2A capability definition."""
44
+ name: str
45
+ type: str = "lockstock_verified"
46
+ description: Optional[str] = None
47
+ input_schema: Optional[Dict[str, Any]] = None
48
+ output_schema: Optional[Dict[str, Any]] = None
49
+
50
+ def to_dict(self) -> Dict[str, Any]:
51
+ result = {
52
+ "name": self.name,
53
+ "type": self.type
54
+ }
55
+ if self.description:
56
+ result["description"] = self.description
57
+ if self.input_schema:
58
+ result["input_schema"] = self.input_schema
59
+ if self.output_schema:
60
+ result["output_schema"] = self.output_schema
61
+ return result
62
+
63
+
64
+ @dataclass
65
+ class A2AAgentCard:
66
+ """
67
+ A2A Protocol Agent Card.
68
+
69
+ This is the standard format for advertising agent capabilities
70
+ in the A2A ecosystem.
71
+ """
72
+
73
+ # Required fields
74
+ agent_id: str
75
+ name: str
76
+
77
+ # Optional metadata
78
+ version: str = "1.0"
79
+ description: Optional[str] = None
80
+ provider: str = "LockStock"
81
+
82
+ # Capabilities
83
+ capabilities: List[A2ACapability] = field(default_factory=list)
84
+
85
+ # Security
86
+ security: Optional[A2ASecurity] = None
87
+
88
+ # Endpoints
89
+ task_endpoint: Optional[str] = None
90
+ status_endpoint: Optional[str] = None
91
+
92
+ # Additional metadata
93
+ metadata: Dict[str, Any] = field(default_factory=dict)
94
+
95
+ def to_dict(self) -> Dict[str, Any]:
96
+ """Convert to A2A Agent Card JSON format."""
97
+ return {
98
+ "version": self.version,
99
+ "metadata": {
100
+ "id": self.agent_id,
101
+ "name": self.name,
102
+ "description": self.description or f"LockStock-secured agent: {self.name}",
103
+ "provider": self.provider,
104
+ **self.metadata
105
+ },
106
+ "capabilities": [cap.to_dict() for cap in self.capabilities],
107
+ "security": self.security.to_dict() if self.security else None,
108
+ "endpoints": {
109
+ "task": self.task_endpoint,
110
+ "status": self.status_endpoint
111
+ }
112
+ }
113
+
114
+ def to_json(self, indent: int = 2) -> str:
115
+ """Convert to JSON string."""
116
+ return json.dumps(self.to_dict(), indent=indent)
117
+
118
+ @classmethod
119
+ def from_lockstock_card(
120
+ cls,
121
+ lockstock_card: Dict[str, Any],
122
+ endpoint: str = "https://lockstock-api-i9kp.onrender.com"
123
+ ) -> "A2AAgentCard":
124
+ """
125
+ Create an A2A Agent Card from a LockStock agent card.
126
+
127
+ Args:
128
+ lockstock_card: LockStock internal agent card
129
+ endpoint: LockStock API endpoint
130
+
131
+ Returns:
132
+ A2A-compatible agent card
133
+ """
134
+ agent_id = lockstock_card.get("agent_id", "unknown")
135
+
136
+ # Convert LockStock capabilities to A2A capabilities
137
+ capabilities = [
138
+ A2ACapability(
139
+ name=cap,
140
+ type="lockstock_verified",
141
+ description=f"Cryptographically verified capability: {cap}"
142
+ )
143
+ for cap in lockstock_card.get("capabilities", [])
144
+ ]
145
+
146
+ # Create security configuration
147
+ security = A2ASecurity(
148
+ protocol="LockStock-v1",
149
+ auth_type=A2AAuthType.LOCKSTOCK,
150
+ verification_endpoint=f"{endpoint}/verify",
151
+ public_key=lockstock_card.get("public_key"),
152
+ signing_supported=True
153
+ )
154
+
155
+ return cls(
156
+ agent_id=agent_id,
157
+ name=lockstock_card.get("display_name", agent_id),
158
+ description=lockstock_card.get("description"),
159
+ capabilities=capabilities,
160
+ security=security,
161
+ task_endpoint=f"{endpoint}/a2a/task",
162
+ status_endpoint=f"{endpoint}/a2a/status/{agent_id}",
163
+ metadata={
164
+ "fingerprint": lockstock_card.get("fingerprint"),
165
+ "owner_id": lockstock_card.get("owner_id"),
166
+ "created_at": lockstock_card.get("created_at"),
167
+ "status": lockstock_card.get("status", "active")
168
+ }
169
+ )
@@ -0,0 +1,294 @@
1
+ """
2
+ A2A Task Handler
3
+ -----------------
4
+ Handler for A2A protocol task requests.
5
+
6
+ The A2A protocol defines a task lifecycle with states:
7
+ - pending: Task received but not started
8
+ - running: Task in progress
9
+ - completed: Task finished successfully
10
+ - failed: Task failed
11
+ - cancelled: Task was cancelled
12
+ """
13
+
14
+ from dataclasses import dataclass, field
15
+ from typing import Dict, Any, Optional, Callable, Awaitable
16
+ from enum import Enum
17
+ import time
18
+ import uuid
19
+
20
+ from lockstock_core import LockStockClient
21
+ from lockstock_core.types import map_tool_to_capability
22
+
23
+
24
+ class A2ATaskState(str, Enum):
25
+ """A2A task lifecycle states."""
26
+ PENDING = "pending"
27
+ RUNNING = "running"
28
+ COMPLETED = "completed"
29
+ FAILED = "failed"
30
+ CANCELLED = "cancelled"
31
+
32
+
33
+ @dataclass
34
+ class A2ATask:
35
+ """An A2A protocol task."""
36
+ task_id: str
37
+ agent_id: str
38
+ capability: str
39
+ input_data: Dict[str, Any]
40
+ state: A2ATaskState = A2ATaskState.PENDING
41
+ output_data: Optional[Dict[str, Any]] = None
42
+ error: Optional[str] = None
43
+ created_at: float = field(default_factory=time.time)
44
+ updated_at: float = field(default_factory=time.time)
45
+ lockstock_hash: Optional[str] = None
46
+
47
+ def to_dict(self) -> Dict[str, Any]:
48
+ return {
49
+ "task_id": self.task_id,
50
+ "agent_id": self.agent_id,
51
+ "capability": self.capability,
52
+ "state": self.state.value,
53
+ "input_data": self.input_data,
54
+ "output_data": self.output_data,
55
+ "error": self.error,
56
+ "created_at": self.created_at,
57
+ "updated_at": self.updated_at,
58
+ "lockstock_verification": {
59
+ "hash": self.lockstock_hash
60
+ } if self.lockstock_hash else None
61
+ }
62
+
63
+
64
+ class A2ATaskHandler:
65
+ """
66
+ Handler for A2A protocol task requests.
67
+
68
+ Manages the task lifecycle while integrating with LockStock
69
+ for authorization and audit.
70
+ """
71
+
72
+ # Type for task executor functions
73
+ TaskExecutor = Callable[[str, Dict[str, Any]], Awaitable[Dict[str, Any]]]
74
+
75
+ def __init__(
76
+ self,
77
+ agent_id: str,
78
+ secret: Optional[str] = None,
79
+ api_key: Optional[str] = None,
80
+ endpoint: str = "https://lockstock-api-i9kp.onrender.com"
81
+ ):
82
+ """
83
+ Initialize A2A task handler.
84
+
85
+ Args:
86
+ agent_id: The agent's unique identifier
87
+ secret: The agent's HMAC secret
88
+ api_key: Admin API key
89
+ endpoint: LockStock API endpoint
90
+ """
91
+ self.client = LockStockClient(
92
+ agent_id=agent_id,
93
+ secret=secret,
94
+ api_key=api_key,
95
+ endpoint=endpoint
96
+ )
97
+ self.agent_id = agent_id
98
+ self._tasks: Dict[str, A2ATask] = {}
99
+ self._executors: Dict[str, "A2ATaskHandler.TaskExecutor"] = {}
100
+
101
+ def register_executor(
102
+ self,
103
+ capability: str,
104
+ executor: "A2ATaskHandler.TaskExecutor"
105
+ ):
106
+ """
107
+ Register an executor for a capability.
108
+
109
+ Args:
110
+ capability: The capability this executor handles
111
+ executor: Async function that executes tasks
112
+ """
113
+ self._executors[capability.upper()] = executor
114
+
115
+ async def create_task(
116
+ self,
117
+ capability: str,
118
+ input_data: Dict[str, Any],
119
+ task_id: Optional[str] = None
120
+ ) -> A2ATask:
121
+ """
122
+ Create a new A2A task.
123
+
124
+ Args:
125
+ capability: Required capability
126
+ input_data: Task input data
127
+ task_id: Optional task ID (generated if not provided)
128
+
129
+ Returns:
130
+ Created task
131
+ """
132
+ task_id = task_id or f"task_{uuid.uuid4().hex[:16]}"
133
+
134
+ task = A2ATask(
135
+ task_id=task_id,
136
+ agent_id=self.agent_id,
137
+ capability=capability.upper(),
138
+ input_data=input_data
139
+ )
140
+
141
+ self._tasks[task_id] = task
142
+
143
+ # Log task creation
144
+ await self.client.log_audit(
145
+ action="a2a_task_create",
146
+ status="CREATED",
147
+ metadata={
148
+ "task_id": task_id,
149
+ "capability": capability
150
+ }
151
+ )
152
+
153
+ return task
154
+
155
+ async def execute_task(self, task_id: str) -> A2ATask:
156
+ """
157
+ Execute a pending task.
158
+
159
+ Args:
160
+ task_id: Task to execute
161
+
162
+ Returns:
163
+ Updated task with result
164
+ """
165
+ task = self._tasks.get(task_id)
166
+ if not task:
167
+ raise ValueError(f"Task not found: {task_id}")
168
+
169
+ if task.state != A2ATaskState.PENDING:
170
+ raise ValueError(f"Task is not pending: {task.state}")
171
+
172
+ # Verify capability with LockStock
173
+ result = await self.client.verify(task=task.capability)
174
+
175
+ if not result.authorized:
176
+ task.state = A2ATaskState.FAILED
177
+ task.error = f"LockStock authorization denied: {result.reason}"
178
+ task.updated_at = time.time()
179
+ return task
180
+
181
+ task.lockstock_hash = result.state_hash
182
+
183
+ # Find executor
184
+ executor = self._executors.get(task.capability)
185
+ if not executor:
186
+ task.state = A2ATaskState.FAILED
187
+ task.error = f"No executor registered for capability: {task.capability}"
188
+ task.updated_at = time.time()
189
+ return task
190
+
191
+ # Execute
192
+ task.state = A2ATaskState.RUNNING
193
+ task.updated_at = time.time()
194
+
195
+ try:
196
+ output = await executor(task.capability, task.input_data)
197
+ task.state = A2ATaskState.COMPLETED
198
+ task.output_data = output
199
+
200
+ # Log success
201
+ await self.client.log_audit(
202
+ action="a2a_task_complete",
203
+ status="COMPLETED",
204
+ metadata={
205
+ "task_id": task_id,
206
+ "capability": task.capability
207
+ }
208
+ )
209
+
210
+ except Exception as e:
211
+ task.state = A2ATaskState.FAILED
212
+ task.error = str(e)
213
+
214
+ # Log failure
215
+ await self.client.log_audit(
216
+ action="a2a_task_fail",
217
+ status="FAILED",
218
+ metadata={
219
+ "task_id": task_id,
220
+ "error": str(e)
221
+ }
222
+ )
223
+
224
+ task.updated_at = time.time()
225
+ return task
226
+
227
+ async def get_task_status(self, task_id: str) -> Optional[A2ATask]:
228
+ """
229
+ Get task status.
230
+
231
+ Args:
232
+ task_id: Task identifier
233
+
234
+ Returns:
235
+ Task if found, None otherwise
236
+ """
237
+ return self._tasks.get(task_id)
238
+
239
+ async def cancel_task(self, task_id: str) -> Optional[A2ATask]:
240
+ """
241
+ Cancel a pending or running task.
242
+
243
+ Args:
244
+ task_id: Task to cancel
245
+
246
+ Returns:
247
+ Updated task if found
248
+ """
249
+ task = self._tasks.get(task_id)
250
+ if not task:
251
+ return None
252
+
253
+ if task.state in [A2ATaskState.COMPLETED, A2ATaskState.FAILED]:
254
+ return task # Already finished
255
+
256
+ task.state = A2ATaskState.CANCELLED
257
+ task.updated_at = time.time()
258
+
259
+ await self.client.log_audit(
260
+ action="a2a_task_cancel",
261
+ status="CANCELLED",
262
+ metadata={"task_id": task_id}
263
+ )
264
+
265
+ return task
266
+
267
+ async def list_tasks(
268
+ self,
269
+ state: Optional[A2ATaskState] = None,
270
+ limit: int = 100
271
+ ) -> list:
272
+ """
273
+ List tasks.
274
+
275
+ Args:
276
+ state: Filter by state (optional)
277
+ limit: Maximum tasks to return
278
+
279
+ Returns:
280
+ List of task summaries
281
+ """
282
+ tasks = list(self._tasks.values())
283
+
284
+ if state:
285
+ tasks = [t for t in tasks if t.state == state]
286
+
287
+ # Sort by created_at descending
288
+ tasks.sort(key=lambda t: t.created_at, reverse=True)
289
+
290
+ return [t.to_dict() for t in tasks[:limit]]
291
+
292
+ async def close(self):
293
+ """Close the underlying client."""
294
+ await self.client.close()