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,193 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LockStock OpenAI Agents SDK Guardrails
|
|
3
|
+
---------------------------------------
|
|
4
|
+
Guardrail implementations for OpenAI Agents SDK.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from agents import Agent
|
|
8
|
+
from lockstock_openai import LockStockGuardrail
|
|
9
|
+
|
|
10
|
+
guardrail = LockStockGuardrail(agent_id="agent_abc123")
|
|
11
|
+
|
|
12
|
+
agent = Agent(
|
|
13
|
+
name="my-agent",
|
|
14
|
+
instructions="You are a helpful assistant.",
|
|
15
|
+
guardrails=[guardrail]
|
|
16
|
+
)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import Any, Dict, Optional, List
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
|
|
22
|
+
from lockstock_core import LockStockClient
|
|
23
|
+
from lockstock_core.types import map_tool_to_capability, VerifyStatus
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class GuardrailResult:
|
|
28
|
+
"""Result of a guardrail check."""
|
|
29
|
+
passed: bool
|
|
30
|
+
reason: Optional[str] = None
|
|
31
|
+
modified_content: Optional[Any] = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class LockStockGuardrail:
|
|
35
|
+
"""
|
|
36
|
+
Base guardrail for OpenAI Agents SDK.
|
|
37
|
+
|
|
38
|
+
Validates that the agent has permission to perform requested actions.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
name = "lockstock"
|
|
42
|
+
description = "LockStock capability authorization guardrail"
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
agent_id: str,
|
|
47
|
+
secret: Optional[str] = None,
|
|
48
|
+
api_key: Optional[str] = None,
|
|
49
|
+
endpoint: str = "https://lockstock-api-i9kp.onrender.com",
|
|
50
|
+
block_on_failure: bool = True
|
|
51
|
+
):
|
|
52
|
+
"""
|
|
53
|
+
Initialize LockStock guardrail.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
agent_id: The agent's unique identifier
|
|
57
|
+
secret: The agent's HMAC secret
|
|
58
|
+
api_key: Admin API key
|
|
59
|
+
endpoint: LockStock API endpoint
|
|
60
|
+
block_on_failure: If True, block execution on auth failure
|
|
61
|
+
"""
|
|
62
|
+
self.client = LockStockClient(
|
|
63
|
+
agent_id=agent_id,
|
|
64
|
+
secret=secret,
|
|
65
|
+
api_key=api_key,
|
|
66
|
+
endpoint=endpoint
|
|
67
|
+
)
|
|
68
|
+
self.agent_id = agent_id
|
|
69
|
+
self.block_on_failure = block_on_failure
|
|
70
|
+
|
|
71
|
+
async def validate(
|
|
72
|
+
self,
|
|
73
|
+
agent: Any,
|
|
74
|
+
message: Dict[str, Any]
|
|
75
|
+
) -> GuardrailResult:
|
|
76
|
+
"""
|
|
77
|
+
Validate a message before it's processed or sent.
|
|
78
|
+
|
|
79
|
+
This method is called by the OpenAI Agents SDK.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
agent: The agent instance
|
|
83
|
+
message: The message to validate
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
GuardrailResult indicating if validation passed
|
|
87
|
+
"""
|
|
88
|
+
# Check for tool calls in assistant messages
|
|
89
|
+
if message.get("role") == "assistant" and "tool_calls" in message:
|
|
90
|
+
for tool_call in message.get("tool_calls", []):
|
|
91
|
+
if "function" in tool_call:
|
|
92
|
+
function_name = tool_call["function"].get("name", "")
|
|
93
|
+
|
|
94
|
+
# Map to capability
|
|
95
|
+
capability = map_tool_to_capability(function_name)
|
|
96
|
+
|
|
97
|
+
# Verify with LockStock
|
|
98
|
+
result = await self.client.verify(
|
|
99
|
+
task=capability,
|
|
100
|
+
tool_name=function_name
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if not result.authorized:
|
|
104
|
+
if self.block_on_failure:
|
|
105
|
+
return GuardrailResult(
|
|
106
|
+
passed=False,
|
|
107
|
+
reason=f"LockStock DENIED: {function_name} requires {capability}. "
|
|
108
|
+
f"Agent {self.agent_id} lacks this capability."
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return GuardrailResult(passed=True)
|
|
112
|
+
|
|
113
|
+
async def close(self):
|
|
114
|
+
"""Close the underlying client."""
|
|
115
|
+
await self.client.close()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class LockStockInputGuardrail(LockStockGuardrail):
|
|
119
|
+
"""
|
|
120
|
+
Input guardrail for OpenAI Agents SDK.
|
|
121
|
+
|
|
122
|
+
Validates incoming messages/requests before processing.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
name = "lockstock_input"
|
|
126
|
+
description = "LockStock input validation guardrail"
|
|
127
|
+
|
|
128
|
+
async def validate_input(
|
|
129
|
+
self,
|
|
130
|
+
agent: Any,
|
|
131
|
+
user_input: str,
|
|
132
|
+
context: Optional[Dict[str, Any]] = None
|
|
133
|
+
) -> GuardrailResult:
|
|
134
|
+
"""
|
|
135
|
+
Validate user input before processing.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
agent: The agent instance
|
|
139
|
+
user_input: The user's input string
|
|
140
|
+
context: Optional context dictionary
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
GuardrailResult indicating if validation passed
|
|
144
|
+
"""
|
|
145
|
+
# Input validation can check for:
|
|
146
|
+
# - Sensitive data patterns
|
|
147
|
+
# - Rate limiting
|
|
148
|
+
# - Input size limits
|
|
149
|
+
|
|
150
|
+
# For now, pass through - capability checks happen at tool use
|
|
151
|
+
return GuardrailResult(passed=True)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class LockStockOutputGuardrail(LockStockGuardrail):
|
|
155
|
+
"""
|
|
156
|
+
Output guardrail for OpenAI Agents SDK.
|
|
157
|
+
|
|
158
|
+
Validates agent outputs before returning to user.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
name = "lockstock_output"
|
|
162
|
+
description = "LockStock output validation guardrail"
|
|
163
|
+
|
|
164
|
+
async def validate_output(
|
|
165
|
+
self,
|
|
166
|
+
agent: Any,
|
|
167
|
+
output: str,
|
|
168
|
+
context: Optional[Dict[str, Any]] = None
|
|
169
|
+
) -> GuardrailResult:
|
|
170
|
+
"""
|
|
171
|
+
Validate agent output before returning.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
agent: The agent instance
|
|
175
|
+
output: The agent's output string
|
|
176
|
+
context: Optional context dictionary
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
GuardrailResult indicating if validation passed
|
|
180
|
+
"""
|
|
181
|
+
# Output validation can check for:
|
|
182
|
+
# - Sensitive data leakage
|
|
183
|
+
# - Compliance requirements
|
|
184
|
+
# - Output format requirements
|
|
185
|
+
|
|
186
|
+
# Log the output to audit trail
|
|
187
|
+
await self.client.log_audit(
|
|
188
|
+
action="output",
|
|
189
|
+
status="COMPLETED",
|
|
190
|
+
metadata={"output_length": len(output)}
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
return GuardrailResult(passed=True)
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LockStock OpenAI Agents SDK Tracing
|
|
3
|
+
------------------------------------
|
|
4
|
+
Tracing integration for audit trail logging.
|
|
5
|
+
|
|
6
|
+
The OpenAI Agents SDK supports extensible tracing. This module
|
|
7
|
+
integrates LockStock's hash chain audit trail with the SDK's
|
|
8
|
+
tracing infrastructure.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Dict, Any, Optional
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
from lockstock_core import LockStockClient
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class TraceSpan:
|
|
20
|
+
"""A span in the trace."""
|
|
21
|
+
name: str
|
|
22
|
+
start_time: float
|
|
23
|
+
end_time: Optional[float] = None
|
|
24
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
25
|
+
parent_span_id: Optional[str] = None
|
|
26
|
+
span_id: Optional[str] = None
|
|
27
|
+
status: str = "running"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class LockStockTracer:
|
|
31
|
+
"""
|
|
32
|
+
Tracer that logs agent activity to LockStock's audit trail.
|
|
33
|
+
|
|
34
|
+
Integrates with OpenAI Agents SDK's tracing infrastructure
|
|
35
|
+
while maintaining cryptographic audit guarantees.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
agent_id: str,
|
|
41
|
+
secret: Optional[str] = None,
|
|
42
|
+
api_key: Optional[str] = None,
|
|
43
|
+
endpoint: str = "https://lockstock-api-i9kp.onrender.com"
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
Initialize LockStock tracer.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
agent_id: The agent's unique identifier
|
|
50
|
+
secret: The agent's HMAC secret
|
|
51
|
+
api_key: Admin API key
|
|
52
|
+
endpoint: LockStock API endpoint
|
|
53
|
+
"""
|
|
54
|
+
self.client = LockStockClient(
|
|
55
|
+
agent_id=agent_id,
|
|
56
|
+
secret=secret,
|
|
57
|
+
api_key=api_key,
|
|
58
|
+
endpoint=endpoint
|
|
59
|
+
)
|
|
60
|
+
self.agent_id = agent_id
|
|
61
|
+
self._spans: Dict[str, TraceSpan] = {}
|
|
62
|
+
self._span_counter = 0
|
|
63
|
+
|
|
64
|
+
async def start_span(
|
|
65
|
+
self,
|
|
66
|
+
name: str,
|
|
67
|
+
parent_span_id: Optional[str] = None,
|
|
68
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
69
|
+
) -> str:
|
|
70
|
+
"""
|
|
71
|
+
Start a new trace span.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
name: Name of the span
|
|
75
|
+
parent_span_id: Optional parent span ID
|
|
76
|
+
metadata: Optional metadata
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The span ID
|
|
80
|
+
"""
|
|
81
|
+
self._span_counter += 1
|
|
82
|
+
span_id = f"{self.agent_id}:span:{self._span_counter}"
|
|
83
|
+
|
|
84
|
+
span = TraceSpan(
|
|
85
|
+
name=name,
|
|
86
|
+
start_time=time.time(),
|
|
87
|
+
metadata=metadata or {},
|
|
88
|
+
parent_span_id=parent_span_id,
|
|
89
|
+
span_id=span_id
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
self._spans[span_id] = span
|
|
93
|
+
|
|
94
|
+
# Log to audit trail
|
|
95
|
+
await self.client.log_audit(
|
|
96
|
+
action=f"span_start:{name}",
|
|
97
|
+
status="STARTED",
|
|
98
|
+
metadata={
|
|
99
|
+
"span_id": span_id,
|
|
100
|
+
"parent_span_id": parent_span_id
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return span_id
|
|
105
|
+
|
|
106
|
+
async def end_span(
|
|
107
|
+
self,
|
|
108
|
+
span_id: str,
|
|
109
|
+
status: str = "success",
|
|
110
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
111
|
+
):
|
|
112
|
+
"""
|
|
113
|
+
End a trace span.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
span_id: The span ID to end
|
|
117
|
+
status: Final status of the span
|
|
118
|
+
metadata: Optional additional metadata
|
|
119
|
+
"""
|
|
120
|
+
if span_id not in self._spans:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
span = self._spans[span_id]
|
|
124
|
+
span.end_time = time.time()
|
|
125
|
+
span.status = status
|
|
126
|
+
|
|
127
|
+
if metadata:
|
|
128
|
+
span.metadata.update(metadata)
|
|
129
|
+
|
|
130
|
+
# Log to audit trail
|
|
131
|
+
await self.client.log_audit(
|
|
132
|
+
action=f"span_end:{span.name}",
|
|
133
|
+
status=status.upper(),
|
|
134
|
+
metadata={
|
|
135
|
+
"span_id": span_id,
|
|
136
|
+
"duration_ms": (span.end_time - span.start_time) * 1000
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
async def log_event(
|
|
141
|
+
self,
|
|
142
|
+
name: str,
|
|
143
|
+
span_id: Optional[str] = None,
|
|
144
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
145
|
+
):
|
|
146
|
+
"""
|
|
147
|
+
Log an event within a span.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
name: Event name
|
|
151
|
+
span_id: Optional span ID
|
|
152
|
+
metadata: Optional metadata
|
|
153
|
+
"""
|
|
154
|
+
await self.client.log_audit(
|
|
155
|
+
action=f"event:{name}",
|
|
156
|
+
status="LOGGED",
|
|
157
|
+
metadata={
|
|
158
|
+
"span_id": span_id,
|
|
159
|
+
**(metadata or {})
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
async def log_tool_call(
|
|
164
|
+
self,
|
|
165
|
+
tool_name: str,
|
|
166
|
+
tool_input: Dict[str, Any],
|
|
167
|
+
tool_output: Optional[Any] = None,
|
|
168
|
+
span_id: Optional[str] = None,
|
|
169
|
+
authorized: bool = True
|
|
170
|
+
):
|
|
171
|
+
"""
|
|
172
|
+
Log a tool call with its input and output.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
tool_name: Name of the tool
|
|
176
|
+
tool_input: Tool input parameters
|
|
177
|
+
tool_output: Tool output (optional)
|
|
178
|
+
span_id: Optional span ID
|
|
179
|
+
authorized: Whether the call was authorized
|
|
180
|
+
"""
|
|
181
|
+
await self.client.log_audit(
|
|
182
|
+
action=f"tool:{tool_name}",
|
|
183
|
+
status="AUTHORIZED" if authorized else "DENIED",
|
|
184
|
+
metadata={
|
|
185
|
+
"span_id": span_id,
|
|
186
|
+
"tool_name": tool_name,
|
|
187
|
+
"input_keys": list(tool_input.keys()),
|
|
188
|
+
"has_output": tool_output is not None
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def get_trace_summary(self) -> Dict[str, Any]:
|
|
193
|
+
"""
|
|
194
|
+
Get a summary of all traced spans.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Dictionary with trace summary
|
|
198
|
+
"""
|
|
199
|
+
completed = [s for s in self._spans.values() if s.end_time is not None]
|
|
200
|
+
running = [s for s in self._spans.values() if s.end_time is None]
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
"agent_id": self.agent_id,
|
|
204
|
+
"total_spans": len(self._spans),
|
|
205
|
+
"completed_spans": len(completed),
|
|
206
|
+
"running_spans": len(running),
|
|
207
|
+
"spans": [
|
|
208
|
+
{
|
|
209
|
+
"span_id": s.span_id,
|
|
210
|
+
"name": s.name,
|
|
211
|
+
"status": s.status,
|
|
212
|
+
"duration_ms": (s.end_time - s.start_time) * 1000 if s.end_time else None
|
|
213
|
+
}
|
|
214
|
+
for s in self._spans.values()
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async def close(self):
|
|
219
|
+
"""Close the underlying client."""
|
|
220
|
+
await self.client.close()
|