supermemory-agent-framework 1.0.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.
- supermemory_agent_framework/__init__.py +60 -0
- supermemory_agent_framework/connection.py +63 -0
- supermemory_agent_framework/context_provider.py +330 -0
- supermemory_agent_framework/exceptions.py +66 -0
- supermemory_agent_framework/middleware.py +421 -0
- supermemory_agent_framework/tools.py +176 -0
- supermemory_agent_framework/utils.py +152 -0
- supermemory_agent_framework-1.0.0.dist-info/METADATA +383 -0
- supermemory_agent_framework-1.0.0.dist-info/RECORD +11 -0
- supermemory_agent_framework-1.0.0.dist-info/WHEEL +4 -0
- supermemory_agent_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Supermemory Agent Framework - Memory tools and middleware for Microsoft Agent Framework."""
|
|
2
|
+
|
|
3
|
+
from .connection import (
|
|
4
|
+
AgentSupermemory,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
from .tools import (
|
|
8
|
+
SupermemoryTools,
|
|
9
|
+
MemorySearchResult,
|
|
10
|
+
MemoryAddResult,
|
|
11
|
+
ProfileResult,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from .middleware import (
|
|
15
|
+
SupermemoryChatMiddleware,
|
|
16
|
+
SupermemoryMiddlewareOptions,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from .context_provider import (
|
|
20
|
+
SupermemoryContextProvider,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from .utils import (
|
|
24
|
+
Logger,
|
|
25
|
+
create_logger,
|
|
26
|
+
deduplicate_memories,
|
|
27
|
+
DeduplicatedMemories,
|
|
28
|
+
convert_profile_to_markdown,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
from .exceptions import (
|
|
32
|
+
SupermemoryError,
|
|
33
|
+
SupermemoryConfigurationError,
|
|
34
|
+
SupermemoryAPIError,
|
|
35
|
+
SupermemoryMemoryOperationError,
|
|
36
|
+
SupermemoryTimeoutError,
|
|
37
|
+
SupermemoryNetworkError,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"AgentSupermemory",
|
|
42
|
+
"SupermemoryTools",
|
|
43
|
+
"MemorySearchResult",
|
|
44
|
+
"MemoryAddResult",
|
|
45
|
+
"ProfileResult",
|
|
46
|
+
"SupermemoryChatMiddleware",
|
|
47
|
+
"SupermemoryMiddlewareOptions",
|
|
48
|
+
"SupermemoryContextProvider",
|
|
49
|
+
"Logger",
|
|
50
|
+
"create_logger",
|
|
51
|
+
"deduplicate_memories",
|
|
52
|
+
"DeduplicatedMemories",
|
|
53
|
+
"convert_profile_to_markdown",
|
|
54
|
+
"SupermemoryError",
|
|
55
|
+
"SupermemoryConfigurationError",
|
|
56
|
+
"SupermemoryAPIError",
|
|
57
|
+
"SupermemoryMemoryOperationError",
|
|
58
|
+
"SupermemoryTimeoutError",
|
|
59
|
+
"SupermemoryNetworkError",
|
|
60
|
+
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Shared connection class for Supermemory Agent Framework integrations.
|
|
2
|
+
|
|
3
|
+
Provides a single connection object that holds the SDK client, container tag,
|
|
4
|
+
conversation ID, and entity context — shared across middleware, tools, and
|
|
5
|
+
context providers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
import supermemory
|
|
13
|
+
|
|
14
|
+
from .exceptions import SupermemoryConfigurationError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgentSupermemory:
|
|
18
|
+
"""Shared Supermemory connection for middleware, tools, and context providers.
|
|
19
|
+
|
|
20
|
+
Centralizes API client creation, container tag, conversation ID, and
|
|
21
|
+
entity context so that all integration points share the same session.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
```python
|
|
25
|
+
from supermemory_agent_framework import AgentSupermemory
|
|
26
|
+
|
|
27
|
+
conn = AgentSupermemory(
|
|
28
|
+
api_key="your-key",
|
|
29
|
+
container_tag="user-123",
|
|
30
|
+
entity_context="The user is a Python developer who prefers async code.",
|
|
31
|
+
)
|
|
32
|
+
```
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
api_key: Optional[str] = None,
|
|
38
|
+
container_tag: str = "msft_agent_chat",
|
|
39
|
+
entity_context: Optional[str] = None,
|
|
40
|
+
conversation_id: Optional[str] = None,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Initialize the shared Supermemory connection.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
api_key: Supermemory API key. Falls back to SUPERMEMORY_API_KEY env var.
|
|
46
|
+
container_tag: Unique identifier for memory scope (e.g., user ID).
|
|
47
|
+
entity_context: Custom context about the user/entity to prepend to memories.
|
|
48
|
+
conversation_id: Conversation ID for grouping messages. Auto-generated if None.
|
|
49
|
+
"""
|
|
50
|
+
resolved_api_key = api_key or os.getenv("SUPERMEMORY_API_KEY")
|
|
51
|
+
if not resolved_api_key:
|
|
52
|
+
raise SupermemoryConfigurationError(
|
|
53
|
+
"SUPERMEMORY_API_KEY environment variable is required but not set. "
|
|
54
|
+
"Pass api_key parameter or set the environment variable."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
self.client: supermemory.AsyncSupermemory = supermemory.AsyncSupermemory(
|
|
58
|
+
api_key=resolved_api_key
|
|
59
|
+
)
|
|
60
|
+
self.container_tag: str = container_tag
|
|
61
|
+
self.conversation_id: str = conversation_id or str(uuid.uuid4())
|
|
62
|
+
self.custom_id: str = f"conversation_{self.conversation_id}"
|
|
63
|
+
self.entity_context: Optional[str] = entity_context
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""Supermemory context provider for Microsoft Agent Framework.
|
|
2
|
+
|
|
3
|
+
Provides a BaseContextProvider subclass that automatically injects relevant
|
|
4
|
+
memories before LLM invocation and stores conversations after.
|
|
5
|
+
|
|
6
|
+
This is the idiomatic way to integrate persistent memory in Agent Framework,
|
|
7
|
+
following the same pattern as the built-in Mem0 integration.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any, Literal, Optional
|
|
11
|
+
|
|
12
|
+
from agent_framework import BaseContextProvider
|
|
13
|
+
|
|
14
|
+
from .connection import AgentSupermemory
|
|
15
|
+
from .utils import (
|
|
16
|
+
convert_profile_to_markdown,
|
|
17
|
+
create_logger,
|
|
18
|
+
deduplicate_memories,
|
|
19
|
+
wrap_memory_injection,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SupermemoryContextProvider(BaseContextProvider):
|
|
24
|
+
"""Context provider that integrates Supermemory into the agent pipeline.
|
|
25
|
+
|
|
26
|
+
Automatically searches for relevant memories before the model is invoked
|
|
27
|
+
and optionally stores conversations after the model responds.
|
|
28
|
+
|
|
29
|
+
This follows the same pattern as the built-in Mem0 context provider,
|
|
30
|
+
making it the most idiomatic way to add persistent memory to agents.
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
```python
|
|
34
|
+
from agent_framework import Agent, AgentSession
|
|
35
|
+
from agent_framework.openai import OpenAIResponsesClient
|
|
36
|
+
from supermemory_agent_framework import (
|
|
37
|
+
AgentSupermemory,
|
|
38
|
+
SupermemoryContextProvider,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
conn = AgentSupermemory(api_key="your-key", container_tag="user-123")
|
|
42
|
+
|
|
43
|
+
provider = SupermemoryContextProvider(
|
|
44
|
+
conn,
|
|
45
|
+
mode="full",
|
|
46
|
+
store_conversations=True,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
agent = OpenAIResponsesClient().as_agent(
|
|
50
|
+
name="MemoryAgent",
|
|
51
|
+
instructions="You are a helpful assistant with memory.",
|
|
52
|
+
context_providers=[provider],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
session = AgentSession()
|
|
56
|
+
response = await agent.run(
|
|
57
|
+
"What's my favorite programming language?",
|
|
58
|
+
session=session,
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
connection: AgentSupermemory,
|
|
66
|
+
*,
|
|
67
|
+
mode: Literal["profile", "query", "full"] = "full",
|
|
68
|
+
store_conversations: bool = False,
|
|
69
|
+
context_prompt: str = "",
|
|
70
|
+
verbose: bool = False,
|
|
71
|
+
source_id: str = "supermemory",
|
|
72
|
+
) -> None:
|
|
73
|
+
"""Initialize the Supermemory context provider.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
connection: Shared AgentSupermemory connection.
|
|
77
|
+
mode: Memory retrieval mode - "profile", "query", or "full".
|
|
78
|
+
store_conversations: Whether to store conversations after each run.
|
|
79
|
+
context_prompt: Header text prepended to memory content.
|
|
80
|
+
verbose: Enable detailed logging.
|
|
81
|
+
source_id: Unique identifier for this provider instance.
|
|
82
|
+
"""
|
|
83
|
+
super().__init__(source_id=source_id)
|
|
84
|
+
|
|
85
|
+
self._connection = connection
|
|
86
|
+
self._container_tag = connection.container_tag
|
|
87
|
+
self._mode = mode
|
|
88
|
+
self._store_conversations = store_conversations
|
|
89
|
+
self._context_prompt = context_prompt
|
|
90
|
+
self._logger = create_logger(verbose)
|
|
91
|
+
self._client = connection.client
|
|
92
|
+
|
|
93
|
+
async def before_run(
|
|
94
|
+
self,
|
|
95
|
+
*,
|
|
96
|
+
agent: Any,
|
|
97
|
+
session: Any,
|
|
98
|
+
context: Any,
|
|
99
|
+
state: dict[str, Any],
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Search Supermemory for relevant memories and inject into context."""
|
|
102
|
+
# Extract query text from input messages
|
|
103
|
+
query_text = ""
|
|
104
|
+
if self._mode != "profile":
|
|
105
|
+
query_text = self._extract_query_from_context(context)
|
|
106
|
+
if not query_text and self._mode == "query":
|
|
107
|
+
self._logger.debug("No user message found, skipping memory search")
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
self._logger.info(
|
|
111
|
+
"Searching Supermemory for memories",
|
|
112
|
+
{
|
|
113
|
+
"container_tag": self._container_tag,
|
|
114
|
+
"mode": self._mode,
|
|
115
|
+
"query_preview": query_text[:100] if query_text else "",
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
memories_text = await self._fetch_memories(query_text)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
self._logger.error(
|
|
123
|
+
"Failed to fetch memories, proceeding without",
|
|
124
|
+
{"error": str(e)},
|
|
125
|
+
)
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
if not memories_text:
|
|
129
|
+
self._logger.debug("No memories found")
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
# Prepend entity context if available
|
|
133
|
+
if self._connection.entity_context:
|
|
134
|
+
memories_text = f"{self._connection.entity_context}\n\n{memories_text}"
|
|
135
|
+
|
|
136
|
+
# Inject memories into the session context
|
|
137
|
+
full_text = wrap_memory_injection(memories_text, self._context_prompt)
|
|
138
|
+
|
|
139
|
+
self._logger.debug(
|
|
140
|
+
"Injecting memories into context",
|
|
141
|
+
{"length": len(memories_text)},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Use extend_instructions to add memory context
|
|
145
|
+
if hasattr(context, "extend_instructions"):
|
|
146
|
+
context.extend_instructions(full_text, source=self.source_id)
|
|
147
|
+
elif hasattr(context, "extend_messages"):
|
|
148
|
+
# Fallback: add as a system message
|
|
149
|
+
context.extend_messages(
|
|
150
|
+
[{"role": "system", "content": full_text}],
|
|
151
|
+
source=self.source_id,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
async def after_run(
|
|
155
|
+
self,
|
|
156
|
+
*,
|
|
157
|
+
agent: Any,
|
|
158
|
+
session: Any,
|
|
159
|
+
context: Any,
|
|
160
|
+
state: dict[str, Any],
|
|
161
|
+
) -> None:
|
|
162
|
+
"""Store conversation messages to Supermemory for future retrieval."""
|
|
163
|
+
if not self._store_conversations:
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
conversation_text = self._extract_conversation_from_context(context)
|
|
168
|
+
if not conversation_text:
|
|
169
|
+
self._logger.debug("No conversation content to store")
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
self._logger.info(
|
|
173
|
+
"Storing conversation to Supermemory",
|
|
174
|
+
{
|
|
175
|
+
"container_tag": self._container_tag,
|
|
176
|
+
"content_length": len(conversation_text),
|
|
177
|
+
},
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
add_params: dict[str, Any] = {
|
|
181
|
+
"content": conversation_text,
|
|
182
|
+
"container_tag": self._container_tag,
|
|
183
|
+
"custom_id": self._connection.custom_id,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await self._client.add(**add_params)
|
|
187
|
+
|
|
188
|
+
self._logger.info("Conversation stored successfully")
|
|
189
|
+
|
|
190
|
+
except Exception as e:
|
|
191
|
+
self._logger.error(
|
|
192
|
+
"Failed to store conversation",
|
|
193
|
+
{"error": str(e)},
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
async def _fetch_memories(self, query_text: str = "") -> str:
|
|
197
|
+
"""Fetch and format memories from Supermemory."""
|
|
198
|
+
kwargs: dict[str, Any] = {"container_tag": self._container_tag}
|
|
199
|
+
if query_text:
|
|
200
|
+
kwargs["q"] = query_text
|
|
201
|
+
|
|
202
|
+
response = await self._client.profile(**kwargs)
|
|
203
|
+
|
|
204
|
+
profile = response.profile if response.profile else None
|
|
205
|
+
static = list(profile.static) if profile and profile.static else []
|
|
206
|
+
dynamic = list(profile.dynamic) if profile and profile.dynamic else []
|
|
207
|
+
search_results_raw = (
|
|
208
|
+
list(response.search_results.results)
|
|
209
|
+
if response.search_results and response.search_results.results
|
|
210
|
+
else []
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
deduplicated = deduplicate_memories(
|
|
214
|
+
static=static,
|
|
215
|
+
dynamic=dynamic,
|
|
216
|
+
search_results=search_results_raw,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Build formatted text based on mode
|
|
220
|
+
profile_text = ""
|
|
221
|
+
if self._mode != "query":
|
|
222
|
+
profile_text = convert_profile_to_markdown(
|
|
223
|
+
{
|
|
224
|
+
"profile": {
|
|
225
|
+
"static": deduplicated.static,
|
|
226
|
+
"dynamic": deduplicated.dynamic,
|
|
227
|
+
},
|
|
228
|
+
"searchResults": {"results": []},
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
search_text = ""
|
|
233
|
+
if self._mode != "profile" and deduplicated.search_results:
|
|
234
|
+
search_text = "Search results for user's recent message:\n" + "\n".join(
|
|
235
|
+
f"- {memory}" for memory in deduplicated.search_results
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return f"{profile_text}\n{search_text}".strip()
|
|
239
|
+
|
|
240
|
+
def _extract_query_from_context(self, context: Any) -> str:
|
|
241
|
+
"""Extract the last user message from the session context."""
|
|
242
|
+
messages = None
|
|
243
|
+
|
|
244
|
+
if hasattr(context, "input_messages"):
|
|
245
|
+
messages = context.input_messages
|
|
246
|
+
elif hasattr(context, "messages"):
|
|
247
|
+
messages = context.messages
|
|
248
|
+
|
|
249
|
+
if not messages:
|
|
250
|
+
return ""
|
|
251
|
+
|
|
252
|
+
for msg in reversed(list(messages)):
|
|
253
|
+
role = None
|
|
254
|
+
content = None
|
|
255
|
+
|
|
256
|
+
if hasattr(msg, "role"):
|
|
257
|
+
role = msg.role
|
|
258
|
+
elif isinstance(msg, dict):
|
|
259
|
+
role = msg.get("role")
|
|
260
|
+
|
|
261
|
+
if role == "user":
|
|
262
|
+
if hasattr(msg, "text"):
|
|
263
|
+
content = msg.text
|
|
264
|
+
elif hasattr(msg, "content"):
|
|
265
|
+
content = msg.content
|
|
266
|
+
elif isinstance(msg, dict):
|
|
267
|
+
content = msg.get("content", "") or msg.get("text", "")
|
|
268
|
+
|
|
269
|
+
if isinstance(content, str):
|
|
270
|
+
return content
|
|
271
|
+
if isinstance(content, list):
|
|
272
|
+
parts = []
|
|
273
|
+
for part in content:
|
|
274
|
+
if isinstance(part, dict) and part.get("type") == "text":
|
|
275
|
+
parts.append(part.get("text", ""))
|
|
276
|
+
elif isinstance(part, str):
|
|
277
|
+
parts.append(part)
|
|
278
|
+
return " ".join(parts)
|
|
279
|
+
return ""
|
|
280
|
+
|
|
281
|
+
def _extract_conversation_from_context(self, context: Any) -> str:
|
|
282
|
+
"""Extract conversation text from context for storage."""
|
|
283
|
+
messages: list[Any] = []
|
|
284
|
+
|
|
285
|
+
# Gather input messages
|
|
286
|
+
if hasattr(context, "input_messages"):
|
|
287
|
+
messages.extend(context.input_messages or [])
|
|
288
|
+
elif hasattr(context, "messages"):
|
|
289
|
+
messages.extend(context.messages or [])
|
|
290
|
+
|
|
291
|
+
# Gather response messages
|
|
292
|
+
if hasattr(context, "response") and context.response:
|
|
293
|
+
resp = context.response
|
|
294
|
+
if hasattr(resp, "text") and resp.text:
|
|
295
|
+
messages.append({"role": "assistant", "content": resp.text})
|
|
296
|
+
elif hasattr(resp, "messages"):
|
|
297
|
+
messages.extend(resp.messages or [])
|
|
298
|
+
|
|
299
|
+
if not messages:
|
|
300
|
+
return ""
|
|
301
|
+
|
|
302
|
+
parts = []
|
|
303
|
+
for msg in messages:
|
|
304
|
+
role = None
|
|
305
|
+
content = None
|
|
306
|
+
|
|
307
|
+
if hasattr(msg, "role"):
|
|
308
|
+
role = msg.role
|
|
309
|
+
elif isinstance(msg, dict):
|
|
310
|
+
role = msg.get("role")
|
|
311
|
+
|
|
312
|
+
if role not in ("user", "assistant", "system"):
|
|
313
|
+
continue
|
|
314
|
+
|
|
315
|
+
if hasattr(msg, "text"):
|
|
316
|
+
content = msg.text
|
|
317
|
+
elif hasattr(msg, "content"):
|
|
318
|
+
content = msg.content
|
|
319
|
+
elif isinstance(msg, dict):
|
|
320
|
+
content = msg.get("content", "") or msg.get("text", "")
|
|
321
|
+
|
|
322
|
+
if isinstance(content, str) and content.strip():
|
|
323
|
+
display = {
|
|
324
|
+
"user": "User",
|
|
325
|
+
"assistant": "Assistant",
|
|
326
|
+
"system": "System",
|
|
327
|
+
}.get(role, str(role))
|
|
328
|
+
parts.append(f"{display}: {content}")
|
|
329
|
+
|
|
330
|
+
return "\n\n".join(parts)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Custom exceptions for Supermemory Agent Framework integration."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SupermemoryError(Exception):
|
|
7
|
+
"""Base exception for all Supermemory-related errors."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str, original_error: Optional[Exception] = None):
|
|
10
|
+
super().__init__(message)
|
|
11
|
+
self.message = message
|
|
12
|
+
self.original_error = original_error
|
|
13
|
+
|
|
14
|
+
def __str__(self) -> str:
|
|
15
|
+
if self.original_error:
|
|
16
|
+
return f"{self.message}: {self.original_error}"
|
|
17
|
+
return self.message
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SupermemoryConfigurationError(SupermemoryError):
|
|
21
|
+
"""Raised when there are configuration issues (e.g., missing API key)."""
|
|
22
|
+
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SupermemoryAPIError(SupermemoryError):
|
|
27
|
+
"""Raised when Supermemory API requests fail."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
message: str,
|
|
32
|
+
status_code: Optional[int] = None,
|
|
33
|
+
response_text: Optional[str] = None,
|
|
34
|
+
original_error: Optional[Exception] = None,
|
|
35
|
+
):
|
|
36
|
+
super().__init__(message, original_error)
|
|
37
|
+
self.status_code = status_code
|
|
38
|
+
self.response_text = response_text
|
|
39
|
+
|
|
40
|
+
def __str__(self) -> str:
|
|
41
|
+
parts = [self.message]
|
|
42
|
+
if self.status_code:
|
|
43
|
+
parts.append(f"Status: {self.status_code}")
|
|
44
|
+
if self.response_text:
|
|
45
|
+
parts.append(f"Response: {self.response_text}")
|
|
46
|
+
if self.original_error:
|
|
47
|
+
parts.append(f"Cause: {self.original_error}")
|
|
48
|
+
return " | ".join(parts)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SupermemoryMemoryOperationError(SupermemoryError):
|
|
52
|
+
"""Raised when memory operations (search, add) fail."""
|
|
53
|
+
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SupermemoryTimeoutError(SupermemoryError):
|
|
58
|
+
"""Raised when operations timeout."""
|
|
59
|
+
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SupermemoryNetworkError(SupermemoryError):
|
|
64
|
+
"""Raised when network operations fail."""
|
|
65
|
+
|
|
66
|
+
pass
|