helm-sdk 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.
- helm_sdk/__init__.py +28 -0
- helm_sdk/client.py +156 -0
- helm_sdk/types_gen.py +173 -0
- helm_sdk-0.1.0.dist-info/METADATA +75 -0
- helm_sdk-0.1.0.dist-info/RECORD +7 -0
- helm_sdk-0.1.0.dist-info/WHEEL +5 -0
- helm_sdk-0.1.0.dist-info/top_level.txt +1 -0
helm_sdk/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""HELM SDK for Python."""
|
|
2
|
+
|
|
3
|
+
from .client import HelmClient, HelmApiError
|
|
4
|
+
from .types_gen import (
|
|
5
|
+
ApprovalRequest,
|
|
6
|
+
ChatCompletionRequest,
|
|
7
|
+
ChatCompletionResponse,
|
|
8
|
+
ConformanceRequest,
|
|
9
|
+
ConformanceResult,
|
|
10
|
+
Receipt,
|
|
11
|
+
Session,
|
|
12
|
+
VerificationResult,
|
|
13
|
+
VersionInfo,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"HelmClient",
|
|
18
|
+
"HelmApiError",
|
|
19
|
+
"ApprovalRequest",
|
|
20
|
+
"ChatCompletionRequest",
|
|
21
|
+
"ChatCompletionResponse",
|
|
22
|
+
"ConformanceRequest",
|
|
23
|
+
"ConformanceResult",
|
|
24
|
+
"Receipt",
|
|
25
|
+
"Session",
|
|
26
|
+
"VerificationResult",
|
|
27
|
+
"VersionInfo",
|
|
28
|
+
]
|
helm_sdk/client.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""HELM SDK — Python Client
|
|
2
|
+
|
|
3
|
+
Typed client for HELM kernel API. Minimal deps (httpx).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import asdict
|
|
9
|
+
from typing import Any, Optional
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from .types_gen import (
|
|
14
|
+
ApprovalRequest,
|
|
15
|
+
ChatCompletionRequest,
|
|
16
|
+
ChatCompletionResponse,
|
|
17
|
+
ConformanceRequest,
|
|
18
|
+
ConformanceResult,
|
|
19
|
+
Receipt,
|
|
20
|
+
Session,
|
|
21
|
+
VerificationResult,
|
|
22
|
+
VersionInfo,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class HelmApiError(Exception):
|
|
27
|
+
"""Raised when the HELM API returns a non-2xx response."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, status: int, message: str, reason_code: str, details: Any = None):
|
|
30
|
+
super().__init__(message)
|
|
31
|
+
self.status = status
|
|
32
|
+
self.reason_code = reason_code
|
|
33
|
+
self.details = details
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class HelmClient:
|
|
37
|
+
"""Typed client for HELM kernel API."""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
base_url: str = "http://localhost:8080",
|
|
42
|
+
api_key: Optional[str] = None,
|
|
43
|
+
timeout: float = 30.0,
|
|
44
|
+
):
|
|
45
|
+
self.base_url = base_url.rstrip("/")
|
|
46
|
+
headers: dict[str, str] = {"Content-Type": "application/json"}
|
|
47
|
+
if api_key:
|
|
48
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
49
|
+
self._client = httpx.Client(
|
|
50
|
+
base_url=self.base_url,
|
|
51
|
+
headers=headers,
|
|
52
|
+
timeout=timeout,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def close(self) -> None:
|
|
56
|
+
self._client.close()
|
|
57
|
+
|
|
58
|
+
def __enter__(self) -> "HelmClient":
|
|
59
|
+
return self
|
|
60
|
+
|
|
61
|
+
def __exit__(self, *args: Any) -> None:
|
|
62
|
+
self.close()
|
|
63
|
+
|
|
64
|
+
def _check(self, resp: httpx.Response) -> None:
|
|
65
|
+
if resp.status_code >= 400:
|
|
66
|
+
try:
|
|
67
|
+
body = resp.json()
|
|
68
|
+
err = body.get("error", {})
|
|
69
|
+
raise HelmApiError(
|
|
70
|
+
status=resp.status_code,
|
|
71
|
+
message=err.get("message", resp.text),
|
|
72
|
+
reason_code=err.get("reason_code", "ERROR_INTERNAL"),
|
|
73
|
+
details=err.get("details"),
|
|
74
|
+
)
|
|
75
|
+
except (ValueError, KeyError):
|
|
76
|
+
raise HelmApiError(
|
|
77
|
+
status=resp.status_code,
|
|
78
|
+
message=resp.text,
|
|
79
|
+
reason_code="ERROR_INTERNAL",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# ── OpenAI Proxy ────────────────────────────────
|
|
83
|
+
def chat_completions(self, req: ChatCompletionRequest) -> ChatCompletionResponse:
|
|
84
|
+
resp = self._client.post("/v1/chat/completions", json=asdict(req))
|
|
85
|
+
self._check(resp)
|
|
86
|
+
data = resp.json()
|
|
87
|
+
return ChatCompletionResponse(**{k: data.get(k) for k in ChatCompletionResponse.__dataclass_fields__})
|
|
88
|
+
|
|
89
|
+
# ── Approval Ceremony ───────────────────────────
|
|
90
|
+
def approve_intent(self, req: ApprovalRequest) -> Receipt:
|
|
91
|
+
resp = self._client.post("/api/v1/kernel/approve", json=asdict(req))
|
|
92
|
+
self._check(resp)
|
|
93
|
+
return Receipt(**resp.json())
|
|
94
|
+
|
|
95
|
+
# ── ProofGraph ──────────────────────────────────
|
|
96
|
+
def list_sessions(self, limit: int = 50, offset: int = 0) -> list[Session]:
|
|
97
|
+
resp = self._client.get(f"/api/v1/proofgraph/sessions?limit={limit}&offset={offset}")
|
|
98
|
+
self._check(resp)
|
|
99
|
+
return [Session(**s) for s in resp.json()]
|
|
100
|
+
|
|
101
|
+
def get_receipts(self, session_id: str) -> list[Receipt]:
|
|
102
|
+
resp = self._client.get(f"/api/v1/proofgraph/sessions/{session_id}/receipts")
|
|
103
|
+
self._check(resp)
|
|
104
|
+
return [Receipt(**r) for r in resp.json()]
|
|
105
|
+
|
|
106
|
+
def get_receipt(self, receipt_hash: str) -> Receipt:
|
|
107
|
+
resp = self._client.get(f"/api/v1/proofgraph/receipts/{receipt_hash}")
|
|
108
|
+
self._check(resp)
|
|
109
|
+
return Receipt(**resp.json())
|
|
110
|
+
|
|
111
|
+
# ── Evidence ────────────────────────────────────
|
|
112
|
+
def export_evidence(self, session_id: Optional[str] = None) -> bytes:
|
|
113
|
+
resp = self._client.post(
|
|
114
|
+
"/api/v1/evidence/export",
|
|
115
|
+
json={"session_id": session_id, "format": "tar.gz"},
|
|
116
|
+
)
|
|
117
|
+
self._check(resp)
|
|
118
|
+
return resp.content
|
|
119
|
+
|
|
120
|
+
def verify_evidence(self, bundle: bytes) -> VerificationResult:
|
|
121
|
+
resp = self._client.post(
|
|
122
|
+
"/api/v1/evidence/verify",
|
|
123
|
+
files={"bundle": ("pack.tar.gz", bundle, "application/octet-stream")},
|
|
124
|
+
)
|
|
125
|
+
self._check(resp)
|
|
126
|
+
return VerificationResult(**resp.json())
|
|
127
|
+
|
|
128
|
+
def replay_verify(self, bundle: bytes) -> VerificationResult:
|
|
129
|
+
resp = self._client.post(
|
|
130
|
+
"/api/v1/replay/verify",
|
|
131
|
+
files={"bundle": ("pack.tar.gz", bundle, "application/octet-stream")},
|
|
132
|
+
)
|
|
133
|
+
self._check(resp)
|
|
134
|
+
return VerificationResult(**resp.json())
|
|
135
|
+
|
|
136
|
+
# ── Conformance ─────────────────────────────────
|
|
137
|
+
def conformance_run(self, req: ConformanceRequest) -> ConformanceResult:
|
|
138
|
+
resp = self._client.post("/api/v1/conformance/run", json=asdict(req))
|
|
139
|
+
self._check(resp)
|
|
140
|
+
return ConformanceResult(**resp.json())
|
|
141
|
+
|
|
142
|
+
def get_conformance_report(self, report_id: str) -> ConformanceResult:
|
|
143
|
+
resp = self._client.get(f"/api/v1/conformance/reports/{report_id}")
|
|
144
|
+
self._check(resp)
|
|
145
|
+
return ConformanceResult(**resp.json())
|
|
146
|
+
|
|
147
|
+
# ── System ──────────────────────────────────────
|
|
148
|
+
def health(self) -> dict[str, str]:
|
|
149
|
+
resp = self._client.get("/healthz")
|
|
150
|
+
self._check(resp)
|
|
151
|
+
return resp.json()
|
|
152
|
+
|
|
153
|
+
def version(self) -> VersionInfo:
|
|
154
|
+
resp = self._client.get("/version")
|
|
155
|
+
self._check(resp)
|
|
156
|
+
return VersionInfo(**resp.json())
|
helm_sdk/types_gen.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# AUTO-GENERATED from api/openapi/helm.openapi.yaml — DO NOT EDIT
|
|
2
|
+
# Regenerate: bash scripts/sdk/gen.sh
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# ── Reason Codes ──────────────────────────────────────
|
|
11
|
+
REASON_CODES = [
|
|
12
|
+
"ALLOW",
|
|
13
|
+
"DENY_TOOL_NOT_FOUND",
|
|
14
|
+
"DENY_SCHEMA_MISMATCH",
|
|
15
|
+
"DENY_OUTPUT_DRIFT",
|
|
16
|
+
"DENY_BUDGET_EXCEEDED",
|
|
17
|
+
"DENY_APPROVAL_REQUIRED",
|
|
18
|
+
"DENY_APPROVAL_TIMEOUT",
|
|
19
|
+
"DENY_SANDBOX_TRAP",
|
|
20
|
+
"DENY_GAS_EXHAUSTION",
|
|
21
|
+
"DENY_TIME_LIMIT",
|
|
22
|
+
"DENY_MEMORY_LIMIT",
|
|
23
|
+
"DENY_POLICY_VIOLATION",
|
|
24
|
+
"DENY_TRUST_KEY_REVOKED",
|
|
25
|
+
"DENY_IDEMPOTENCY_DUPLICATE",
|
|
26
|
+
"ERROR_INTERNAL",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class HelmErrorDetail:
|
|
32
|
+
message: str = ""
|
|
33
|
+
type: str = ""
|
|
34
|
+
code: str = ""
|
|
35
|
+
reason_code: str = ""
|
|
36
|
+
details: Optional[Dict[str, Any]] = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class ChatMessage:
|
|
41
|
+
role: str = ""
|
|
42
|
+
content: str = ""
|
|
43
|
+
tool_call_id: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class ToolFunction:
|
|
48
|
+
name: str = ""
|
|
49
|
+
description: str = ""
|
|
50
|
+
parameters: Optional[Dict[str, Any]] = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class Tool:
|
|
55
|
+
type: str = "function"
|
|
56
|
+
function: Optional[ToolFunction] = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class ChatCompletionRequest:
|
|
61
|
+
model: str = ""
|
|
62
|
+
messages: List[ChatMessage] = field(default_factory=list)
|
|
63
|
+
tools: Optional[List[Tool]] = None
|
|
64
|
+
temperature: Optional[float] = None
|
|
65
|
+
max_tokens: Optional[int] = None
|
|
66
|
+
stream: bool = False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class ToolCall:
|
|
71
|
+
id: str = ""
|
|
72
|
+
type: str = ""
|
|
73
|
+
function: Optional[Dict[str, str]] = None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class ChoiceMessage:
|
|
78
|
+
role: str = ""
|
|
79
|
+
content: Optional[str] = None
|
|
80
|
+
tool_calls: Optional[List[ToolCall]] = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class Choice:
|
|
85
|
+
index: int = 0
|
|
86
|
+
message: Optional[ChoiceMessage] = None
|
|
87
|
+
finish_reason: str = ""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class Usage:
|
|
92
|
+
prompt_tokens: int = 0
|
|
93
|
+
completion_tokens: int = 0
|
|
94
|
+
total_tokens: int = 0
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class ChatCompletionResponse:
|
|
99
|
+
id: str = ""
|
|
100
|
+
object: str = "chat.completion"
|
|
101
|
+
created: int = 0
|
|
102
|
+
model: str = ""
|
|
103
|
+
choices: List[Choice] = field(default_factory=list)
|
|
104
|
+
usage: Optional[Usage] = None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class ApprovalRequest:
|
|
109
|
+
intent_hash: str = ""
|
|
110
|
+
signature_b64: str = ""
|
|
111
|
+
public_key_b64: str = ""
|
|
112
|
+
challenge_response: Optional[str] = None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class Receipt:
|
|
117
|
+
receipt_id: str = ""
|
|
118
|
+
decision_id: str = ""
|
|
119
|
+
effect_id: str = ""
|
|
120
|
+
status: str = ""
|
|
121
|
+
reason_code: str = ""
|
|
122
|
+
output_hash: str = ""
|
|
123
|
+
blob_hash: str = ""
|
|
124
|
+
prev_hash: str = ""
|
|
125
|
+
lamport_clock: int = 0
|
|
126
|
+
signature: str = ""
|
|
127
|
+
timestamp: str = ""
|
|
128
|
+
principal: str = ""
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass
|
|
132
|
+
class Session:
|
|
133
|
+
session_id: str = ""
|
|
134
|
+
created_at: str = ""
|
|
135
|
+
receipt_count: int = 0
|
|
136
|
+
last_lamport_clock: int = 0
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class ExportRequest:
|
|
141
|
+
session_id: Optional[str] = None
|
|
142
|
+
format: str = "tar.gz"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclass
|
|
146
|
+
class VerificationResult:
|
|
147
|
+
verdict: str = ""
|
|
148
|
+
checks: Optional[Dict[str, str]] = None
|
|
149
|
+
errors: List[str] = field(default_factory=list)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass
|
|
153
|
+
class ConformanceRequest:
|
|
154
|
+
level: str = "L1"
|
|
155
|
+
profile: str = "full"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@dataclass
|
|
159
|
+
class ConformanceResult:
|
|
160
|
+
report_id: str = ""
|
|
161
|
+
level: str = ""
|
|
162
|
+
verdict: str = ""
|
|
163
|
+
gates: int = 0
|
|
164
|
+
failed: int = 0
|
|
165
|
+
details: Optional[Dict[str, str]] = None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@dataclass
|
|
169
|
+
class VersionInfo:
|
|
170
|
+
version: str = ""
|
|
171
|
+
commit: str = ""
|
|
172
|
+
build_time: str = ""
|
|
173
|
+
go_version: str = ""
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: helm-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for HELM — fail-closed tool calling for AI agents
|
|
5
|
+
Author-email: Mindburn Labs <oss@mindburn.org>
|
|
6
|
+
License: BSL-1.1
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: httpx>=0.25.0
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
12
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
13
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
14
|
+
Requires-Dist: types-requests; extra == "dev"
|
|
15
|
+
|
|
16
|
+
# HELM SDK — Python
|
|
17
|
+
|
|
18
|
+
Typed Python client for the HELM kernel API. One dependency: `httpx`.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install helm-sdk
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Example
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from helm_sdk import HelmClient, HelmApiError, ChatCompletionRequest, ChatMessage
|
|
30
|
+
|
|
31
|
+
helm = HelmClient(base_url="http://localhost:8080")
|
|
32
|
+
|
|
33
|
+
# OpenAI-compatible chat (tool calls governed by HELM)
|
|
34
|
+
try:
|
|
35
|
+
res = helm.chat_completions(ChatCompletionRequest(
|
|
36
|
+
model="gpt-4",
|
|
37
|
+
messages=[ChatMessage(role="user", content="List files in /tmp")],
|
|
38
|
+
))
|
|
39
|
+
print(res.choices[0].message.content)
|
|
40
|
+
except HelmApiError as e:
|
|
41
|
+
print(f"Denied: {e.reason_code}") # e.g. DENY_TOOL_NOT_FOUND
|
|
42
|
+
|
|
43
|
+
# Export + verify evidence pack
|
|
44
|
+
pack = helm.export_evidence()
|
|
45
|
+
result = helm.verify_evidence(pack)
|
|
46
|
+
print(result.verdict) # PASS
|
|
47
|
+
|
|
48
|
+
# Conformance
|
|
49
|
+
from helm_sdk import ConformanceRequest
|
|
50
|
+
conf = helm.conformance_run(ConformanceRequest(level="L2"))
|
|
51
|
+
print(conf.verdict, conf.gates, "gates")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## API
|
|
55
|
+
|
|
56
|
+
| Method | Endpoint |
|
|
57
|
+
|--------|----------|
|
|
58
|
+
| `chat_completions(req)` | `POST /v1/chat/completions` |
|
|
59
|
+
| `approve_intent(req)` | `POST /api/v1/kernel/approve` |
|
|
60
|
+
| `list_sessions()` | `GET /api/v1/proofgraph/sessions` |
|
|
61
|
+
| `get_receipts(session_id)` | `GET /api/v1/proofgraph/sessions/{id}/receipts` |
|
|
62
|
+
| `export_evidence(session_id?)` | `POST /api/v1/evidence/export` |
|
|
63
|
+
| `verify_evidence(bundle)` | `POST /api/v1/evidence/verify` |
|
|
64
|
+
| `replay_verify(bundle)` | `POST /api/v1/replay/verify` |
|
|
65
|
+
| `conformance_run(req)` | `POST /api/v1/conformance/run` |
|
|
66
|
+
| `health()` | `GET /healthz` |
|
|
67
|
+
| `version()` | `GET /version` |
|
|
68
|
+
|
|
69
|
+
## Error Handling
|
|
70
|
+
|
|
71
|
+
All errors raise `HelmApiError` with a typed `reason_code`:
|
|
72
|
+
```python
|
|
73
|
+
try: helm.chat_completions(req)
|
|
74
|
+
except HelmApiError as e: print(e.reason_code)
|
|
75
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
helm_sdk/__init__.py,sha256=_0BiJPT28L2kV5iDsBFLqAIktni2lo4jRd_28nt2xmw,551
|
|
2
|
+
helm_sdk/client.py,sha256=Yfwuu5wAq9Nfhh2dnD3qRz0pQvTRM-xjyodQaucZklA,5788
|
|
3
|
+
helm_sdk/types_gen.py,sha256=QKj4KH0L_tqmUFijIg6yxZxW1z9KsspwEJYzTVmI5DE,3528
|
|
4
|
+
helm_sdk-0.1.0.dist-info/METADATA,sha256=7RxPDw97oQtwhsC-vC2m1kVoc9H31ju968EWgvloSHY,2217
|
|
5
|
+
helm_sdk-0.1.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
6
|
+
helm_sdk-0.1.0.dist-info/top_level.txt,sha256=5V0cwQFcRzWuO0kC_cKfaUMUP1hhUGHB8H_q1YaQ7TA,9
|
|
7
|
+
helm_sdk-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
helm_sdk
|