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,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()