syntarus 0.2.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.
- syntarus/__init__.py +18 -0
- syntarus/client.py +219 -0
- syntarus/exceptions.py +38 -0
- syntarus-0.2.0.dist-info/METADATA +142 -0
- syntarus-0.2.0.dist-info/RECORD +7 -0
- syntarus-0.2.0.dist-info/WHEEL +4 -0
- syntarus-0.2.0.dist-info/licenses/LICENSE +21 -0
syntarus/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Official Syntarus project-scoped memory clients."""
|
|
2
|
+
|
|
3
|
+
from .client import AsyncMemoryClient, MemoryClient
|
|
4
|
+
from .exceptions import (
|
|
5
|
+
APIConnectionError,
|
|
6
|
+
APITimeoutError,
|
|
7
|
+
AuthenticationError,
|
|
8
|
+
EventFailedError,
|
|
9
|
+
PermissionDenied,
|
|
10
|
+
RateLimitError,
|
|
11
|
+
SyntarusError,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"AsyncMemoryClient", "MemoryClient", "SyntarusError", "AuthenticationError",
|
|
16
|
+
"PermissionDenied", "RateLimitError", "APIConnectionError", "APITimeoutError",
|
|
17
|
+
"EventFailedError",
|
|
18
|
+
]
|
syntarus/client.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Synchronous and asynchronous clients for the Syntarus v1 memory API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from .exceptions import (
|
|
13
|
+
APIConnectionError,
|
|
14
|
+
APITimeoutError,
|
|
15
|
+
AuthenticationError,
|
|
16
|
+
EventFailedError,
|
|
17
|
+
PermissionDenied,
|
|
18
|
+
RateLimitError,
|
|
19
|
+
SyntarusError,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
DEFAULT_BASE_URL = "https://ai.syntarus.com/v1"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _raise_for_response(response: httpx.Response) -> None:
|
|
26
|
+
if response.is_success:
|
|
27
|
+
return
|
|
28
|
+
try:
|
|
29
|
+
detail = response.json().get("detail", "Request failed.")
|
|
30
|
+
except Exception:
|
|
31
|
+
detail = "Request failed."
|
|
32
|
+
if response.status_code == 401:
|
|
33
|
+
raise AuthenticationError("Project API key was rejected.")
|
|
34
|
+
if response.status_code == 403:
|
|
35
|
+
raise PermissionDenied(str(detail))
|
|
36
|
+
if response.status_code == 429:
|
|
37
|
+
raw_retry = response.headers.get("Retry-After")
|
|
38
|
+
try:
|
|
39
|
+
retry_after = float(raw_retry) if raw_retry is not None else None
|
|
40
|
+
except ValueError:
|
|
41
|
+
retry_after = None
|
|
42
|
+
raise RateLimitError("Project API rate limit exceeded.", retry_after=retry_after)
|
|
43
|
+
raise SyntarusError(f"Syntarus API error ({response.status_code}): {detail}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _BaseClient:
|
|
47
|
+
def __init__(self, api_key: str, *, base_url: str = DEFAULT_BASE_URL, timeout: float = 20.0):
|
|
48
|
+
if not api_key.startswith("sk_mem_"):
|
|
49
|
+
raise ValueError("api_key must be a Syntarus project API key")
|
|
50
|
+
self._base_url = base_url.rstrip("/")
|
|
51
|
+
self._headers = {"Authorization": f"Bearer {api_key}", "User-Agent": "syntarus-python/0.2.0"}
|
|
52
|
+
self._timeout = timeout
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _payload(user_id: str, messages: list[dict[str, str]], agent_id: str | None, run_id: str | None, metadata: dict[str, Any] | None) -> dict[str, Any]:
|
|
56
|
+
return {"user_id": user_id, "messages": messages, "agent_id": agent_id, "run_id": run_id, "metadata": metadata or {}}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class MemoryClient(_BaseClient):
|
|
60
|
+
"""Blocking client. Use ``AsyncMemoryClient`` inside async applications."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, api_key: str, *, base_url: str = DEFAULT_BASE_URL, timeout: float = 20.0):
|
|
63
|
+
super().__init__(api_key, base_url=base_url, timeout=timeout)
|
|
64
|
+
self._client = httpx.Client(base_url=self._base_url, headers=self._headers, timeout=self._timeout)
|
|
65
|
+
|
|
66
|
+
def close(self) -> None:
|
|
67
|
+
self._client.close()
|
|
68
|
+
|
|
69
|
+
def __enter__(self) -> "MemoryClient":
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
def __exit__(self, *_args: object) -> None:
|
|
73
|
+
self.close()
|
|
74
|
+
|
|
75
|
+
def _request(self, method: str, path: str, **kwargs: Any) -> httpx.Response:
|
|
76
|
+
try:
|
|
77
|
+
response = self._client.request(method, path, **kwargs)
|
|
78
|
+
except httpx.TimeoutException as exc:
|
|
79
|
+
raise APITimeoutError("Syntarus API request timed out.") from exc
|
|
80
|
+
except httpx.RequestError as exc:
|
|
81
|
+
raise APIConnectionError("Could not connect to the Syntarus API.") from exc
|
|
82
|
+
_raise_for_response(response)
|
|
83
|
+
return response
|
|
84
|
+
|
|
85
|
+
def add(self, *, user_id: str, messages: list[dict[str, str]], agent_id: str | None = None, run_id: str | None = None, metadata: dict[str, Any] | None = None, idempotency_key: str | None = None) -> dict[str, Any]:
|
|
86
|
+
response = self._request("POST", "/memories", json=self._payload(user_id, messages, agent_id, run_id, metadata), headers={"Idempotency-Key": idempotency_key or uuid.uuid4().hex})
|
|
87
|
+
return response.json()
|
|
88
|
+
|
|
89
|
+
def event(self, event_id: str) -> dict[str, Any]:
|
|
90
|
+
response = self._request("GET", f"/events/{event_id}")
|
|
91
|
+
return response.json()
|
|
92
|
+
|
|
93
|
+
def wait_for_event(self, event_id: str, *, timeout: float = 60.0, poll_interval: float = 0.5) -> dict[str, Any]:
|
|
94
|
+
if timeout <= 0 or poll_interval <= 0:
|
|
95
|
+
raise ValueError("timeout and poll_interval must be positive")
|
|
96
|
+
deadline = time.monotonic() + timeout
|
|
97
|
+
while True:
|
|
98
|
+
event = self.event(event_id)
|
|
99
|
+
if event.get("status") == "succeeded":
|
|
100
|
+
return event
|
|
101
|
+
if event.get("status") in {"dead_letter", "cancelled"}:
|
|
102
|
+
raise EventFailedError(event)
|
|
103
|
+
if time.monotonic() >= deadline:
|
|
104
|
+
raise APITimeoutError(f"Timed out waiting for event {event_id}.")
|
|
105
|
+
time.sleep(min(poll_interval, max(0.0, deadline - time.monotonic())))
|
|
106
|
+
|
|
107
|
+
def search(self, query: str, *, user_id: str, top_k: int = 10, agent_id: str | None = None) -> dict[str, Any]:
|
|
108
|
+
response = self._request("POST", "/memories/search", json={"user_id": user_id, "query": query, "top_k": top_k, "agent_id": agent_id})
|
|
109
|
+
return response.json()
|
|
110
|
+
|
|
111
|
+
def export(self, *, user_id: str, agent_id: str | None = None) -> dict[str, Any]:
|
|
112
|
+
params = {"user_id": user_id, **({"agent_id": agent_id} if agent_id else {})}
|
|
113
|
+
response = self._request("GET", "/memories/export", params=params)
|
|
114
|
+
return response.json()
|
|
115
|
+
|
|
116
|
+
def delete(self, memory_id: str) -> None:
|
|
117
|
+
self._request("DELETE", f"/memories/{memory_id}")
|
|
118
|
+
|
|
119
|
+
def delete_user(self, *, user_id: str, agent_id: str | None = None) -> None:
|
|
120
|
+
params = {"user_id": user_id, **({"agent_id": agent_id} if agent_id else {})}
|
|
121
|
+
self._request("DELETE", "/memories", params=params)
|
|
122
|
+
|
|
123
|
+
def create_webhook(self, url: str, *, event_types: list[str] | None = None) -> dict[str, Any]:
|
|
124
|
+
response = self._request("POST", "/webhooks", json={"url": url, "event_types": event_types or ["memory.ingest.succeeded"]})
|
|
125
|
+
return response.json()
|
|
126
|
+
|
|
127
|
+
def list_webhooks(self) -> dict[str, Any]:
|
|
128
|
+
return self._request("GET", "/webhooks").json()
|
|
129
|
+
|
|
130
|
+
def webhook_deliveries(self, *, webhook_id: str | None = None, limit: int = 100) -> dict[str, Any]:
|
|
131
|
+
params: dict[str, Any] = {"limit": limit}
|
|
132
|
+
if webhook_id:
|
|
133
|
+
params["webhook_id"] = webhook_id
|
|
134
|
+
return self._request("GET", "/webhooks/deliveries", params=params).json()
|
|
135
|
+
|
|
136
|
+
def delete_webhook(self, webhook_id: str) -> None:
|
|
137
|
+
self._request("DELETE", f"/webhooks/{webhook_id}")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class AsyncMemoryClient(_BaseClient):
|
|
141
|
+
"""Async client for FastAPI, LangGraph and other async agent runtimes."""
|
|
142
|
+
|
|
143
|
+
def __init__(self, api_key: str, *, base_url: str = DEFAULT_BASE_URL, timeout: float = 20.0):
|
|
144
|
+
super().__init__(api_key, base_url=base_url, timeout=timeout)
|
|
145
|
+
self._client = httpx.AsyncClient(base_url=self._base_url, headers=self._headers, timeout=self._timeout)
|
|
146
|
+
|
|
147
|
+
async def aclose(self) -> None:
|
|
148
|
+
await self._client.aclose()
|
|
149
|
+
|
|
150
|
+
async def __aenter__(self) -> "AsyncMemoryClient":
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
async def __aexit__(self, *_args: object) -> None:
|
|
154
|
+
await self.aclose()
|
|
155
|
+
|
|
156
|
+
async def _request(self, method: str, path: str, **kwargs: Any) -> httpx.Response:
|
|
157
|
+
try:
|
|
158
|
+
response = await self._client.request(method, path, **kwargs)
|
|
159
|
+
except httpx.TimeoutException as exc:
|
|
160
|
+
raise APITimeoutError("Syntarus API request timed out.") from exc
|
|
161
|
+
except httpx.RequestError as exc:
|
|
162
|
+
raise APIConnectionError("Could not connect to the Syntarus API.") from exc
|
|
163
|
+
_raise_for_response(response)
|
|
164
|
+
return response
|
|
165
|
+
|
|
166
|
+
async def add(self, *, user_id: str, messages: list[dict[str, str]], agent_id: str | None = None, run_id: str | None = None, metadata: dict[str, Any] | None = None, idempotency_key: str | None = None) -> dict[str, Any]:
|
|
167
|
+
response = await self._request("POST", "/memories", json=self._payload(user_id, messages, agent_id, run_id, metadata), headers={"Idempotency-Key": idempotency_key or uuid.uuid4().hex})
|
|
168
|
+
return response.json()
|
|
169
|
+
|
|
170
|
+
async def event(self, event_id: str) -> dict[str, Any]:
|
|
171
|
+
response = await self._request("GET", f"/events/{event_id}")
|
|
172
|
+
return response.json()
|
|
173
|
+
|
|
174
|
+
async def wait_for_event(self, event_id: str, *, timeout: float = 60.0, poll_interval: float = 0.5) -> dict[str, Any]:
|
|
175
|
+
if timeout <= 0 or poll_interval <= 0:
|
|
176
|
+
raise ValueError("timeout and poll_interval must be positive")
|
|
177
|
+
deadline = time.monotonic() + timeout
|
|
178
|
+
while True:
|
|
179
|
+
event = await self.event(event_id)
|
|
180
|
+
if event.get("status") == "succeeded":
|
|
181
|
+
return event
|
|
182
|
+
if event.get("status") in {"dead_letter", "cancelled"}:
|
|
183
|
+
raise EventFailedError(event)
|
|
184
|
+
if time.monotonic() >= deadline:
|
|
185
|
+
raise APITimeoutError(f"Timed out waiting for event {event_id}.")
|
|
186
|
+
await asyncio.sleep(min(poll_interval, max(0.0, deadline - time.monotonic())))
|
|
187
|
+
|
|
188
|
+
async def search(self, query: str, *, user_id: str, top_k: int = 10, agent_id: str | None = None) -> dict[str, Any]:
|
|
189
|
+
response = await self._request("POST", "/memories/search", json={"user_id": user_id, "query": query, "top_k": top_k, "agent_id": agent_id})
|
|
190
|
+
return response.json()
|
|
191
|
+
|
|
192
|
+
async def export(self, *, user_id: str, agent_id: str | None = None) -> dict[str, Any]:
|
|
193
|
+
params = {"user_id": user_id, **({"agent_id": agent_id} if agent_id else {})}
|
|
194
|
+
response = await self._request("GET", "/memories/export", params=params)
|
|
195
|
+
return response.json()
|
|
196
|
+
|
|
197
|
+
async def delete(self, memory_id: str) -> None:
|
|
198
|
+
await self._request("DELETE", f"/memories/{memory_id}")
|
|
199
|
+
|
|
200
|
+
async def delete_user(self, *, user_id: str, agent_id: str | None = None) -> None:
|
|
201
|
+
params = {"user_id": user_id, **({"agent_id": agent_id} if agent_id else {})}
|
|
202
|
+
await self._request("DELETE", "/memories", params=params)
|
|
203
|
+
|
|
204
|
+
async def create_webhook(self, url: str, *, event_types: list[str] | None = None) -> dict[str, Any]:
|
|
205
|
+
response = await self._request("POST", "/webhooks", json={"url": url, "event_types": event_types or ["memory.ingest.succeeded"]})
|
|
206
|
+
return response.json()
|
|
207
|
+
|
|
208
|
+
async def list_webhooks(self) -> dict[str, Any]:
|
|
209
|
+
return (await self._request("GET", "/webhooks")).json()
|
|
210
|
+
|
|
211
|
+
async def webhook_deliveries(self, *, webhook_id: str | None = None, limit: int = 100) -> dict[str, Any]:
|
|
212
|
+
params: dict[str, Any] = {"limit": limit}
|
|
213
|
+
if webhook_id:
|
|
214
|
+
params["webhook_id"] = webhook_id
|
|
215
|
+
response = await self._request("GET", "/webhooks/deliveries", params=params)
|
|
216
|
+
return response.json()
|
|
217
|
+
|
|
218
|
+
async def delete_webhook(self, webhook_id: str) -> None:
|
|
219
|
+
await self._request("DELETE", f"/webhooks/{webhook_id}")
|
syntarus/exceptions.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Typed API errors. Exception messages intentionally omit request secrets."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SyntarusError(Exception):
|
|
5
|
+
"""Base exception raised for an unsuccessful Syntarus API response."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AuthenticationError(SyntarusError):
|
|
9
|
+
"""The project API key is invalid, expired, or revoked."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PermissionDenied(SyntarusError):
|
|
13
|
+
"""The project API key does not have the required scope."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RateLimitError(SyntarusError):
|
|
17
|
+
"""The project API key has exceeded its request budget."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, message: str, *, retry_after: float | None = None):
|
|
20
|
+
super().__init__(message)
|
|
21
|
+
self.retry_after = retry_after
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class APIConnectionError(SyntarusError):
|
|
25
|
+
"""The Syntarus API could not be reached."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class APITimeoutError(APIConnectionError):
|
|
29
|
+
"""The Syntarus API did not respond before the configured timeout."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class EventFailedError(SyntarusError):
|
|
33
|
+
"""An accepted asynchronous memory event reached a terminal failure."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, event: dict):
|
|
36
|
+
self.event = event
|
|
37
|
+
detail = event.get("last_error") or f"event ended with status {event.get('status')}"
|
|
38
|
+
super().__init__(str(detail))
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: syntarus
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Project-scoped memory client for Syntarus
|
|
5
|
+
Project-URL: Documentation, https://www.syntarus.com/pages/developers.html
|
|
6
|
+
Project-URL: Homepage, https://www.syntarus.com
|
|
7
|
+
Project-URL: Source, https://github.com/sujalkherawat25-stack/memoryos/tree/main/sdk
|
|
8
|
+
Author: Syntarus
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026 Syntarus
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Keywords: agents,ai,llm,memory
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
39
|
+
Requires-Python: >=3.10
|
|
40
|
+
Requires-Dist: httpx<1,>=0.27
|
|
41
|
+
Provides-Extra: test
|
|
42
|
+
Requires-Dist: build<2,>=1.2; extra == 'test'
|
|
43
|
+
Requires-Dist: pytest-asyncio<1,>=0.24; extra == 'test'
|
|
44
|
+
Requires-Dist: pytest<9,>=8; extra == 'test'
|
|
45
|
+
Requires-Dist: twine<7,>=5; extra == 'test'
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
|
|
48
|
+
# Syntarus Python SDK
|
|
49
|
+
|
|
50
|
+
The official Python client for the project-scoped Syntarus memory API.
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install syntarus
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Python 3.10 or newer is required. Keep project API keys in a server-side
|
|
59
|
+
secret manager; never ship them in browser or mobile applications.
|
|
60
|
+
|
|
61
|
+
## Add and search memory
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from syntarus import MemoryClient
|
|
65
|
+
|
|
66
|
+
with MemoryClient(api_key="sk_mem_...") as memory:
|
|
67
|
+
accepted = memory.add(
|
|
68
|
+
user_id="customer_123",
|
|
69
|
+
agent_id="support_agent",
|
|
70
|
+
run_id="ticket_456",
|
|
71
|
+
messages=[
|
|
72
|
+
{"role": "user", "content": "I prefer vegetarian food."},
|
|
73
|
+
{"role": "assistant", "content": "I will remember that."},
|
|
74
|
+
],
|
|
75
|
+
metadata={"channel": "voice"},
|
|
76
|
+
idempotency_key="call-456-final",
|
|
77
|
+
)
|
|
78
|
+
memory.wait_for_event(accepted["event_id"])
|
|
79
|
+
result = memory.search(
|
|
80
|
+
"What food does the customer prefer?",
|
|
81
|
+
user_id="customer_123",
|
|
82
|
+
agent_id="support_agent",
|
|
83
|
+
)
|
|
84
|
+
print(result["context"])
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`agent_id` creates an isolated memory namespace. Omit it for a user profile
|
|
88
|
+
shared by every agent in the project. `run_id` groups the conversation history
|
|
89
|
+
used during ingestion. `metadata` is event metadata and is returned by event
|
|
90
|
+
status and webhook payloads; it is not currently a memory-search filter.
|
|
91
|
+
|
|
92
|
+
## Async client
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from syntarus import AsyncMemoryClient
|
|
96
|
+
|
|
97
|
+
async with AsyncMemoryClient(api_key="sk_mem_...") as memory:
|
|
98
|
+
accepted = await memory.add(
|
|
99
|
+
user_id="customer_123",
|
|
100
|
+
messages=[{"role": "user", "content": "Call me after 5 PM."}],
|
|
101
|
+
)
|
|
102
|
+
await memory.wait_for_event(accepted["event_id"])
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Lifecycle operations
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
memory.export(user_id="customer_123", agent_id="support_agent")
|
|
109
|
+
memory.delete("memory-uuid")
|
|
110
|
+
memory.delete_user(user_id="customer_123", agent_id="support_agent")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Deleting a user without `agent_id` deletes only the shared user namespace.
|
|
114
|
+
Delete each agent namespace separately when an application uses agent-scoped
|
|
115
|
+
memory.
|
|
116
|
+
|
|
117
|
+
## Webhooks
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
created = memory.create_webhook("https://example.com/syntarus/events")
|
|
121
|
+
signing_secret = created["signing_secret"] # returned once
|
|
122
|
+
memory.list_webhooks()
|
|
123
|
+
memory.webhook_deliveries(webhook_id=created["webhook"]["id"])
|
|
124
|
+
memory.delete_webhook(created["webhook"]["id"])
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Deliveries include `X-Syntarus-Timestamp`, `X-Syntarus-Delivery`, and
|
|
128
|
+
`X-Syntarus-Signature-256`. Verify the signature as HMAC-SHA256 over
|
|
129
|
+
`<timestamp>.<raw_request_body>` and reject timestamps outside a five-minute
|
|
130
|
+
window. A non-2xx response is retried durably and eventually becomes a
|
|
131
|
+
dead-letter delivery visible through `webhook_deliveries`.
|
|
132
|
+
|
|
133
|
+
## Errors
|
|
134
|
+
|
|
135
|
+
The SDK raises typed exceptions derived from `SyntarusError`:
|
|
136
|
+
|
|
137
|
+
- `AuthenticationError`
|
|
138
|
+
- `PermissionDenied`
|
|
139
|
+
- `RateLimitError` (`retry_after` is available when the API sends it)
|
|
140
|
+
- `APIConnectionError`
|
|
141
|
+
- `APITimeoutError`
|
|
142
|
+
- `EventFailedError` (`event` contains the terminal event record)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
syntarus/__init__.py,sha256=e3EKF7l7_kp1NpQ4ODO30zi00EhdpgWgisF_zVvLbXc,492
|
|
2
|
+
syntarus/client.py,sha256=_Em0uDOZfhB_ex_cEOveq_NuIRebD1D4rOkfSTtMRzE,10380
|
|
3
|
+
syntarus/exceptions.py,sha256=MZd6DpODf8kTBZGwJTSviIWLo-ajWLZykPylw3ryHZU,1175
|
|
4
|
+
syntarus-0.2.0.dist-info/METADATA,sha256=jZB6YrXiCoNc4aIev9lHaMBGz4mpPGfbM-MbYIpRGq0,5231
|
|
5
|
+
syntarus-0.2.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
6
|
+
syntarus-0.2.0.dist-info/licenses/LICENSE,sha256=g-up2h6UU5Af0pIbAAu214vBiACCwqocsK3gvD6BbkA,1065
|
|
7
|
+
syntarus-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Syntarus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|