aury-agent 0.0.4__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.
Files changed (149) hide show
  1. aury/__init__.py +2 -0
  2. aury/agents/__init__.py +55 -0
  3. aury/agents/a2a/__init__.py +168 -0
  4. aury/agents/backends/__init__.py +196 -0
  5. aury/agents/backends/artifact/__init__.py +9 -0
  6. aury/agents/backends/artifact/memory.py +130 -0
  7. aury/agents/backends/artifact/types.py +133 -0
  8. aury/agents/backends/code/__init__.py +65 -0
  9. aury/agents/backends/file/__init__.py +11 -0
  10. aury/agents/backends/file/local.py +66 -0
  11. aury/agents/backends/file/types.py +40 -0
  12. aury/agents/backends/invocation/__init__.py +8 -0
  13. aury/agents/backends/invocation/memory.py +81 -0
  14. aury/agents/backends/invocation/types.py +110 -0
  15. aury/agents/backends/memory/__init__.py +8 -0
  16. aury/agents/backends/memory/memory.py +179 -0
  17. aury/agents/backends/memory/types.py +136 -0
  18. aury/agents/backends/message/__init__.py +9 -0
  19. aury/agents/backends/message/memory.py +122 -0
  20. aury/agents/backends/message/types.py +124 -0
  21. aury/agents/backends/sandbox.py +275 -0
  22. aury/agents/backends/session/__init__.py +8 -0
  23. aury/agents/backends/session/memory.py +93 -0
  24. aury/agents/backends/session/types.py +124 -0
  25. aury/agents/backends/shell/__init__.py +11 -0
  26. aury/agents/backends/shell/local.py +110 -0
  27. aury/agents/backends/shell/types.py +55 -0
  28. aury/agents/backends/shell.py +209 -0
  29. aury/agents/backends/snapshot/__init__.py +19 -0
  30. aury/agents/backends/snapshot/git.py +95 -0
  31. aury/agents/backends/snapshot/hybrid.py +125 -0
  32. aury/agents/backends/snapshot/memory.py +86 -0
  33. aury/agents/backends/snapshot/types.py +59 -0
  34. aury/agents/backends/state/__init__.py +29 -0
  35. aury/agents/backends/state/composite.py +49 -0
  36. aury/agents/backends/state/file.py +57 -0
  37. aury/agents/backends/state/memory.py +52 -0
  38. aury/agents/backends/state/sqlite.py +262 -0
  39. aury/agents/backends/state/types.py +178 -0
  40. aury/agents/backends/subagent/__init__.py +165 -0
  41. aury/agents/cli/__init__.py +41 -0
  42. aury/agents/cli/chat.py +239 -0
  43. aury/agents/cli/config.py +236 -0
  44. aury/agents/cli/extensions.py +460 -0
  45. aury/agents/cli/main.py +189 -0
  46. aury/agents/cli/session.py +337 -0
  47. aury/agents/cli/workflow.py +276 -0
  48. aury/agents/context_providers/__init__.py +66 -0
  49. aury/agents/context_providers/artifact.py +299 -0
  50. aury/agents/context_providers/base.py +177 -0
  51. aury/agents/context_providers/memory.py +70 -0
  52. aury/agents/context_providers/message.py +130 -0
  53. aury/agents/context_providers/skill.py +50 -0
  54. aury/agents/context_providers/subagent.py +46 -0
  55. aury/agents/context_providers/tool.py +68 -0
  56. aury/agents/core/__init__.py +83 -0
  57. aury/agents/core/base.py +573 -0
  58. aury/agents/core/context.py +797 -0
  59. aury/agents/core/context_builder.py +303 -0
  60. aury/agents/core/event_bus/__init__.py +15 -0
  61. aury/agents/core/event_bus/bus.py +203 -0
  62. aury/agents/core/factory.py +169 -0
  63. aury/agents/core/isolator.py +97 -0
  64. aury/agents/core/logging.py +95 -0
  65. aury/agents/core/parallel.py +194 -0
  66. aury/agents/core/runner.py +139 -0
  67. aury/agents/core/services/__init__.py +5 -0
  68. aury/agents/core/services/file_session.py +144 -0
  69. aury/agents/core/services/message.py +53 -0
  70. aury/agents/core/services/session.py +53 -0
  71. aury/agents/core/signals.py +109 -0
  72. aury/agents/core/state.py +363 -0
  73. aury/agents/core/types/__init__.py +107 -0
  74. aury/agents/core/types/action.py +176 -0
  75. aury/agents/core/types/artifact.py +135 -0
  76. aury/agents/core/types/block.py +736 -0
  77. aury/agents/core/types/message.py +350 -0
  78. aury/agents/core/types/recall.py +144 -0
  79. aury/agents/core/types/session.py +257 -0
  80. aury/agents/core/types/subagent.py +154 -0
  81. aury/agents/core/types/tool.py +205 -0
  82. aury/agents/eval/__init__.py +331 -0
  83. aury/agents/hitl/__init__.py +57 -0
  84. aury/agents/hitl/ask_user.py +242 -0
  85. aury/agents/hitl/compaction.py +230 -0
  86. aury/agents/hitl/exceptions.py +87 -0
  87. aury/agents/hitl/permission.py +617 -0
  88. aury/agents/hitl/revert.py +216 -0
  89. aury/agents/llm/__init__.py +31 -0
  90. aury/agents/llm/adapter.py +367 -0
  91. aury/agents/llm/openai.py +294 -0
  92. aury/agents/llm/provider.py +476 -0
  93. aury/agents/mcp/__init__.py +153 -0
  94. aury/agents/memory/__init__.py +46 -0
  95. aury/agents/memory/compaction.py +394 -0
  96. aury/agents/memory/manager.py +465 -0
  97. aury/agents/memory/processor.py +177 -0
  98. aury/agents/memory/store.py +187 -0
  99. aury/agents/memory/types.py +137 -0
  100. aury/agents/messages/__init__.py +40 -0
  101. aury/agents/messages/config.py +47 -0
  102. aury/agents/messages/raw_store.py +224 -0
  103. aury/agents/messages/store.py +118 -0
  104. aury/agents/messages/types.py +88 -0
  105. aury/agents/middleware/__init__.py +31 -0
  106. aury/agents/middleware/base.py +341 -0
  107. aury/agents/middleware/chain.py +342 -0
  108. aury/agents/middleware/message.py +129 -0
  109. aury/agents/middleware/message_container.py +126 -0
  110. aury/agents/middleware/raw_message.py +153 -0
  111. aury/agents/middleware/truncation.py +139 -0
  112. aury/agents/middleware/types.py +81 -0
  113. aury/agents/plugin.py +162 -0
  114. aury/agents/react/__init__.py +4 -0
  115. aury/agents/react/agent.py +1923 -0
  116. aury/agents/sandbox/__init__.py +23 -0
  117. aury/agents/sandbox/local.py +239 -0
  118. aury/agents/sandbox/remote.py +200 -0
  119. aury/agents/sandbox/types.py +115 -0
  120. aury/agents/skill/__init__.py +16 -0
  121. aury/agents/skill/loader.py +180 -0
  122. aury/agents/skill/types.py +83 -0
  123. aury/agents/tool/__init__.py +39 -0
  124. aury/agents/tool/builtin/__init__.py +23 -0
  125. aury/agents/tool/builtin/ask_user.py +155 -0
  126. aury/agents/tool/builtin/bash.py +107 -0
  127. aury/agents/tool/builtin/delegate.py +726 -0
  128. aury/agents/tool/builtin/edit.py +121 -0
  129. aury/agents/tool/builtin/plan.py +277 -0
  130. aury/agents/tool/builtin/read.py +91 -0
  131. aury/agents/tool/builtin/thinking.py +111 -0
  132. aury/agents/tool/builtin/yield_result.py +130 -0
  133. aury/agents/tool/decorator.py +252 -0
  134. aury/agents/tool/set.py +204 -0
  135. aury/agents/usage/__init__.py +12 -0
  136. aury/agents/usage/tracker.py +236 -0
  137. aury/agents/workflow/__init__.py +85 -0
  138. aury/agents/workflow/adapter.py +268 -0
  139. aury/agents/workflow/dag.py +116 -0
  140. aury/agents/workflow/dsl.py +575 -0
  141. aury/agents/workflow/executor.py +659 -0
  142. aury/agents/workflow/expression.py +136 -0
  143. aury/agents/workflow/parser.py +182 -0
  144. aury/agents/workflow/state.py +145 -0
  145. aury/agents/workflow/types.py +86 -0
  146. aury_agent-0.0.4.dist-info/METADATA +90 -0
  147. aury_agent-0.0.4.dist-info/RECORD +149 -0
  148. aury_agent-0.0.4.dist-info/WHEEL +4 -0
  149. aury_agent-0.0.4.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,350 @@
