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 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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ helm_sdk