runlayer-hooks-sdk 0.2.1__tar.gz → 0.2.2__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.
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/.gitignore +10 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/PKG-INFO +1 -1
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/pyproject.toml +1 -1
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/runlayer_sdk/hook_transport.py +48 -11
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/runlayer_sdk/tool_enforcement.py +20 -2
- runlayer_hooks_sdk-0.2.2/tests/test_hook_transport.py +121 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/tests/test_tool_enforcement.py +48 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/uv.lock +2 -2
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/LICENSE +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/Makefile +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/README.md +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/runlayer_sdk/__init__.py +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/runlayer_sdk/claude_agent_sdk.py +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/runlayer_sdk/client.py +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/runlayer_sdk/google_adk.py +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/runlayer_sdk/openai_agents_sdk.py +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/runlayer_sdk/preflight.py +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/runlayer_sdk/tool_adapter.py +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/runlayer_sdk/types.py +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/runlayer_sdk/vercel_ai_sdk.py +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/tests/http_mock.py +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/tests/test_adapters.py +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/tests/test_claude_agent_sdk.py +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/tests/test_client.py +0 -0
- {runlayer_hooks_sdk-0.2.1 → runlayer_hooks_sdk-0.2.2}/tests/test_license.py +0 -0
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
.worktrees
|
|
5
5
|
node_modules/
|
|
6
6
|
.pnpm-store
|
|
7
|
+
|
|
8
|
+
# Any stray `pnpm` invocation in a bun subproject writes these into the working
|
|
9
|
+
# tree. Ignore by default; re-allow the subprojects that are still on pnpm.
|
|
10
|
+
pnpm-lock.yaml
|
|
11
|
+
pnpm-workspace.yaml
|
|
12
|
+
!/llm-gateway/test/clients/pnpm-lock.yaml
|
|
7
13
|
/test-results/
|
|
8
14
|
/playwright-report/
|
|
9
15
|
/blob-report/
|
|
@@ -110,3 +116,7 @@ docs/superpowers/
|
|
|
110
116
|
# SDD (subagent-driven-development) task reports — internal artifacts
|
|
111
117
|
sdd/
|
|
112
118
|
tmp/
|
|
119
|
+
|
|
120
|
+
# Extractor-emitted load models are artifacts; the committed fixture stays hand-built.
|
|
121
|
+
/load-testing/load-model.json
|
|
122
|
+
/load-testing/model/load-model.json
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import gzip
|
|
3
4
|
from collections.abc import Callable
|
|
4
5
|
from contextlib import AbstractContextManager
|
|
5
6
|
from dataclasses import dataclass
|
|
6
|
-
from typing import Any, Protocol
|
|
7
|
+
from typing import Any, Protocol, TypedDict
|
|
7
8
|
|
|
8
9
|
import httpx
|
|
9
10
|
from runlayer_sdk.types import FetchLike, FetchResponse
|
|
@@ -12,6 +13,38 @@ API_KEY_HEADER_NAME = "x-runlayer-api-key"
|
|
|
12
13
|
ENFORCEMENT_HOOK_TIMEOUT_SECONDS = 30
|
|
13
14
|
EVENT_HOOK_TIMEOUT_SECONDS = 5
|
|
14
15
|
|
|
16
|
+
# Compress only bodies past this size: small hook bodies gain nothing from
|
|
17
|
+
# gzip, and the compress call would just add CPU to the latency-critical path.
|
|
18
|
+
GZIP_MIN_PAYLOAD_BYTES = 16 * 1024
|
|
19
|
+
# Speed over ratio: hook POSTs are latency-critical, and level 1 already
|
|
20
|
+
# collapses JSON transcript bodies dramatically.
|
|
21
|
+
GZIP_COMPRESS_LEVEL = 1
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class WireBody(TypedDict):
|
|
25
|
+
"""What actually goes on the wire: body + any transport headers it needs."""
|
|
26
|
+
|
|
27
|
+
content: str | bytes
|
|
28
|
+
headers: dict[str, str]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def encode_wire_body(payload: str | bytes, *, compress: bool = False) -> WireBody:
|
|
32
|
+
"""Gzip the payload when opted in and larger than ``GZIP_MIN_PAYLOAD_BYTES``.
|
|
33
|
+
|
|
34
|
+
``bytes`` input is treated as an already-encoded wire body and passed
|
|
35
|
+
through untouched, so a caller that pre-encoded (to measure the wire size)
|
|
36
|
+
can post the result without double-compressing.
|
|
37
|
+
"""
|
|
38
|
+
if not compress or isinstance(payload, bytes):
|
|
39
|
+
return {"content": payload, "headers": {}}
|
|
40
|
+
raw = payload.encode("utf-8")
|
|
41
|
+
if len(raw) <= GZIP_MIN_PAYLOAD_BYTES:
|
|
42
|
+
return {"content": payload, "headers": {}}
|
|
43
|
+
return {
|
|
44
|
+
"content": gzip.compress(raw, compresslevel=GZIP_COMPRESS_LEVEL),
|
|
45
|
+
"headers": {"Content-Encoding": "gzip"},
|
|
46
|
+
}
|
|
47
|
+
|
|
15
48
|
|
|
16
49
|
@dataclass(frozen=True)
|
|
17
50
|
class HookTarget:
|
|
@@ -22,6 +55,7 @@ class HookTarget:
|
|
|
22
55
|
HOOK_RELAY_TARGETS: dict[str, HookTarget] = {
|
|
23
56
|
"enforce": HookTarget("/api/v1/hooks/cursor", ENFORCEMENT_HOOK_TIMEOUT_SECONDS),
|
|
24
57
|
"event": HookTarget("/api/v1/hooks/events", EVENT_HOOK_TIMEOUT_SECONDS),
|
|
58
|
+
"mcp-usage": HookTarget("/api/v1/hooks/mcp-usage", EVENT_HOOK_TIMEOUT_SECONDS),
|
|
25
59
|
# Tool lifecycle endpoints normalize into LocalToolPre/PostRequest for
|
|
26
60
|
# scanning. Routing them to /events would silently downgrade enforcement.
|
|
27
61
|
"tool-pre": HookTarget("/api/v1/hooks/tool/pre", ENFORCEMENT_HOOK_TIMEOUT_SECONDS),
|
|
@@ -44,9 +78,9 @@ class HookHTTPClient(Protocol):
|
|
|
44
78
|
self,
|
|
45
79
|
url: str,
|
|
46
80
|
*,
|
|
47
|
-
content: str,
|
|
81
|
+
content: str | bytes,
|
|
48
82
|
headers: dict[str, str],
|
|
49
|
-
timeout: int | float,
|
|
83
|
+
timeout: int | float | httpx.Timeout,
|
|
50
84
|
) -> HookHTTPResponse: ...
|
|
51
85
|
|
|
52
86
|
|
|
@@ -67,9 +101,9 @@ class FetchLikeHookHTTPClient:
|
|
|
67
101
|
self,
|
|
68
102
|
url: str,
|
|
69
103
|
*,
|
|
70
|
-
content: str,
|
|
104
|
+
content: str | bytes,
|
|
71
105
|
headers: dict[str, str],
|
|
72
|
-
timeout: int | float,
|
|
106
|
+
timeout: int | float | httpx.Timeout,
|
|
73
107
|
) -> FetchResponse:
|
|
74
108
|
return self.fetch_impl(
|
|
75
109
|
url,
|
|
@@ -86,11 +120,14 @@ def post_hook_endpoint(
|
|
|
86
120
|
http_client: HookHTTPClient,
|
|
87
121
|
base_url: str,
|
|
88
122
|
endpoint: str,
|
|
89
|
-
payload: str,
|
|
123
|
+
payload: str | bytes,
|
|
90
124
|
*,
|
|
91
125
|
headers: dict[str, str],
|
|
92
|
-
timeout: int | float,
|
|
126
|
+
timeout: int | float | httpx.Timeout,
|
|
93
127
|
) -> HookHTTPResponse:
|
|
128
|
+
# Compression happens upstream via encode_wire_body: callers that opt in
|
|
129
|
+
# pre-encode the payload (and its Content-Encoding header) so they can
|
|
130
|
+
# also measure the wire size without re-compressing. bytes pass through.
|
|
94
131
|
return http_client.post(
|
|
95
132
|
f"{base_url.rstrip('/')}{endpoint}",
|
|
96
133
|
content=payload,
|
|
@@ -117,9 +154,9 @@ class HookAPIClient:
|
|
|
117
154
|
def post_target(
|
|
118
155
|
self,
|
|
119
156
|
target: str,
|
|
120
|
-
payload: str,
|
|
157
|
+
payload: str | bytes,
|
|
121
158
|
*,
|
|
122
|
-
timeout: int | float | None = None,
|
|
159
|
+
timeout: int | float | httpx.Timeout | None = None,
|
|
123
160
|
) -> HookHTTPResponse:
|
|
124
161
|
spec = HOOK_RELAY_TARGETS[target]
|
|
125
162
|
effective_timeout = timeout if timeout is not None else spec.timeout
|
|
@@ -128,9 +165,9 @@ class HookAPIClient:
|
|
|
128
165
|
def post_endpoint(
|
|
129
166
|
self,
|
|
130
167
|
endpoint: str,
|
|
131
|
-
payload: str,
|
|
168
|
+
payload: str | bytes,
|
|
132
169
|
*,
|
|
133
|
-
timeout: int | float,
|
|
170
|
+
timeout: int | float | httpx.Timeout,
|
|
134
171
|
) -> HookHTTPResponse:
|
|
135
172
|
with self.http_client_factory() as http_client:
|
|
136
173
|
return post_hook_endpoint(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import re
|
|
4
3
|
from collections.abc import Sequence
|
|
5
4
|
from typing import cast
|
|
6
5
|
from urllib.parse import ParseResult, urljoin, urlparse
|
|
@@ -186,7 +185,26 @@ def _option_bool(
|
|
|
186
185
|
|
|
187
186
|
|
|
188
187
|
def _has_explicit_origin_reference(value: str) -> bool:
|
|
189
|
-
|
|
188
|
+
# Detects an RFC-3986 scheme prefix (``scheme:``) or a protocol-relative
|
|
189
|
+
# ``//`` origin. Pure string scan: a published SDK must not carry a
|
|
190
|
+
# regex-engine dependency for one prefix check. Semantics are exactly
|
|
191
|
+
# ``^[a-zA-Z][a-zA-Z\d+\-.]*:`` with Unicode-aware ``\d`` — first char an
|
|
192
|
+
# ASCII letter, then a run of scheme chars (ASCII letter, any Unicode
|
|
193
|
+
# decimal digit per ``str.isdecimal()`` == the ``Nd`` category, or one of
|
|
194
|
+
# ``+ - .``), then a colon.
|
|
195
|
+
if value.startswith("//"):
|
|
196
|
+
return True
|
|
197
|
+
if not value or not ("a" <= value[0] <= "z" or "A" <= value[0] <= "Z"):
|
|
198
|
+
return False
|
|
199
|
+
i = 1
|
|
200
|
+
while i < len(value) and (
|
|
201
|
+
"a" <= value[i] <= "z"
|
|
202
|
+
or "A" <= value[i] <= "Z"
|
|
203
|
+
or value[i] in "+-."
|
|
204
|
+
or value[i].isdecimal()
|
|
205
|
+
):
|
|
206
|
+
i += 1
|
|
207
|
+
return i < len(value) and value[i] == ":"
|
|
190
208
|
|
|
191
209
|
|
|
192
210
|
def _has_valid_explicit_host(value: str) -> bool:
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import gzip
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from runlayer_sdk.hook_transport import (
|
|
7
|
+
GZIP_MIN_PAYLOAD_BYTES,
|
|
8
|
+
HookAPIClient,
|
|
9
|
+
encode_wire_body,
|
|
10
|
+
post_hook_endpoint,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
_LARGE_PAYLOAD = json.dumps({"transcript": "x" * (GZIP_MIN_PAYLOAD_BYTES + 1024)})
|
|
14
|
+
_SMALL_PAYLOAD = json.dumps({"k": "v"})
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _Response:
|
|
18
|
+
text = "{}"
|
|
19
|
+
status_code = 200
|
|
20
|
+
is_success = True
|
|
21
|
+
|
|
22
|
+
def json(self):
|
|
23
|
+
return {}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _CapturingHTTPClient:
|
|
27
|
+
def __init__(self) -> None:
|
|
28
|
+
self.calls: list[dict] = []
|
|
29
|
+
|
|
30
|
+
def __enter__(self) -> _CapturingHTTPClient:
|
|
31
|
+
return self
|
|
32
|
+
|
|
33
|
+
def __exit__(self, *_args: object) -> None:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def post(self, url, *, content, headers, timeout):
|
|
37
|
+
self.calls.append(
|
|
38
|
+
{"url": url, "content": content, "headers": headers, "timeout": timeout}
|
|
39
|
+
)
|
|
40
|
+
return _Response()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TestEncodeWireBody:
|
|
44
|
+
def test_default_is_identity(self):
|
|
45
|
+
wire = encode_wire_body(_LARGE_PAYLOAD)
|
|
46
|
+
assert wire["content"] == _LARGE_PAYLOAD
|
|
47
|
+
assert wire["headers"] == {}
|
|
48
|
+
|
|
49
|
+
def test_small_payload_not_compressed_even_when_opted_in(self):
|
|
50
|
+
wire = encode_wire_body(_SMALL_PAYLOAD, compress=True)
|
|
51
|
+
assert wire["content"] == _SMALL_PAYLOAD
|
|
52
|
+
assert wire["headers"] == {}
|
|
53
|
+
|
|
54
|
+
def test_payload_at_threshold_not_compressed(self):
|
|
55
|
+
payload = "x" * GZIP_MIN_PAYLOAD_BYTES
|
|
56
|
+
wire = encode_wire_body(payload, compress=True)
|
|
57
|
+
assert wire["content"] == payload
|
|
58
|
+
assert wire["headers"] == {}
|
|
59
|
+
|
|
60
|
+
def test_large_payload_compressed_with_header_and_round_trips(self):
|
|
61
|
+
wire = encode_wire_body(_LARGE_PAYLOAD, compress=True)
|
|
62
|
+
content = wire["content"]
|
|
63
|
+
assert isinstance(content, bytes)
|
|
64
|
+
assert len(content) < len(_LARGE_PAYLOAD.encode("utf-8"))
|
|
65
|
+
assert wire["headers"] == {"Content-Encoding": "gzip"}
|
|
66
|
+
assert gzip.decompress(content).decode("utf-8") == _LARGE_PAYLOAD
|
|
67
|
+
|
|
68
|
+
def test_bytes_input_passes_through_without_double_compression(self):
|
|
69
|
+
pre_encoded = encode_wire_body(_LARGE_PAYLOAD, compress=True)["content"]
|
|
70
|
+
assert isinstance(pre_encoded, bytes)
|
|
71
|
+
wire = encode_wire_body(pre_encoded, compress=True)
|
|
72
|
+
assert wire["content"] is pre_encoded
|
|
73
|
+
assert wire["headers"] == {}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TestPostHookEndpoint:
|
|
77
|
+
def test_str_payload_sent_identity(self):
|
|
78
|
+
http_client = _CapturingHTTPClient()
|
|
79
|
+
post_hook_endpoint(
|
|
80
|
+
http_client,
|
|
81
|
+
"https://api.example.test",
|
|
82
|
+
"/api/v1/hooks/events",
|
|
83
|
+
_LARGE_PAYLOAD,
|
|
84
|
+
headers={},
|
|
85
|
+
timeout=5,
|
|
86
|
+
)
|
|
87
|
+
(call,) = http_client.calls
|
|
88
|
+
assert call["content"] == _LARGE_PAYLOAD
|
|
89
|
+
assert "Content-Encoding" not in call["headers"]
|
|
90
|
+
|
|
91
|
+
def test_pre_encoded_bytes_and_headers_pass_through_untouched(self):
|
|
92
|
+
"""Compression lives in encode_wire_body; the transport posts exactly
|
|
93
|
+
the bytes + headers the caller pre-encoded."""
|
|
94
|
+
wire = encode_wire_body(_LARGE_PAYLOAD, compress=True)
|
|
95
|
+
http_client = _CapturingHTTPClient()
|
|
96
|
+
post_hook_endpoint(
|
|
97
|
+
http_client,
|
|
98
|
+
"https://api.example.test",
|
|
99
|
+
"/api/v1/hooks/events",
|
|
100
|
+
wire["content"],
|
|
101
|
+
headers={"x-runlayer-api-key": "sk", **wire["headers"]},
|
|
102
|
+
timeout=5,
|
|
103
|
+
)
|
|
104
|
+
(call,) = http_client.calls
|
|
105
|
+
assert call["content"] is wire["content"]
|
|
106
|
+
assert call["headers"]["Content-Encoding"] == "gzip"
|
|
107
|
+
assert call["headers"]["x-runlayer-api-key"] == "sk"
|
|
108
|
+
assert gzip.decompress(call["content"]).decode("utf-8") == _LARGE_PAYLOAD
|
|
109
|
+
|
|
110
|
+
def test_post_target_sends_payload_as_given(self):
|
|
111
|
+
http_client = _CapturingHTTPClient()
|
|
112
|
+
client = HookAPIClient(
|
|
113
|
+
"https://api.example.test",
|
|
114
|
+
headers={},
|
|
115
|
+
http_client_factory=lambda: http_client,
|
|
116
|
+
)
|
|
117
|
+
client.post_target("event", _LARGE_PAYLOAD)
|
|
118
|
+
(call,) = http_client.calls
|
|
119
|
+
assert call["url"].endswith("/api/v1/hooks/events")
|
|
120
|
+
assert call["content"] == _LARGE_PAYLOAD
|
|
121
|
+
assert "Content-Encoding" not in call["headers"]
|
|
@@ -225,3 +225,51 @@ def test_client_sends_tool_url_to_enforcement_filter_only():
|
|
|
225
225
|
"/api/v1/hooks/events",
|
|
226
226
|
]
|
|
227
227
|
assert "tool_url" not in request_json(requests[1])
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# regex-engine: stdlib-parity
|
|
231
|
+
def test_has_explicit_origin_reference_matches_old_regex():
|
|
232
|
+
"""Pins the pure-string scheme detector to the pre-RE2 regex exactly,
|
|
233
|
+
including the Unicode-decimal-digit behavior of the old ``\\d`` (ENG-4056)."""
|
|
234
|
+
import random
|
|
235
|
+
import re as stdlib_re
|
|
236
|
+
|
|
237
|
+
from runlayer_sdk.tool_enforcement import _has_explicit_origin_reference
|
|
238
|
+
|
|
239
|
+
old = stdlib_re.compile(r"^[a-zA-Z][a-zA-Z\d+\-.]*:")
|
|
240
|
+
|
|
241
|
+
def old_impl(value: str) -> bool:
|
|
242
|
+
return bool(old.match(value)) or value.startswith("//")
|
|
243
|
+
|
|
244
|
+
corpus = [
|
|
245
|
+
"",
|
|
246
|
+
"http://x",
|
|
247
|
+
"https://x",
|
|
248
|
+
"mailto:a@b",
|
|
249
|
+
"a+b-c.d:rest",
|
|
250
|
+
"//protocol-relative",
|
|
251
|
+
"/single-slash",
|
|
252
|
+
"no-scheme-here",
|
|
253
|
+
"1http:", # leading digit -> not a scheme
|
|
254
|
+
"+bad:", # leading + -> not a scheme
|
|
255
|
+
".bad:",
|
|
256
|
+
"a b:", # space breaks the run before the colon
|
|
257
|
+
"scheme", # no colon
|
|
258
|
+
"scheme:",
|
|
259
|
+
"x:",
|
|
260
|
+
"HTTP://X",
|
|
261
|
+
"a:",
|
|
262
|
+
"://",
|
|
263
|
+
"a²:", # superscript-2 (category No) — NOT \d, breaks the run
|
|
264
|
+
"a٠:", # Arabic-Indic digit (category Nd) — IS \d
|
|
265
|
+
"a\n:",
|
|
266
|
+
"urn:isbn:0451450523",
|
|
267
|
+
]
|
|
268
|
+
for v in corpus:
|
|
269
|
+
assert _has_explicit_origin_reference(v) == old_impl(v), repr(v)
|
|
270
|
+
|
|
271
|
+
rng = random.Random(4056)
|
|
272
|
+
alphabet = "abAB01+-.: /²٠\t"
|
|
273
|
+
for _ in range(2000):
|
|
274
|
+
v = "".join(rng.choices(alphabet, k=rng.randint(0, 8)))
|
|
275
|
+
assert _has_explicit_origin_reference(v) == old_impl(v), repr(v)
|
|
@@ -3,7 +3,7 @@ revision = 3
|
|
|
3
3
|
requires-python = ">=3.10"
|
|
4
4
|
|
|
5
5
|
[options]
|
|
6
|
-
exclude-newer = "
|
|
6
|
+
exclude-newer = "2026-08-04T20:02:56.085185Z"
|
|
7
7
|
exclude-newer-span = "P7D"
|
|
8
8
|
|
|
9
9
|
[[package]]
|
|
@@ -177,7 +177,7 @@ wheels = [
|
|
|
177
177
|
|
|
178
178
|
[[package]]
|
|
179
179
|
name = "runlayer-hooks-sdk"
|
|
180
|
-
version = "0.2.
|
|
180
|
+
version = "0.2.2"
|
|
181
181
|
source = { editable = "." }
|
|
182
182
|
dependencies = [
|
|
183
183
|
{ name = "httpx" },
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|