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.
- lockstock_a2a/__init__.py +23 -0
- lockstock_a2a/adapter.py +216 -0
- lockstock_a2a/agent_card.py +169 -0
- lockstock_a2a/task_handler.py +294 -0
- lockstock_claude/__init__.py +10 -0
- lockstock_claude/hooks.py +182 -0
- lockstock_claude/skills.py +145 -0
- lockstock_integrations-1.0.0.dist-info/METADATA +141 -0
- lockstock_integrations-1.0.0.dist-info/RECORD +16 -0
- lockstock_integrations-1.0.0.dist-info/WHEEL +4 -0
- lockstock_langgraph/__init__.py +19 -0
- lockstock_langgraph/checkpointer.py +225 -0
- lockstock_langgraph/middleware.py +295 -0
- lockstock_openai/__init__.py +15 -0
- lockstock_openai/guardrails.py +193 -0
- lockstock_openai/tracing.py +220 -0
|
@@ -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
|
+
]
|
lockstock_a2a/adapter.py
ADDED
|
@@ -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()
|