helixor-reasoner 0.2.0__tar.gz
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.
- helixor_reasoner-0.2.0/LICENSE +17 -0
- helixor_reasoner-0.2.0/PKG-INFO +57 -0
- helixor_reasoner-0.2.0/README.md +35 -0
- helixor_reasoner-0.2.0/helixor_reasoner/__init__.py +7 -0
- helixor_reasoner-0.2.0/helixor_reasoner/_operations/__init__.py +16 -0
- helixor_reasoner-0.2.0/helixor_reasoner/_operations/public_operations.py +34 -0
- helixor_reasoner-0.2.0/helixor_reasoner/client.py +193 -0
- helixor_reasoner-0.2.0/helixor_reasoner/contract.py +15 -0
- helixor_reasoner-0.2.0/helixor_reasoner/errors.py +46 -0
- helixor_reasoner-0.2.0/helixor_reasoner/models.py +191 -0
- helixor_reasoner-0.2.0/helixor_reasoner/operation_contracts.py +79 -0
- helixor_reasoner-0.2.0/helixor_reasoner/py.typed +0 -0
- helixor_reasoner-0.2.0/pyproject.toml +36 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 Helixor AI, Inc.
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: helixor-reasoner
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Typed client for the Helixor reasoning API: decide, fetch proofs, report outcomes.
|
|
5
|
+
Project-URL: Homepage, https://helixor.dev
|
|
6
|
+
Author-email: Helixor <engineering@helixor.ai>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: decisions,helixor,reasoning,sdk
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: httpx>=0.27
|
|
20
|
+
Requires-Dist: pydantic>=2.7
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# helixor-reasoner
|
|
24
|
+
|
|
25
|
+
Typed client for the Helixor reasoning API: decide, fetch proofs, report outcomes.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install helixor-reasoner
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from helixor_reasoner import ApiClient
|
|
37
|
+
|
|
38
|
+
with ApiClient(base_url="https://api.example.com") as client:
|
|
39
|
+
result = client.get_health()
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Responses are validated against the API contract; non-2xx responses raise typed exceptions.
|
|
43
|
+
|
|
44
|
+
## Operations
|
|
45
|
+
|
|
46
|
+
| Method | HTTP | Summary |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `decide` | `POST /v1/decide` | Decide |
|
|
49
|
+
| `get_health` | `GET /v1/health` | Health |
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
`Apache-2.0` - see [LICENSE](LICENSE).
|
|
54
|
+
|
|
55
|
+
## Links
|
|
56
|
+
|
|
57
|
+
- Homepage: https://helixor.dev
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# helixor-reasoner
|
|
2
|
+
|
|
3
|
+
Typed client for the Helixor reasoning API: decide, fetch proofs, report outcomes.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install helixor-reasoner
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from helixor_reasoner import ApiClient
|
|
15
|
+
|
|
16
|
+
with ApiClient(base_url="https://api.example.com") as client:
|
|
17
|
+
result = client.get_health()
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Responses are validated against the API contract; non-2xx responses raise typed exceptions.
|
|
21
|
+
|
|
22
|
+
## Operations
|
|
23
|
+
|
|
24
|
+
| Method | HTTP | Summary |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `decide` | `POST /v1/decide` | Decide |
|
|
27
|
+
| `get_health` | `GET /v1/health` | Health |
|
|
28
|
+
|
|
29
|
+
## License
|
|
30
|
+
|
|
31
|
+
`Apache-2.0` - see [LICENSE](LICENSE).
|
|
32
|
+
|
|
33
|
+
## Links
|
|
34
|
+
|
|
35
|
+
- Homepage: https://helixor.dev
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from .client import ApiClient
|
|
2
|
+
from . import models
|
|
3
|
+
from .contract import CONTRACT_HASH_ALGORITHM, OPERATION_HASH_ALGORITHM, OPERATION_SHA256, SPEC_SHA256
|
|
4
|
+
from .operation_contracts import OPERATION_CONTRACTS, OperationContractsManifest
|
|
5
|
+
from .errors import ApiException, ApiHttpException, ApiResponseValidationException
|
|
6
|
+
|
|
7
|
+
__all__ = ["ApiClient", "models", "CONTRACT_HASH_ALGORITHM", "OPERATION_HASH_ALGORITHM", "OPERATION_SHA256", "SPEC_SHA256", "OPERATION_CONTRACTS", "OperationContractsManifest", "ApiException", "ApiHttpException", "ApiResponseValidationException"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from ..client import ApiClient
|
|
7
|
+
|
|
8
|
+
from .public_operations import PublicOperations, PublicResourceMethods
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PublicResource(PublicResourceMethods):
|
|
12
|
+
def __init__(self, client: ApiClient) -> None:
|
|
13
|
+
self._client = client
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ['PublicOperations', 'PublicResourceMethods', 'PublicResource']
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Iterator, Literal, Mapping, Protocol, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from .. import models as Models
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ..client import ApiClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _ClientHelpers(Protocol):
|
|
12
|
+
"""What the operation methods below call on self; ApiClient satisfies it."""
|
|
13
|
+
|
|
14
|
+
def request(self, method: str, path: str, *, body: Any = None, content_type: str = 'application/json', response_model: type[Any] | None = None, request_headers: Mapping[str, Any] | None = None) -> Any: ...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PublicOperations:
|
|
18
|
+
def decide(self: _ClientHelpers, body: Models.DecideRequest) -> Models.DecisionResponse:
|
|
19
|
+
path = '/v1/decide'
|
|
20
|
+
return self.request('POST', path, body=body, content_type='application/json', response_model=Models.DecisionResponse)
|
|
21
|
+
|
|
22
|
+
def get_health(self: _ClientHelpers) -> Models.HealthResponse:
|
|
23
|
+
path = '/v1/health'
|
|
24
|
+
return self.request('GET', path, response_model=Models.HealthResponse)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PublicResourceMethods:
|
|
28
|
+
_client: ApiClient
|
|
29
|
+
|
|
30
|
+
def decide(self, body: Models.DecideRequest) -> Models.DecisionResponse:
|
|
31
|
+
return self._client.decide(body=body)
|
|
32
|
+
|
|
33
|
+
def get_health(self) -> Models.HealthResponse:
|
|
34
|
+
return self._client.get_health()
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from typing import Any, Callable, Iterator, Mapping, TypeVar
|
|
6
|
+
from urllib.parse import quote, urlencode
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, TypeAdapter, ValidationError
|
|
11
|
+
|
|
12
|
+
from . import models as Models
|
|
13
|
+
from ._operations import PublicOperations, PublicResource
|
|
14
|
+
from .errors import ApiDeserializationException, ApiHttpException, ApiResponseValidationException, ApiSerializationException, ApiValidationException, RawResponse
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
T = TypeVar("T")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ApiClient(PublicOperations):
|
|
21
|
+
def __init__(self, base_url: str | None = None, headers: Mapping[str, str] | None = None, api_key: str | Callable[[], str] | None = None, timeout: float | None = None) -> None:
|
|
22
|
+
self.base_url = (base_url or None or '').rstrip('/')
|
|
23
|
+
if not self.base_url:
|
|
24
|
+
raise ValueError('ApiClient requires base_url: this SDK has no default API host')
|
|
25
|
+
self.headers = dict(headers or {})
|
|
26
|
+
self.api_key = api_key
|
|
27
|
+
# The configured timeout is the DEFAULT, not a ceiling. A caller driving an
|
|
28
|
+
# LLM-backed operation legitimately needs longer than a CRUD caller, and one
|
|
29
|
+
# baked value forces it to reach around the generated client.
|
|
30
|
+
self.timeout = 60.0 if timeout is None else timeout
|
|
31
|
+
self._client = httpx.Client(base_url=self.base_url, headers=self.headers, timeout=self.timeout, follow_redirects=False)
|
|
32
|
+
|
|
33
|
+
self.public = PublicResource(self)
|
|
34
|
+
|
|
35
|
+
def close(self) -> None:
|
|
36
|
+
self._client.close()
|
|
37
|
+
|
|
38
|
+
def __enter__(self) -> ApiClient:
|
|
39
|
+
return self
|
|
40
|
+
|
|
41
|
+
def __exit__(self, exc_type: Any, exc: Any, traceback: Any) -> None:
|
|
42
|
+
self.close()
|
|
43
|
+
|
|
44
|
+
def request(self, method: str, path: str, *, body: Any = None, content_type: str = 'application/json', response_model: type[T] | None = None, request_headers: Mapping[str, Any] | None = None) -> T | Any | None:
|
|
45
|
+
try:
|
|
46
|
+
request_kwargs = self._encode_request_body(body, content_type)
|
|
47
|
+
except Exception as exc:
|
|
48
|
+
raise ApiSerializationException("Unable to serialize API request body") from exc
|
|
49
|
+
path = self._append_auth_query(path)
|
|
50
|
+
headers = self._build_headers(content_type)
|
|
51
|
+
if request_headers is not None:
|
|
52
|
+
headers.update({name: str(value) for name, value in request_headers.items() if value is not None})
|
|
53
|
+
max_attempts = 3
|
|
54
|
+
retry_statuses = [408, 409, 429, 500, 502, 503, 504]
|
|
55
|
+
retry_methods = ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE']
|
|
56
|
+
backoff = 0.25
|
|
57
|
+
max_backoff = 2.0
|
|
58
|
+
response = None
|
|
59
|
+
for attempt in range(1, max_attempts + 1):
|
|
60
|
+
try:
|
|
61
|
+
with self._client.stream(method, path, headers=headers, **request_kwargs) as streamed_response:
|
|
62
|
+
declared_length = streamed_response.headers.get('Content-Length')
|
|
63
|
+
if declared_length is not None and int(declared_length) > 10485760:
|
|
64
|
+
raise ApiDeserializationException('API response exceeds the configured size limit')
|
|
65
|
+
content = bytearray()
|
|
66
|
+
for chunk in streamed_response.iter_bytes():
|
|
67
|
+
if len(content) + len(chunk) > 10485760:
|
|
68
|
+
raise ApiDeserializationException('API response exceeds the configured size limit')
|
|
69
|
+
content.extend(chunk)
|
|
70
|
+
# iter_bytes() already decoded the body; drop the encoding headers so
|
|
71
|
+
# the rebuilt response does not decode (e.g. gunzip) it a second time.
|
|
72
|
+
decoded_headers = [
|
|
73
|
+
(name, value) for name, value in streamed_response.headers.multi_items()
|
|
74
|
+
if name.lower() not in ('content-encoding', 'content-length')
|
|
75
|
+
]
|
|
76
|
+
response = httpx.Response(
|
|
77
|
+
status_code=streamed_response.status_code,
|
|
78
|
+
headers=decoded_headers,
|
|
79
|
+
content=bytes(content),
|
|
80
|
+
)
|
|
81
|
+
except httpx.RequestError:
|
|
82
|
+
if attempt >= max_attempts or method.upper() not in retry_methods or False:
|
|
83
|
+
raise
|
|
84
|
+
time.sleep(min(backoff, max_backoff))
|
|
85
|
+
backoff *= 2
|
|
86
|
+
continue
|
|
87
|
+
if response.status_code not in retry_statuses or method.upper() not in retry_methods or attempt >= max_attempts:
|
|
88
|
+
break
|
|
89
|
+
time.sleep(min(backoff, max_backoff))
|
|
90
|
+
backoff *= 2
|
|
91
|
+
assert response is not None
|
|
92
|
+
if response.status_code >= 400:
|
|
93
|
+
parsed_error: Any
|
|
94
|
+
try:
|
|
95
|
+
parsed_error = response.json()
|
|
96
|
+
except ValueError:
|
|
97
|
+
parsed_error = None
|
|
98
|
+
raise ApiHttpException(
|
|
99
|
+
f'API request failed with status {response.status_code}',
|
|
100
|
+
raw_response=RawResponse(response.status_code, response.headers, response.text),
|
|
101
|
+
parsed_error=parsed_error,
|
|
102
|
+
)
|
|
103
|
+
if not response.content:
|
|
104
|
+
return None
|
|
105
|
+
if response_model is bytes:
|
|
106
|
+
return response.content
|
|
107
|
+
try:
|
|
108
|
+
payload = response.json()
|
|
109
|
+
except ValueError as exc:
|
|
110
|
+
raise ApiDeserializationException(
|
|
111
|
+
"Unable to parse API response JSON",
|
|
112
|
+
raw_response=RawResponse(response.status_code, response.headers, response.text),
|
|
113
|
+
) from exc
|
|
114
|
+
if response_model is None:
|
|
115
|
+
return payload
|
|
116
|
+
try:
|
|
117
|
+
return TypeAdapter(response_model).validate_python(payload)
|
|
118
|
+
except ValidationError as exc:
|
|
119
|
+
raise ApiResponseValidationException(
|
|
120
|
+
"API response failed schema validation",
|
|
121
|
+
raw_response=RawResponse(response.status_code, response.headers, response.text),
|
|
122
|
+
parsed_error=payload,
|
|
123
|
+
) from exc
|
|
124
|
+
|
|
125
|
+
def _encode_body(self, body: Any) -> Any:
|
|
126
|
+
if isinstance(body, BaseModel):
|
|
127
|
+
return body.model_dump(mode="json", by_alias=True, exclude_none=True)
|
|
128
|
+
return body
|
|
129
|
+
|
|
130
|
+
def _encode_request_body(self, body: Any, content_type: str) -> dict[str, Any]:
|
|
131
|
+
if body is None:
|
|
132
|
+
return {}
|
|
133
|
+
if content_type == 'multipart/form-data':
|
|
134
|
+
multipart_body = (
|
|
135
|
+
body.model_dump(mode='python', by_alias=True, exclude_none=True)
|
|
136
|
+
if isinstance(body, BaseModel)
|
|
137
|
+
else body
|
|
138
|
+
)
|
|
139
|
+
if not isinstance(multipart_body, Mapping):
|
|
140
|
+
raise ApiSerializationException('Multipart request body must be a mapping')
|
|
141
|
+
parts: dict[str, Any] = {}
|
|
142
|
+
for key, value in multipart_body.items():
|
|
143
|
+
if value is None:
|
|
144
|
+
continue
|
|
145
|
+
if isinstance(value, (bytes, bytearray, memoryview)):
|
|
146
|
+
parts[key] = (key, bytes(value), 'application/octet-stream')
|
|
147
|
+
continue
|
|
148
|
+
if isinstance(value, bool):
|
|
149
|
+
form_value = 'true' if value else 'false'
|
|
150
|
+
elif isinstance(value, (dict, list)):
|
|
151
|
+
form_value = json.dumps(value, separators=(',', ':'))
|
|
152
|
+
else:
|
|
153
|
+
form_value = str(value)
|
|
154
|
+
parts[key] = (None, form_value)
|
|
155
|
+
return {'files': parts}
|
|
156
|
+
encoded = self._encode_body(body)
|
|
157
|
+
if content_type == 'application/json':
|
|
158
|
+
return {'json': encoded}
|
|
159
|
+
if content_type == 'application/x-www-form-urlencoded':
|
|
160
|
+
return {'data': encoded}
|
|
161
|
+
if content_type in {'application/octet-stream', 'application/pdf'} or content_type.startswith('image/'):
|
|
162
|
+
return {'content': encoded}
|
|
163
|
+
return {'content': encoded}
|
|
164
|
+
|
|
165
|
+
def _resolve_auth_value(self, value: str | Callable[[], str] | None) -> str | None:
|
|
166
|
+
if value is None:
|
|
167
|
+
return None
|
|
168
|
+
return value() if callable(value) else value
|
|
169
|
+
|
|
170
|
+
def _build_headers(self, content_type: str = 'application/json') -> dict[str, str]:
|
|
171
|
+
headers = dict(self.headers)
|
|
172
|
+
if content_type != 'multipart/form-data':
|
|
173
|
+
headers.setdefault('Content-Type', content_type)
|
|
174
|
+
api_key = self._resolve_auth_value(self.api_key)
|
|
175
|
+
if api_key:
|
|
176
|
+
headers['X-API-Key'] = api_key
|
|
177
|
+
return headers
|
|
178
|
+
|
|
179
|
+
def _append_auth_query(self, path: str) -> str:
|
|
180
|
+
return path
|
|
181
|
+
|
|
182
|
+
def _build_path(self, path: str, params: Mapping[str, Any]) -> str:
|
|
183
|
+
for key, value in params.items():
|
|
184
|
+
if value is None or value == "":
|
|
185
|
+
raise ApiValidationException(f"Missing required path parameter: {key}")
|
|
186
|
+
path = path.replace("{" + key + "}", quote(str(value), safe=""))
|
|
187
|
+
return path
|
|
188
|
+
|
|
189
|
+
def _append_query(self, path: str, params: Mapping[str, Any]) -> str:
|
|
190
|
+
query_params = {key: value for key, value in params.items() if value is not None}
|
|
191
|
+
if not query_params:
|
|
192
|
+
return path
|
|
193
|
+
return path + "?" + urlencode(query_params, doseq=True)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Generated contract identity for drift detection.
|
|
2
|
+
|
|
3
|
+
SPEC_SHA256 is the canonical hash of the whole OpenAPI document.
|
|
4
|
+
OPERATION_SHA256 is the wire identity of each HTTP operation; login
|
|
5
|
+
compatibility uses the login operation hash, not the document hash.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
SPEC_SHA256 = "a81ad4404e5cdbbc52afdc4a70c033345aa3fe39074cc42c9f30b4e082f30fa8"
|
|
9
|
+
CONTRACT_VERSION = "0.1.0"
|
|
10
|
+
CONTRACT_HASH_ALGORITHM = "sha256-canonical-json-v1"
|
|
11
|
+
OPERATION_HASH_ALGORITHM = "sha256-canonical-json-operation-v1"
|
|
12
|
+
OPERATION_SHA256 = {
|
|
13
|
+
"GET /v1/health": "5ee627b0358ea87788b51bfe6e9dceca9e8c790b52d52d3e53e626d72cd80f46",
|
|
14
|
+
"POST /v1/decide": "64422b005c949027ebc3599e842c9f9ae1b4a4f2e5dbfd727ec4a49f629d508a"
|
|
15
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Mapping
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(slots=True)
|
|
8
|
+
class RawResponse:
|
|
9
|
+
status_code: int | None
|
|
10
|
+
headers: Mapping[str, str]
|
|
11
|
+
body: str | bytes | None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ApiErrorBody(dict):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ApiException(Exception):
|
|
19
|
+
def __init__(self, message: str, *, raw_response: RawResponse | None = None, parsed_error: Any = None) -> None:
|
|
20
|
+
super().__init__(message)
|
|
21
|
+
self.raw_response = raw_response
|
|
22
|
+
self.parsed_error = parsed_error
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ApiHttpException(ApiException):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ApiValidationException(ApiException):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ApiResponseValidationException(ApiException):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ApiNetworkException(ApiException):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ApiSerializationException(ApiException):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ApiDeserializationException(ApiException):
|
|
46
|
+
pass
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import warnings
|
|
5
|
+
from datetime import date, datetime
|
|
6
|
+
from typing import Annotated, Any, Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_LOGGER = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
_SEEN_UNKNOWN_FIELDS: set[tuple[str, tuple[str, ...]]] = set()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _UnknownFieldWarningModel(BaseModel):
|
|
17
|
+
"""Accepts unknown fields but reports them once per model and field set.
|
|
18
|
+
|
|
19
|
+
The unknown_fields policy for this SDK is "warn". Parsing stays permissive
|
|
20
|
+
on purpose: a producer adding a field must not break a consumer mid-rolling
|
|
21
|
+
deploy. But silence is how a renamed field becomes a None that surfaces as a
|
|
22
|
+
confusing error several layers away, so each new shape is logged once --
|
|
23
|
+
deduplicated because these models are parsed per response and an unbounded
|
|
24
|
+
log would be worse than no log at all.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@model_validator(mode="after")
|
|
28
|
+
def _report_unknown_fields(self):
|
|
29
|
+
extra = self.model_extra
|
|
30
|
+
if not extra:
|
|
31
|
+
return self
|
|
32
|
+
names = tuple(sorted(extra))
|
|
33
|
+
key = (type(self).__name__, names)
|
|
34
|
+
if key not in _SEEN_UNKNOWN_FIELDS:
|
|
35
|
+
_SEEN_UNKNOWN_FIELDS.add(key)
|
|
36
|
+
_LOGGER.warning(
|
|
37
|
+
"%s received field(s) not declared by the contract: %s. "
|
|
38
|
+
"The producer may be ahead of this generated SDK; regenerate it "
|
|
39
|
+
"if these fields are expected.",
|
|
40
|
+
type(self).__name__,
|
|
41
|
+
", ".join(names),
|
|
42
|
+
)
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ComposedVerdict(_UnknownFieldWarningModel):
|
|
47
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
48
|
+
band: str | None = None
|
|
49
|
+
rule_ids: list[str] | None = None
|
|
50
|
+
verdict: str
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DecideFailureDetail(_UnknownFieldWarningModel):
|
|
54
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
55
|
+
failure_code: str
|
|
56
|
+
message: str
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DecideFailureResponse(_UnknownFieldWarningModel):
|
|
60
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
61
|
+
detail: DecideFailureDetail
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
with warnings.catch_warnings():
|
|
65
|
+
# Contract field(s) intentionally shadow pydantic BaseModel attributes: schema.
|
|
66
|
+
warnings.filterwarnings('ignore', message='^Field name "schema" in "DecideRequest" shadows an attribute in parent "_UnknownFieldWarningModel"$', category=UserWarning)
|
|
67
|
+
class DecideRequest(_UnknownFieldWarningModel):
|
|
68
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
69
|
+
answer_under_test: Any | None = None
|
|
70
|
+
evidence: list[EvidenceItem] | None = None
|
|
71
|
+
goal: str
|
|
72
|
+
questions: list[DecisionQuestion]
|
|
73
|
+
require: DecideRequire | None = None
|
|
74
|
+
schema: Literal['helixor.decide_request.v1'] = Field(default='helixor.decide_request.v1')
|
|
75
|
+
scope: DecideScope | None = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class DecideRequire(_UnknownFieldWarningModel):
|
|
79
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
80
|
+
max_latency_ms: int | None = None
|
|
81
|
+
min_confidence: float | None = None
|
|
82
|
+
proof: bool | None = None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class DecideScope(_UnknownFieldWarningModel):
|
|
86
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
87
|
+
domain: str | None = None
|
|
88
|
+
org: str | None = None
|
|
89
|
+
session: str | None = None
|
|
90
|
+
tenant: str | None = None
|
|
91
|
+
user: str | None = None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class DecisionCost(_UnknownFieldWarningModel):
|
|
95
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
96
|
+
units: int
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class DecisionOption(_UnknownFieldWarningModel):
|
|
100
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
101
|
+
criteria: str | None = None
|
|
102
|
+
label: str | None = None
|
|
103
|
+
option_id: str
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class DecisionProof(_UnknownFieldWarningModel):
|
|
107
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
108
|
+
artifact: str | None = None
|
|
109
|
+
checks: list[str] | None = None
|
|
110
|
+
citations: list[str] | None = None
|
|
111
|
+
digest: str | None = None
|
|
112
|
+
kind: Literal['replay', 'solver_certificate', 'citation_set', 'tcn_consistent', 're_derivation_agreement', 'distribution_threshold']
|
|
113
|
+
threshold: float | None = None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
with warnings.catch_warnings():
|
|
117
|
+
# Contract field(s) intentionally shadow pydantic BaseModel attributes: schema.
|
|
118
|
+
warnings.filterwarnings('ignore', message='^Field name "schema" in "DecisionQuestion" shadows an attribute in parent "_UnknownFieldWarningModel"$', category=UserWarning)
|
|
119
|
+
class DecisionQuestion(_UnknownFieldWarningModel):
|
|
120
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
121
|
+
context_refs: list[str] | None = None
|
|
122
|
+
family_id: str
|
|
123
|
+
kind: Literal['choice', 'score', 'boolean']
|
|
124
|
+
metadata: dict[str, Any] | None = None
|
|
125
|
+
options: list[DecisionOption]
|
|
126
|
+
prompt: str
|
|
127
|
+
question_id: str
|
|
128
|
+
schema: Literal['helixor.decision_question.v1'] = Field(default='helixor.decision_question.v1')
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
with warnings.catch_warnings():
|
|
132
|
+
# Contract field(s) intentionally shadow pydantic BaseModel attributes: schema.
|
|
133
|
+
warnings.filterwarnings('ignore', message='^Field name "schema" in "DecisionResponse" shadows an attribute in parent "_UnknownFieldWarningModel"$', category=UserWarning)
|
|
134
|
+
class DecisionResponse(_UnknownFieldWarningModel):
|
|
135
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
136
|
+
composed: ComposedVerdict | None = None
|
|
137
|
+
cost: DecisionCost | None = None
|
|
138
|
+
helix: HelixDecisionNote | None = None
|
|
139
|
+
latency_ms: int | None = None
|
|
140
|
+
proof_id: str | None = None
|
|
141
|
+
schema: Literal['helixor.decision.v1'] = Field(default='helixor.decision.v1')
|
|
142
|
+
verdicts: dict[str, QuestionVerdict]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class EvidenceItem(_UnknownFieldWarningModel):
|
|
146
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
147
|
+
fields: dict[str, Any] | None = None
|
|
148
|
+
id: str
|
|
149
|
+
source: str | None = None
|
|
150
|
+
text: str | None = None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class HTTPValidationError(_UnknownFieldWarningModel):
|
|
154
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
155
|
+
detail: list[ValidationError] | None = None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class HealthResponse(_UnknownFieldWarningModel):
|
|
159
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
160
|
+
capabilities_loaded: int
|
|
161
|
+
capabilities_total: int
|
|
162
|
+
component: str
|
|
163
|
+
status: str
|
|
164
|
+
version: str
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class HelixDecisionNote(_UnknownFieldWarningModel):
|
|
168
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
169
|
+
degraded: list[dict[str, Any]] | None = None
|
|
170
|
+
fingerprint: str | None = None
|
|
171
|
+
version: str | None = None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class QuestionVerdict(_UnknownFieldWarningModel):
|
|
175
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
176
|
+
masked: list[str] | None = None
|
|
177
|
+
p_correct: float | None = None
|
|
178
|
+
proof: DecisionProof | None = None
|
|
179
|
+
reason: Literal['not_certified', 'insufficient_evidence', 'over_budget'] | Literal['malformed', 'disallowed'] | None = None
|
|
180
|
+
value: Any | None = None
|
|
181
|
+
verdict: Literal['answer', 'abstain', 'refuse']
|
|
182
|
+
would_unmask: str | None = None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class ValidationError(_UnknownFieldWarningModel):
|
|
186
|
+
model_config = ConfigDict(populate_by_name=True, extra='allow')
|
|
187
|
+
ctx: dict[str, Any] | None = None
|
|
188
|
+
input: Any | None = None
|
|
189
|
+
loc: list[str | int]
|
|
190
|
+
msg: str
|
|
191
|
+
type: str
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ContractRef(BaseModel):
|
|
11
|
+
content_types: list[str] = Field(default_factory=list)
|
|
12
|
+
schema_ref: str | None = None
|
|
13
|
+
schema_value: dict[str, Any] = Field(default_factory=dict, alias='schema')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RequestContract(ContractRef):
|
|
17
|
+
required: bool = False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ResponseContract(ContractRef):
|
|
21
|
+
status_code: str | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ErrorContract(ResponseContract):
|
|
25
|
+
envelope: dict[str, Any] = Field(default_factory=dict)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OperationParameterContract(BaseModel):
|
|
29
|
+
name: str
|
|
30
|
+
location: str = Field(alias='in')
|
|
31
|
+
required: bool = False
|
|
32
|
+
schema_ref: str | None = None
|
|
33
|
+
schema_value: dict[str, Any] = Field(default_factory=dict, alias='schema')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class WorkflowContract(BaseModel):
|
|
37
|
+
query_key_strategy: str
|
|
38
|
+
audit_tags: list[str] = Field(default_factory=list)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class OperationContract(BaseModel):
|
|
42
|
+
operation_id: str = Field(alias='operationId')
|
|
43
|
+
kind: Literal['query', 'mutation']
|
|
44
|
+
method: str
|
|
45
|
+
path: str
|
|
46
|
+
sha256: str = ''
|
|
47
|
+
tags: list[str] = Field(default_factory=list)
|
|
48
|
+
summary: str = ''
|
|
49
|
+
idempotent: bool
|
|
50
|
+
retryable: bool
|
|
51
|
+
cache_key: list[str] = Field(alias='cacheKey')
|
|
52
|
+
parameters: list[OperationParameterContract] = Field(default_factory=list)
|
|
53
|
+
request_contract: RequestContract = Field(alias='requestContract')
|
|
54
|
+
response_contract: ResponseContract = Field(alias='responseContract')
|
|
55
|
+
error_contract: ErrorContract = Field(alias='errorContract')
|
|
56
|
+
auth: dict[str, Any]
|
|
57
|
+
pagination: dict[str, Any] | None = None
|
|
58
|
+
workflow: WorkflowContract
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class OperationContractsManifest(BaseModel):
|
|
62
|
+
schema_version: str
|
|
63
|
+
package_name: str
|
|
64
|
+
base_url: str | None = None
|
|
65
|
+
auth: dict[str, Any]
|
|
66
|
+
runtime: dict[str, Any]
|
|
67
|
+
resources: dict[str, Any]
|
|
68
|
+
validation: dict[str, Any]
|
|
69
|
+
error_handling: dict[str, Any]
|
|
70
|
+
operations: list[OperationContract]
|
|
71
|
+
diagnostics: list[str] = Field(default_factory=list)
|
|
72
|
+
|
|
73
|
+
def by_operation_id(self) -> dict[str, OperationContract]:
|
|
74
|
+
return {operation.operation_id: operation for operation in self.operations}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
RAW_OPERATION_CONTRACTS = json.loads("{\n \"auth\": {\n \"auto_detect\": true,\n \"default_provider\": null,\n \"enabled\": true,\n \"fail_on_missing_required\": true,\n \"headers\": {},\n \"operations\": {},\n \"providers\": {},\n \"schemes\": {\n \"apiKeyAuth\": {\n \"env_var\": \"HELIXOR_API_KEY\",\n \"location\": \"header\",\n \"name\": \"X-API-Key\",\n \"prefix\": \"Bearer\",\n \"required\": false,\n \"scopes\": [],\n \"security_scheme\": \"apiKeyAuth\",\n \"type\": \"api_key\"\n }\n }\n },\n \"base_url\": null,\n \"diagnostics\": [],\n \"error_handling\": {\n \"envelope\": {\n \"fields\": {\n \"code\": \"code\",\n \"details\": \"details\",\n \"message\": \"message\",\n \"request_id\": \"requestId\",\n \"timestamp\": \"timestamp\",\n \"trace_id\": \"traceId\"\n },\n \"import_from\": null,\n \"schema_ref\": null,\n \"source\": \"openapi\",\n \"type_name\": \"ApiErrorBody\"\n },\n \"exceptions\": {\n \"base_class\": \"ApiException\",\n \"deserialization_exception\": \"ApiDeserializationException\",\n \"http_exception\": \"ApiHttpException\",\n \"import_from\": null,\n \"network_exception\": \"ApiNetworkException\",\n \"response_validation_exception\": \"ApiResponseValidationException\",\n \"serialization_exception\": \"ApiSerializationException\",\n \"source\": \"generate\",\n \"validation_exception\": \"ApiValidationException\"\n },\n \"mode\": \"throw\",\n \"raw_payload\": {\n \"include_body\": true,\n \"include_headers\": true,\n \"max_body_bytes\": 65536\n },\n \"unknown_error_body\": \"wrap_raw\"\n },\n \"operations\": [\n {\n \"auth\": {\n \"headers\": [],\n \"required\": true,\n \"schemes\": [\n {\n \"configured\": true,\n \"env_var\": null,\n \"location\": \"header\",\n \"name\": \"ApiKeyAuth\",\n \"prefix\": null,\n \"required\": true,\n \"scopes\": [],\n \"security_scheme\": \"ApiKeyAuth\",\n \"type\": \"api_key\"\n }\n ],\n \"security\": [\n {\n \"ApiKeyAuth\": []\n }\n ]\n },\n \"cacheKey\": [\n \"decide\"\n ],\n \"errorContract\": {\n \"content_types\": [\n \"application/json\"\n ],\n \"envelope\": {\n \"fields\": {\n \"code\": \"code\",\n \"details\": \"details\",\n \"message\": \"message\",\n \"request_id\": \"requestId\",\n \"timestamp\": \"timestamp\",\n \"trace_id\": \"traceId\"\n },\n \"import_from\": null,\n \"schema_ref\": null,\n \"source\": \"openapi\",\n \"type_name\": \"ApiErrorBody\"\n },\n \"schema\": {},\n \"schema_ref\": \"#/components/schemas/DecideFailureResponse\",\n \"status_code\": \"404\"\n },\n \"idempotent\": false,\n \"kind\": \"mutation\",\n \"method\": \"POST\",\n \"operationId\": \"decide\",\n \"parameters\": [],\n \"path\": \"/v1/decide\",\n \"requestContract\": {\n \"content_types\": [\n \"application/json\"\n ],\n \"contents\": [\n {\n \"content_type\": \"application/json\",\n \"schema\": {},\n \"schema_ref\": \"#/components/schemas/DecideRequest\"\n }\n ],\n \"default_content_type\": \"application/json\",\n \"required\": true,\n \"schema\": {},\n \"schema_ref\": \"#/components/schemas/DecideRequest\"\n },\n \"resource\": {\n \"language_names\": {\n \"csharp\": \"DecideAsync\",\n \"java\": \"decide\",\n \"python\": \"decide\",\n \"rust\": \"decide\",\n \"typescript\": \"decide\"\n },\n \"method_name\": \"decide\",\n \"name\": \"public\"\n },\n \"responseContract\": {\n \"content_types\": [\n \"application/json\"\n ],\n \"schema\": {},\n \"schema_ref\": \"#/components/schemas/DecisionResponse\",\n \"status_code\": \"200\"\n },\n \"retryable\": false,\n \"runtime\": {\n \"idempotency\": {\n \"applies\": false,\n \"enabled\": false,\n \"header_name\": \"Idempotency-Key\",\n \"methods\": [\n \"POST\",\n \"PATCH\"\n ]\n },\n \"retry\": {\n \"enabled\": true,\n \"initial_backoff_seconds\": 0.25,\n \"max_attempts\": 3,\n \"max_backoff_seconds\": 2.0,\n \"retry_methods\": [\n \"GET\",\n \"HEAD\",\n \"OPTIONS\",\n \"PUT\",\n \"DELETE\"\n ],\n \"retry_on_network_error\": true,\n \"retry_statuses\": [\n 408,\n 409,\n 429,\n 500,\n 502,\n 503,\n 504\n ],\n \"retryable\": false\n },\n \"timeout\": {\n \"total_seconds\": 60.0\n }\n },\n \"sha256\": \"64422b005c949027ebc3599e842c9f9ae1b4a4f2e5dbfd727ec4a49f629d508a\",\n \"summary\": \"Decide\",\n \"tags\": [\n \"public\"\n ],\n \"workflow\": {\n \"audit_tags\": [\n \"sdk\",\n \"mutation\",\n \"post\"\n ],\n \"query_key_strategy\": \"operation_and_params\"\n }\n },\n {\n \"auth\": {\n \"headers\": [],\n \"required\": true,\n \"schemes\": [\n {\n \"configured\": true,\n \"env_var\": null,\n \"location\": \"header\",\n \"name\": \"ApiKeyAuth\",\n \"prefix\": null,\n \"required\": true,\n \"scopes\": [],\n \"security_scheme\": \"ApiKeyAuth\",\n \"type\": \"api_key\"\n }\n ],\n \"security\": [\n {\n \"ApiKeyAuth\": []\n }\n ]\n },\n \"cacheKey\": [\n \"getHealth\"\n ],\n \"errorContract\": {\n \"content_types\": [],\n \"envelope\": {\n \"fields\": {\n \"code\": \"code\",\n \"details\": \"details\",\n \"message\": \"message\",\n \"request_id\": \"requestId\",\n \"timestamp\": \"timestamp\",\n \"trace_id\": \"traceId\"\n },\n \"import_from\": null,\n \"schema_ref\": null,\n \"source\": \"openapi\",\n \"type_name\": \"ApiErrorBody\"\n },\n \"schema\": {},\n \"schema_ref\": null,\n \"status_code\": null\n },\n \"idempotent\": true,\n \"kind\": \"query\",\n \"method\": \"GET\",\n \"operationId\": \"getHealth\",\n \"parameters\": [],\n \"path\": \"/v1/health\",\n \"requestContract\": {\n \"content_types\": [],\n \"contents\": [],\n \"default_content_type\": null,\n \"required\": false,\n \"schema\": {},\n \"schema_ref\": null\n },\n \"resource\": {\n \"language_names\": {\n \"csharp\": \"GetHealthAsync\",\n \"java\": \"getHealth\",\n \"python\": \"get_health\",\n \"rust\": \"get_health\",\n \"typescript\": \"getHealth\"\n },\n \"method_name\": \"getHealth\",\n \"name\": \"public\"\n },\n \"responseContract\": {\n \"content_types\": [\n \"application/json\"\n ],\n \"schema\": {},\n \"schema_ref\": \"#/components/schemas/HealthResponse\",\n \"status_code\": \"200\"\n },\n \"retryable\": true,\n \"runtime\": {\n \"idempotency\": {\n \"applies\": false,\n \"enabled\": false,\n \"header_name\": \"Idempotency-Key\",\n \"methods\": [\n \"POST\",\n \"PATCH\"\n ]\n },\n \"retry\": {\n \"enabled\": true,\n \"initial_backoff_seconds\": 0.25,\n \"max_attempts\": 3,\n \"max_backoff_seconds\": 2.0,\n \"retry_methods\": [\n \"GET\",\n \"HEAD\",\n \"OPTIONS\",\n \"PUT\",\n \"DELETE\"\n ],\n \"retry_on_network_error\": true,\n \"retry_statuses\": [\n 408,\n 409,\n 429,\n 500,\n 502,\n 503,\n 504\n ],\n \"retryable\": true\n },\n \"timeout\": {\n \"total_seconds\": 60.0\n }\n },\n \"sha256\": \"5ee627b0358ea87788b51bfe6e9dceca9e8c790b52d52d3e53e626d72cd80f46\",\n \"summary\": \"Health\",\n \"tags\": [\n \"public\"\n ],\n \"workflow\": {\n \"audit_tags\": [\n \"sdk\",\n \"query\",\n \"get\"\n ],\n \"query_key_strategy\": \"operation_and_params\"\n }\n }\n ],\n \"package_name\": \"helixor-reasoner\",\n \"pagination\": {\n \"auto_detect\": false,\n \"enabled\": true,\n \"operations\": {},\n \"patterns\": {}\n },\n \"resources\": {\n \"enabled\": true,\n \"infer_from_tags\": true,\n \"max_python_methods_per_class\": 40,\n \"oversized_python_group_severity\": \"warning\",\n \"resources\": {}\n },\n \"runtime\": {\n \"idempotency\": {\n \"enabled\": false,\n \"header_name\": \"Idempotency-Key\",\n \"methods\": [\n \"POST\",\n \"PATCH\"\n ]\n },\n \"max_response_bytes\": 10485760,\n \"retry\": {\n \"enabled\": true,\n \"initial_backoff_seconds\": 0.25,\n \"max_attempts\": 3,\n \"max_backoff_seconds\": 2.0,\n \"retry_methods\": [\n \"GET\",\n \"HEAD\",\n \"OPTIONS\",\n \"PUT\",\n \"DELETE\"\n ],\n \"retry_on_network_error\": true,\n \"retry_statuses\": [\n 408,\n 409,\n 429,\n 500,\n 502,\n 503,\n 504\n ]\n },\n \"timeout\": {\n \"total_seconds\": 60.0\n }\n },\n \"schema_version\": \"sdk.operation-contracts.v1\",\n \"validation\": {\n \"error_response\": \"strict\",\n \"raw_payload_capture\": true,\n \"request\": \"strict\",\n \"response\": \"strict\",\n \"unknown_fields\": \"warn\"\n }\n}")
|
|
78
|
+
|
|
79
|
+
OPERATION_CONTRACTS = OperationContractsManifest.model_validate(RAW_OPERATION_CONTRACTS)
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "helixor-reasoner"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Typed client for the Helixor reasoning API: decide, fetch proofs, report outcomes."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
authors = [{name = "Helixor", email = "engineering@helixor.ai"}]
|
|
14
|
+
keywords = ["helixor", "reasoning", "decisions", "sdk"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
dependencies = ["httpx>=0.27", "pydantic>=2.7"]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://helixor.dev"
|
|
29
|
+
|
|
30
|
+
[tool.hatch.build.targets.wheel]
|
|
31
|
+
packages = ["helixor_reasoner"]
|
|
32
|
+
core-metadata-version = "2.4"
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.targets.sdist]
|
|
35
|
+
include = ["/helixor_reasoner", "/README.md", "/LICENSE", "/pyproject.toml"]
|
|
36
|
+
core-metadata-version = "2.4"
|