1
+ """Message data structure for LLM communication.
2
+
3
+ Message is the AI layer - separate from Block (UI layer).
4
+ Messages are stored independently and linked to Blocks via invocation_id.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass, field
9
+ from datetime import datetime
10
+ from enum import Enum
11
+ from typing import Any, TYPE_CHECKING
12
+
13
+ from .session import generate_id
14
+
15
+ if TYPE_CHECKING:
16
+ from .artifact import ArtifactRef
17
+
18
+
19
+ class MessageRole(Enum):
20
+ """Message role."""
21
+ SYSTEM = "system"
22
+ USER = "user"
23
+ ASSISTANT = "assistant"
24
+
25
+
26
+ @dataclass
27
+ class MessageContent:
28
+ """Content block within a Message.
29
+
30
+ Unlike Block (UI layer), MessageContent is purely for LLM communication.
31
+ """
32
+ type: str # text, image, tool_use, tool_result
33
+
34
+ # text
35
+ text: str | None = None
36
+
37
+ # image
38
+ image_url: str | None = None
39
+ image_base64: str | None = None
40
+ image_media_type: str | None = None
41
+
42
+ # tool_use
43
+ tool_call_id: str | None = None
44
+ tool_name: str | None = None
45
+ tool_arguments: dict[str, Any] | None = None
46
+
47
+ # tool_result
48
+ tool_use_id: str | None = None
49
+ tool_output: str | None = None
50
+ is_error: bool = False
51
+
52
+ def to_llm_format(self) -> dict[str, Any]:
53
+ """Convert to LLM API format."""
54
+ if self.type == "text":
55
+ return {"type": "text", "text": self.text or ""}
56
+
57
+ elif self.type == "image":
58
+ if self.image_url:
59
+ return {
60
+ "type": "image",
61
+ "source": {"type": "url", "url": self.image_url},
62
+ }
63
+ elif self.image_base64:
64
+ return {
65
+ "type": "image",
66
+ "source": {
67
+ "type": "base64",
68
+ "media_type": self.image_media_type or "image/png",
69
+ "data": self.image_base64,
70
+ },
71
+ }
72
+
73
+ elif self.type == "tool_use":
74
+ return {
75
+ "type": "tool_use",
76
+ "id": self.tool_call_id or "",
77
+ "name": self.tool_name or "",
78
+ "input": self.tool_arguments or {},
79
+ }
80
+
81
+ elif self.type == "tool_result":
82
+ return {
83
+ "type": "tool_result",
84
+ "tool_use_id": self.tool_use_id or "",
85
+ "content": self.tool_output or "",
86
+ "is_error": self.is_error,
87
+ }
88
+
89
+ return {"type": self.type}
90
+
91
+ def to_dict(self) -> dict[str, Any]:
92
+ """Convert to dictionary for serialization."""
93
+ data: dict[str, Any] = {"type": self.type}
94
+
95
+ if self.text is not None:
96
+ data["text"] = self.text
97
+ if self.image_url is not None:
98
+ data["image_url"] = self.image_url
99
+ if self.image_base64 is not None:
100
+ data["image_base64"] = self.image_base64
101
+ if self.image_media_type is not None:
102
+ data["image_media_type"] = self.image_media_type
103
+ if self.tool_call_id is not None:
104
+ data["tool_call_id"] = self.tool_call_id
105
+ if self.tool_name is not None:
106
+ data["tool_name"] = self.tool_name
107
+ if self.tool_arguments is not None:
108
+ data["tool_arguments"] = self.tool_arguments
109
+ if self.tool_use_id is not None:
110
+ data["tool_use_id"] = self.tool_use_id
111
+ if self.tool_output is not None:
112
+ data["tool_output"] = self.tool_output
113
+ if self.is_error:
114
+ data["is_error"] = self.is_error
115
+
116
+ return data
117
+
118
+ @classmethod
119
+ def from_dict(cls, data: dict[str, Any]) -> "MessageContent":
120
+ """Create from dictionary."""
121
+ return cls(
122
+ type=data["type"],
123
+ text=data.get("text"),
124
+ image_url=data.get("image_url"),
125
+ image_base64=data.get("image_base64"),
126
+ image_media_type=data.get("image_media_type"),
127
+ tool_call_id=data.get("tool_call_id"),
128
+ tool_name=data.get("tool_name"),
129
+ tool_arguments=data.get("tool_arguments"),
130
+ tool_use_id=data.get("tool_use_id"),
131
+ tool_output=data.get("tool_output"),
132
+ is_error=data.get("is_error", False),
133
+ )
134
+
135
+ # Factory methods
136
+ @classmethod
137
+ def text(cls, content: str) -> "MessageContent":
138
+ """Create text content."""
139
+ return cls(type="text", text=content)
140
+
141
+ @classmethod
142
+ def tool_use(cls, call_id: str, name: str, arguments: dict[str, Any]) -> "MessageContent":
143
+ """Create tool use content."""
144
+ return cls(
145
+ type="tool_use",
146
+ tool_call_id=call_id,
147
+ tool_name=name,
148
+ tool_arguments=arguments,
149
+ )
150
+
151
+ @classmethod
152
+ def tool_result(cls, tool_use_id: str, output: str, is_error: bool = False) -> "MessageContent":
153
+ """Create tool result content."""
154
+ return cls(
155
+ type="tool_result",
156
+ tool_use_id=tool_use_id,
157
+ tool_output=output,
158
+ is_error=is_error,
159
+ )
160
+
161
+
162
+ @dataclass
163
+ class Message:
164
+ """A message in the conversation (AI layer).
165
+
166
+ Message is separate from Block:
167
+ - Message: AI communication layer (stored for LLM context)
168
+ - Block: UI display layer (stored for frontend rendering)
169
+
170
+ They are linked via invocation_id for revert operations.
171
+ """
172
+ id: str = field(default_factory=lambda: generate_id("msg"))
173
+ role: MessageRole = MessageRole.USER
174
+
175
+ # Content (multiple formats supported)
176
+ content: list[MessageContent] = field(default_factory=list)
177
+
178
+ # Artifact references (full content in ArtifactStore)
179
+ artifact_refs: list["ArtifactRef"] = field(default_factory=list)
180
+
181
+ # Session context
182
+ session_id: str = ""
183
+ invocation_id: str = ""
184
+
185
+ # Branch for sub-agent isolation
186
+ branch: str | None = None
187
+
188
+ # Timestamps
189
+ created_at: datetime = field(default_factory=datetime.now)
190
+
191
+ # Extension
192
+ metadata: dict[str, Any] = field(default_factory=dict)
193
+
194
+ @property
195
+ def text_content(self) -> str:
196
+ """Get combined text content."""
197
+ parts = []
198
+ for c in self.content:
199
+ if c.type == "text" and c.text:
200
+ parts.append(c.text)
201
+ return "".join(parts)
202
+
203
+ @property
204
+ def tool_calls(self) -> list[MessageContent]:
205
+ """Get all tool_use content."""
206
+ return [c for c in self.content if c.type == "tool_use"]
207
+
208
+ @property
209
+ def tool_results(self) -> list[MessageContent]:
210
+ """Get all tool_result content."""
211
+ return [c for c in self.content if c.type == "tool_result"]
212
+
213
+ def to_llm_format(self) -> dict[str, Any]:
214
+ """Convert to LLM API format (Anthropic-style).
215
+
216
+ Returns a message dict suitable for LLM API calls.
217
+ Artifact references are converted to text descriptions.
218
+ """
219
+ content_parts: list[dict[str, Any]] = []
220
+
221
+ # Add regular content
222
+ for c in self.content:
223
+ llm_part = c.to_llm_format()
224
+ if llm_part:
225
+ content_parts.append(llm_part)
226
+
227
+ # Add artifact references as context
228
+ if self.artifact_refs:
229
+ refs_text = "\n\n[Available Artifacts]\n"
230
+ for ref in self.artifact_refs:
231
+ refs_text += f"- [artifact:{ref.artifact_id}] {ref.title or 'Untitled'}: {ref.summary or 'No summary'}\n"
232
+ content_parts.append({"type": "text", "text": refs_text})
233
+
234
+ # Simplify if single text content
235
+ if len(content_parts) == 1 and content_parts[0].get("type") == "text":
236
+ return {
237
+ "role": self.role.value,
238
+ "content": content_parts[0]["text"],
239
+ }
240
+
241
+ return {
242
+ "role": self.role.value,
243
+ "content": content_parts,
244
+ }
245
+
246
+ def to_dict(self) -> dict[str, Any]:
247
+ """Convert to dictionary for serialization."""
248
+ return {
249
+ "id": self.id,
250
+ "role": self.role.value,
251
+ "content": [c.to_dict() for c in self.content],
252
+ "artifact_refs": [r.to_dict() for r in self.artifact_refs] if self.artifact_refs else [],
253
+ "session_id": self.session_id,
254
+ "invocation_id": self.invocation_id,
255
+ "branch": self.branch,
256
+ "created_at": self.created_at.isoformat(),
257
+ "metadata": self.metadata,
258
+ }
259
+
260
+ @classmethod
261
+ def from_dict(cls, data: dict[str, Any]) -> "Message":
262
+ """Create from dictionary."""
263
+ from .artifact import ArtifactRef
264
+
265
+ return cls(
266
+ id=data["id"],
267
+ role=MessageRole(data["role"]),
268
+ content=[MessageContent.from_dict(c) for c in data.get("content", [])],
269
+ artifact_refs=[ArtifactRef.from_dict(r) for r in data.get("artifact_refs", [])],
270
+ session_id=data.get("session_id", ""),
271
+ invocation_id=data.get("invocation_id", ""),
272
+ branch=data.get("branch"),
273
+ created_at=datetime.fromisoformat(data["created_at"]) if "created_at" in data else datetime.now(),
274
+ metadata=data.get("metadata", {}),
275
+ )
276
+
277
+ # Factory methods
278
+ @classmethod
279
+ def user(cls, text: str, session_id: str = "", invocation_id: str = "") -> "Message":
280
+ """Create a user message."""
281
+ return cls(
282
+ role=MessageRole.USER,
283
+ content=[MessageContent.text(text)],
284
+ session_id=session_id,
285
+ invocation_id=invocation_id,
286
+ )
287
+
288
+ @classmethod
289
+ def assistant(cls, text: str, session_id: str = "", invocation_id: str = "") -> "Message":
290
+ """Create an assistant message."""
291
+ return cls(
292
+ role=MessageRole.ASSISTANT,
293
+ content=[MessageContent.text(text)],
294
+ session_id=session_id,
295
+ invocation_id=invocation_id,
296
+ )
297
+
298
+ @classmethod
299
+ def system(cls, text: str) -> "Message":
300
+ """Create a system message."""
301
+ return cls(
302
+ role=MessageRole.SYSTEM,
303
+ content=[MessageContent.text(text)],
304
+ )
305
+
306
+
307
+ @dataclass
308
+ class PromptInput:
309
+ """User input for agent invocation.
310
+
311
+ Attributes:
312
+ text: User message text
313
+ runtime_tools: Tools to add for this run only
314
+ vars: Runtime variables (accessible by Managers via ctx.input.vars)
315
+ attachments: Image/file attachments
316
+ metadata: Additional metadata
317
+ """
318
+ text: str
319
+ runtime_tools: list[Any] = field(default_factory=list) # list[BaseTool]
320
+ vars: dict[str, Any] = field(default_factory=dict)
321
+ attachments: list[dict[str, Any]] | None = None
322
+ metadata: dict[str, Any] = field(default_factory=dict)
323
+
324
+ def to_message(self, session_id: str = "", invocation_id: str = "") -> Message:
325
+ """Convert to a Message."""
326
+ content = [MessageContent.text(self.text)]
327
+
328
+ # Handle attachments (images, etc.)
329
+ if self.attachments:
330
+ for att in self.attachments:
331
+ if att.get("type") == "image":
332
+ if "url" in att:
333
+ content.append(MessageContent(
334
+ type="image",
335
+ image_url=att["url"],
336
+ ))
337
+ elif "base64" in att:
338
+ content.append(MessageContent(
339
+ type="image",
340
+ image_base64=att["base64"],
341
+ image_media_type=att.get("media_type", "image/png"),
342
+ ))
343
+
344
+ return Message(
345
+ role=MessageRole.USER,
346
+ content=content,
347
+ session_id=session_id,
348
+ invocation_id=invocation_id,
349
+ metadata=self.metadata,
350
+ )
@@ -0,0 +1,144 @@
1
+ """Recall data structures for session memory.
2
+
3
+ Recalls are key points within a session:
4
+ - Extracted from conversation (user info, preferences, decisions)
5
+ - Used for quick context retrieval
6
+ - Support revert (linked to invocation_id)
7
+ - Support SubAgent isolation (branch field)
8
+
9
+ Unlike Knowledge (cross-session, user-defined), Recalls are:
10
+ - Session-scoped
11
+ - Framework-managed
12
+ - Automatically linked to invocations for revert
13
+ """
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import dataclass, field
17
+ from datetime import datetime
18
+ from typing import Any
19
+
20
+ from .session import generate_id
21
+
22
+
23
+ @dataclass
24
+ class Recall:
25
+ """A recall point (session memory).
26
+
27
+ Captures important information from the conversation
28
+ for quick retrieval and context building.
29
+ """
30
+ id: str = field(default_factory=lambda: generate_id("rcl"))
31
+
32
+ # Content
33
+ content: str = ""
34
+ importance: float = 0.5 # 0.0 - 1.0
35
+
36
+ # Classification
37
+ category: str | None = None # user_info, preference, task, decision, fact...
38
+ tags: list[str] = field(default_factory=list)
39
+
40
+ # Session context (for revert)
41
+ session_id: str = ""
42
+ invocation_id: str = "" # Source invocation (used for revert)
43
+
44
+ # SubAgent isolation
45
+ branch: str | None = None
46
+
47
+ # Timestamps
48
+ created_at: datetime = field(default_factory=datetime.now)
49
+ expires_at: datetime | None = None # Optional TTL
50
+
51
+ # Extension
52
+ metadata: dict[str, Any] = field(default_factory=dict)
53
+
54
+ def to_dict(self) -> dict[str, Any]:
55
+ """Convert to dictionary for serialization."""
56
+ return {
57
+ "id": self.id,
58
+ "content": self.content,
59
+ "importance": self.importance,
60
+ "category": self.category,
61
+ "tags": self.tags,
62
+ "session_id": self.session_id,
63
+ "invocation_id": self.invocation_id,
64
+ "branch": self.branch,
65
+ "created_at": self.created_at.isoformat(),
66
+ "expires_at": self.expires_at.isoformat() if self.expires_at else None,
67
+ "metadata": self.metadata,
68
+ }
69
+
70
+ @classmethod
71
+ def from_dict(cls, data: dict[str, Any]) -> "Recall":
72
+ """Create from dictionary."""
73
+ return cls(
74
+ id=data["id"],
75
+ content=data.get("content", ""),
76
+ importance=data.get("importance", 0.5),
77
+ category=data.get("category"),
78
+ tags=data.get("tags", []),
79
+ session_id=data.get("session_id", ""),
80
+ invocation_id=data.get("invocation_id", ""),
81
+ branch=data.get("branch"),
82
+ created_at=datetime.fromisoformat(data["created_at"]) if "created_at" in data else datetime.now(),
83
+ expires_at=datetime.fromisoformat(data["expires_at"]) if data.get("expires_at") else None,
84
+ metadata=data.get("metadata", {}),
85
+ )
86
+
87
+
88
+ @dataclass
89
+ class Summary:
90
+ """Session summary (compressed history).
91
+
92
+ Reduces token consumption by summarizing old messages.
93
+ """
94
+ id: str = field(default_factory=lambda: generate_id("sum"))
95
+
96
+ # Content
97
+ content: str = ""
98
+
99
+ # Coverage
100
+ session_id: str = ""
101
+ covers_until_invocation: str = "" # Summary covers up to this invocation
102
+ compressed_message_count: int = 0
103
+
104
+ # Token stats
105
+ token_count: int = 0
106
+
107
+ # Timestamps
108
+ created_at: datetime = field(default_factory=datetime.now)
109
+ updated_at: datetime | None = None
110
+
111
+ # Extension
112
+ metadata: dict[str, Any] = field(default_factory=dict)
113
+
114
+ def to_dict(self) -> dict[str, Any]:
115
+ """Convert to dictionary for serialization."""
116
+ return {
117
+ "id": self.id,
118
+ "content": self.content,
119
+ "session_id": self.session_id,
120
+ "covers_until_invocation": self.covers_until_invocation,
121
+ "compressed_message_count": self.compressed_message_count,
122
+ "token_count": self.token_count,
123
+ "created_at": self.created_at.isoformat(),
124
+ "updated_at": self.updated_at.isoformat() if self.updated_at else None,
125
+ "metadata": self.metadata,
126
+ }
127
+
128
+ @classmethod
129
+ def from_dict(cls, data: dict[str, Any]) -> "Summary":
130
+ """Create from dictionary."""
131
+ return cls(
132
+ id=data["id"],
133
+ content=data.get("content", ""),
134
+ session_id=data.get("session_id", ""),
135
+ covers_until_invocation=data.get("covers_until_invocation", ""),
136
+ compressed_message_count=data.get("compressed_message_count", 0),
137
+ token_count=data.get("token_count", 0),
138
+ created_at=datetime.fromisoformat(data["created_at"]) if "created_at" in data else datetime.now(),
139
+ updated_at=datetime.fromisoformat(data["updated_at"]) if data.get("updated_at") else None,
140
+ metadata=data.get("metadata", {}),
141
+ )
142
+
143
+
144
+ __all__ = ["Recall", "Summary"]