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
aury/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ """Aury - Agent Framework."""
2
+ __path__ = __import__('pkgutil').extend_path(__path__, __name__)
@@ -0,0 +1,55 @@
1
+ """Aury Agent Framework.
2
+
3
+ A framework supporting both React Agent (autonomous loop) and Workflow (DAG orchestration).
4
+
5
+ Package Structure:
6
+ aury.agents.backends - Backend protocols and implementations
7
+ aury.agents.core - Core infrastructure (types, bus, context)
8
+ aury.agents.llm - LLM adapters
9
+ aury.agents.plugin - Middleware system
10
+ aury.agents.memory - Memory system
11
+ aury.agents.react - ReactAgent
12
+ aury.agents.workflow - Workflow orchestration
13
+ aury.agents.tool - Tool system
14
+ aury.agents.skill - Skill system (capability bundles)
15
+ aury.agents.sandbox - Sandbox system (isolated execution)
16
+ aury.agents.cli - CLI
17
+
18
+ Quick Start:
19
+ from aury.agents import ReactAgent, AgentConfig
20
+ from aury.agents.core.types import Session, PromptInput
21
+ from aury.agents.backends import MemoryStateBackend
22
+ from aury.agents.core.event_bus import EventBus, Bus
23
+ from aury.agents.llm import MockLLMProvider
24
+ from aury.agents.tool import ToolSet
25
+ """
26
+
27
+ __version__ = "0.1.0"
28
+
29
+ # Only export the most commonly used classes at top level
30
+ # For other classes, import from submodules directly
31
+ from .core.base import BaseAgent, AgentConfig, ToolInjectionMode
32
+ from .core.event_bus import EventBus, Events
33
+ from .core.context import InvocationContext
34
+ from .core.types import Session, PromptInput, generate_id
35
+ from .react import ReactAgent
36
+ from .workflow import WorkflowAgent
37
+ from .context_providers import ContextProvider, AgentContext
38
+
39
+ __all__ = [
40
+ "__version__",
41
+ "BaseAgent",
42
+ "AgentConfig",
43
+ "ToolInjectionMode",
44
+ "EventBus",
45
+ "Events",
46
+ "InvocationContext",
47
+ "Session",
48
+ "PromptInput",
49
+ "generate_id",
50
+ "ReactAgent",
51
+ "WorkflowAgent",
52
+ # Providers
53
+ "ContextProvider",
54
+ "AgentContext",
55
+ ]
@@ -0,0 +1,168 @@
1
+ """A2A (Agent-to-Agent) communication module.
2
+
3
+ TODO: Implement A2A protocol support for inter-agent communication.
4
+
5
+ This module will provide:
6
+ - A2AClient: Client for calling remote A2A-compliant agents
7
+ - A2AServer: Server to expose agent as A2A endpoint
8
+ - AgentCard: Agent capability declaration
9
+ - AgentSkill: Skill declaration for AgentCard
10
+
11
+ Reference: Google A2A Protocol
12
+ https://github.com/google-a2a/a2a-samples
13
+ """
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import dataclass, field
17
+ from typing import Any, AsyncIterator
18
+
19
+
20
+ # =============================================================================
21
+ # TODO: Agent Card & Skill
22
+ # =============================================================================
23
+
24
+ @dataclass
25
+ class AgentSkill:
26
+ """Skill declaration for AgentCard.
27
+
28
+ TODO: Implement skill definition.
29
+ """
30
+ id: str
31
+ name: str
32
+ description: str
33
+ input_modes: list[str] = field(default_factory=lambda: ["text"])
34
+ output_modes: list[str] = field(default_factory=lambda: ["text"])
35
+ examples: list[str] = field(default_factory=list)
36
+ tags: list[str] = field(default_factory=list)
37
+
38
+
39
+ @dataclass
40
+ class AgentCard:
41
+ """Agent capability declaration.
42
+
43
+ TODO: Implement agent card for A2A discovery.
44
+ """
45
+ name: str
46
+ description: str
47
+ url: str = ""
48
+ version: str = "1.0.0"
49
+ capabilities: dict[str, Any] = field(default_factory=dict)
50
+ skills: list[AgentSkill] = field(default_factory=list)
51
+ default_input_modes: list[str] = field(default_factory=lambda: ["text"])
52
+ default_output_modes: list[str] = field(default_factory=lambda: ["text"])
53
+ authentication: dict[str, Any] = field(default_factory=dict)
54
+
55
+
56
+ # =============================================================================
57
+ # TODO: A2A Client
58
+ # =============================================================================
59
+
60
+ class A2AClient:
61
+ """Client for calling remote A2A-compliant agents.
62
+
63
+ TODO: Implement A2A client.
64
+
65
+ Usage:
66
+ # Discover agent
67
+ agent_card = await A2AClient.discover("http://example.com")
68
+
69
+ # Send task
70
+ client = A2AClient(agent_card)
71
+ result = await client.send_task("Hello")
72
+
73
+ # Stream task
74
+ async for event in client.stream_task("Generate report"):
75
+ print(event)
76
+ """
77
+
78
+ def __init__(self, agent_card: AgentCard):
79
+ self.agent_card = agent_card
80
+ raise NotImplementedError("A2A client is not yet implemented")
81
+
82
+ @classmethod
83
+ async def discover(cls, url: str) -> AgentCard:
84
+ """Discover agent capabilities from URL.
85
+
86
+ TODO: Implement discovery via /.well-known/agent.json
87
+ """
88
+ raise NotImplementedError("A2A discovery is not yet implemented")
89
+
90
+ @classmethod
91
+ def from_url(cls, url: str) -> "A2AClient":
92
+ """Create client from URL (discovers agent card first)."""
93
+ raise NotImplementedError("A2A client is not yet implemented")
94
+
95
+ async def send_task(
96
+ self,
97
+ message: str,
98
+ accepted_output_modes: list[str] | None = None,
99
+ ) -> Any:
100
+ """Send task and wait for result.
101
+
102
+ TODO: Implement synchronous task submission.
103
+ """
104
+ raise NotImplementedError("A2A send_task is not yet implemented")
105
+
106
+ async def stream_task(
107
+ self,
108
+ message: str,
109
+ accepted_output_modes: list[str] | None = None,
110
+ ) -> AsyncIterator[Any]:
111
+ """Send task and stream results.
112
+
113
+ TODO: Implement streaming task submission via SSE.
114
+ """
115
+ raise NotImplementedError("A2A stream_task is not yet implemented")
116
+ yield # Make it a generator
117
+
118
+
119
+ # =============================================================================
120
+ # TODO: A2A Server
121
+ # =============================================================================
122
+
123
+ class A2AServer:
124
+ """Server to expose agent as A2A endpoint.
125
+
126
+ TODO: Implement A2A server.
127
+
128
+ Usage:
129
+ server = A2AServer(
130
+ agent=my_agent,
131
+ agent_card=agent_card,
132
+ host="0.0.0.0",
133
+ port=8080,
134
+ )
135
+ await server.start()
136
+ """
137
+
138
+ def __init__(
139
+ self,
140
+ agent: Any,
141
+ agent_card: AgentCard,
142
+ host: str = "0.0.0.0",
143
+ port: int = 8080,
144
+ ):
145
+ self.agent = agent
146
+ self.agent_card = agent_card
147
+ self.host = host
148
+ self.port = port
149
+ raise NotImplementedError("A2A server is not yet implemented")
150
+
151
+ async def start(self) -> None:
152
+ """Start the A2A server.
153
+
154
+ TODO: Implement HTTP server with A2A protocol handlers.
155
+ """
156
+ raise NotImplementedError("A2A server is not yet implemented")
157
+
158
+ async def stop(self) -> None:
159
+ """Stop the A2A server."""
160
+ raise NotImplementedError("A2A server is not yet implemented")
161
+
162
+
163
+ __all__ = [
164
+ "AgentSkill",
165
+ "AgentCard",
166
+ "A2AClient",
167
+ "A2AServer",
168
+ ]
@@ -0,0 +1,196 @@
1
+ """Backend protocols and implementations.
2
+
3
+ Backends provide abstracted interfaces for various capabilities:
4
+
5
+ Data Backends (storage):
6
+ - SessionBackend: Session management
7
+ - InvocationBackend: Invocation management
8
+ - MessageBackend: Message storage (truncated/raw)
9
+ - MemoryBackend: Long-term memory with search
10
+ - ArtifactBackend: File/artifact storage
11
+ - StateBackend: Generic key-value state
12
+
13
+ Capability Backends:
14
+ - SnapshotBackend: File state tracking and revert
15
+ - ShellBackend: Shell command execution
16
+ - FileBackend: File system operations
17
+ - CodeBackend: Code execution
18
+ - SubAgentBackend: Sub-agent registry and retrieval
19
+
20
+ Backends Container:
21
+ - Backends: Dataclass container for dependency injection
22
+ """
23
+ from dataclasses import dataclass, field
24
+ from typing import TYPE_CHECKING
25
+
26
+ # Data backends - new architecture
27
+ from .session import SessionBackend, InMemorySessionBackend
28
+ from .invocation import InvocationBackend, InMemoryInvocationBackend
29
+ from .message import MessageBackend, MessageType, InMemoryMessageBackend
30
+ from .memory import MemoryBackend, InMemoryMemoryBackend
31
+ from .artifact import ArtifactBackend, ArtifactSource, InMemoryArtifactBackend
32
+
33
+ # State backend - simplified to key-value
34
+ from .state import StateBackend, StateStore, StoreBasedStateBackend, SQLiteStateBackend, MemoryStateBackend, FileStateBackend, CompositeStateBackend
35
+
36
+ # Capability backends - existing
37
+ from .snapshot import SnapshotBackend, Patch, InMemorySnapshotBackend, GitSnapshotBackend, GitS3HybridBackend
38
+ from .shell import ShellBackend, ShellResult, LocalShellBackend
39
+ from .file import FileBackend, LocalFileBackend
40
+ from .code import CodeBackend, CodeResult
41
+ from .subagent import SubAgentBackend, AgentConfig, ListSubAgentBackend
42
+ from .sandbox import SandboxShellBackend, SandboxCodeBackend
43
+
44
+
45
+ @dataclass
46
+ class Backends:
47
+ """Container for all backends.
48
+
49
+ Provides a unified way to inject backends into agents and context.
50
+
51
+ Data backends (for storage):
52
+ - session: Session management (required)
53
+ - invocation: Invocation management (required)
54
+ - message: Message storage (required)
55
+ - memory: Long-term memory (optional)
56
+ - artifact: Artifact storage (optional)
57
+ - state: Generic key-value state (optional)
58
+
59
+ Capability backends (for actions):
60
+ - snapshot: File tracking and revert (optional)
61
+ - shell: Shell execution (optional)
62
+ - file: File operations (optional)
63
+ - code: Code execution (optional)
64
+ - subagent: Sub-agent registry (optional)
65
+
66
+ Example:
67
+ # Create with defaults
68
+ backends = Backends.create_default()
69
+
70
+ # Create with custom implementations
71
+ backends = Backends(
72
+ session=DatabaseSessionBackend(db),
73
+ invocation=DatabaseInvocationBackend(db),
74
+ message=DatabaseMessageBackend(db),
75
+ memory=VectorMemoryBackend(embedding_model),
76
+ )
77
+
78
+ # Use in agent
79
+ agent = ReactAgent.create(llm=llm, backends=backends)
80
+ """
81
+ # Data backends - required
82
+ session: SessionBackend
83
+ invocation: InvocationBackend
84
+ message: MessageBackend
85
+
86
+ # Data backends - optional
87
+ memory: MemoryBackend | None = None
88
+ artifact: ArtifactBackend | None = None
89
+ state: StateBackend | None = None
90
+
91
+ # Capability backends - optional
92
+ snapshot: SnapshotBackend | None = None
93
+ shell: ShellBackend | None = None
94
+ file: FileBackend | None = None
95
+ code: CodeBackend | None = None
96
+ subagent: SubAgentBackend | None = None
97
+
98
+ @classmethod
99
+ def create_default(cls) -> "Backends":
100
+ """Create Backends with default in-memory implementations.
101
+
102
+ Suitable for testing and simple single-process use cases.
103
+ """
104
+ return cls(
105
+ session=InMemorySessionBackend(),
106
+ invocation=InMemoryInvocationBackend(),
107
+ message=InMemoryMessageBackend(),
108
+ memory=InMemoryMemoryBackend(),
109
+ artifact=InMemoryArtifactBackend(),
110
+ state=MemoryStateBackend(),
111
+ )
112
+
113
+ @classmethod
114
+ def create_sqlite(cls, db_path: str = "./data/agent.db") -> "Backends":
115
+ """Create Backends with SQLite storage.
116
+
117
+ Uses SQLite for state backend, in-memory for others.
118
+ For production, implement database backends.
119
+
120
+ Args:
121
+ db_path: Path to SQLite database file
122
+ """
123
+ return cls(
124
+ session=InMemorySessionBackend(),
125
+ invocation=InMemoryInvocationBackend(),
126
+ message=InMemoryMessageBackend(),
127
+ memory=InMemoryMemoryBackend(),
128
+ artifact=InMemoryArtifactBackend(),
129
+ state=SQLiteStateBackend(db_path),
130
+ )
131
+
132
+
133
+ __all__ = [
134
+ # Backends container
135
+ "Backends",
136
+
137
+ # Session backend
138
+ "SessionBackend",
139
+ "InMemorySessionBackend",
140
+
141
+ # Invocation backend
142
+ "InvocationBackend",
143
+ "InMemoryInvocationBackend",
144
+
145
+ # Message backend
146
+ "MessageBackend",
147
+ "MessageType",
148
+ "InMemoryMessageBackend",
149
+
150
+ # Memory backend
151
+ "MemoryBackend",
152
+ "InMemoryMemoryBackend",
153
+
154
+ # Artifact backend
155
+ "ArtifactBackend",
156
+ "ArtifactSource",
157
+ "InMemoryArtifactBackend",
158
+
159
+ # State backend (key-value)
160
+ "StateBackend",
161
+ "StateStore",
162
+ "StoreBasedStateBackend",
163
+ "SQLiteStateBackend",
164
+ "MemoryStateBackend",
165
+ "FileStateBackend",
166
+ "CompositeStateBackend",
167
+
168
+ # Snapshot backend
169
+ "SnapshotBackend",
170
+ "Patch",
171
+ "InMemorySnapshotBackend",
172
+ "GitSnapshotBackend",
173
+ "GitS3HybridBackend",
174
+
175
+ # Shell backend
176
+ "ShellBackend",
177
+ "ShellResult",
178
+ "LocalShellBackend",
179
+
180
+ # File backend
181
+ "FileBackend",
182
+ "LocalFileBackend",
183
+
184
+ # Code backend
185
+ "CodeBackend",
186
+ "CodeResult",
187
+
188
+ # SubAgent backend
189
+ "SubAgentBackend",
190
+ "AgentConfig",
191
+ "ListSubAgentBackend",
192
+
193
+ # Sandbox backends
194
+ "SandboxShellBackend",
195
+ "SandboxCodeBackend",
196
+ ]
@@ -0,0 +1,9 @@
1
+ """Artifact backend."""
2
+ from .types import ArtifactBackend, ArtifactSource
3
+ from .memory import InMemoryArtifactBackend
4
+
5
+ __all__ = [
6
+ "ArtifactBackend",
7
+ "ArtifactSource",
8
+ "InMemoryArtifactBackend",
9
+ ]
@@ -0,0 +1,130 @@
1
+ """In-memory artifact backend implementation."""
2
+ from __future__ import annotations
3
+
4
+ import uuid
5
+ from datetime import datetime
6
+ from typing import Any
7
+
8
+ from .types import ArtifactSource
9
+
10
+
11
+ class InMemoryArtifactBackend:
12
+ """In-memory implementation of ArtifactBackend.
13
+
14
+ Stores artifact metadata in memory.
15
+ Suitable for testing and simple use cases.
16
+ """
17
+
18
+ def __init__(self) -> None:
19
+ self._artifacts: dict[str, dict[str, Any]] = {}
20
+ self._session_artifacts: dict[str, list[str]] = {}
21
+
22
+ def _make_key(self, session_id: str, namespace: str | None) -> str:
23
+ if namespace:
24
+ return f"{session_id}:{namespace}"
25
+ return session_id
26
+
27
+ async def save(
28
+ self,
29
+ session_id: str,
30
+ name: str,
31
+ path: str,
32
+ source: ArtifactSource | str | None = None,
33
+ mime_type: str | None = None,
34
+ size: int | None = None,
35
+ invocation_id: str | None = None,
36
+ namespace: str | None = None,
37
+ metadata: dict[str, Any] | None = None,
38
+ ) -> str:
39
+ """Save an artifact reference."""
40
+ artifact_id = f"art_{uuid.uuid4().hex[:12]}"
41
+ key = self._make_key(session_id, namespace)
42
+
43
+ artifact = {
44
+ "id": artifact_id,
45
+ "session_id": session_id,
46
+ "name": name,
47
+ "path": path,
48
+ "source": source,
49
+ "mime_type": mime_type,
50
+ "size": size,
51
+ "invocation_id": invocation_id,
52
+ "namespace": namespace,
53
+ "metadata": metadata or {},
54
+ "created_at": datetime.now().isoformat(),
55
+ }
56
+
57
+ self._artifacts[artifact_id] = artifact
58
+
59
+ if key not in self._session_artifacts:
60
+ self._session_artifacts[key] = []
61
+ self._session_artifacts[key].append(artifact_id)
62
+
63
+ return artifact_id
64
+
65
+ async def get(self, id: str) -> dict[str, Any] | None:
66
+ """Get artifact by ID."""
67
+ return self._artifacts.get(id)
68
+
69
+ async def delete(self, id: str) -> bool:
70
+ """Delete an artifact reference."""
71
+ if id not in self._artifacts:
72
+ return False
73
+
74
+ artifact = self._artifacts.pop(id)
75
+ session_id = artifact["session_id"]
76
+ namespace = artifact.get("namespace")
77
+ key = self._make_key(session_id, namespace)
78
+
79
+ if key in self._session_artifacts:
80
+ self._session_artifacts[key] = [
81
+ aid for aid in self._session_artifacts[key] if aid != id
82
+ ]
83
+
84
+ return True
85
+
86
+ async def delete_by_invocation(
87
+ self,
88
+ session_id: str,
89
+ invocation_id: str,
90
+ namespace: str | None = None,
91
+ ) -> int:
92
+ """Delete artifacts by invocation."""
93
+ key = self._make_key(session_id, namespace)
94
+ artifact_ids = self._session_artifacts.get(key, [])
95
+
96
+ to_delete = [
97
+ aid for aid in artifact_ids
98
+ if self._artifacts.get(aid, {}).get("invocation_id") == invocation_id
99
+ ]
100
+
101
+ for aid in to_delete:
102
+ await self.delete(aid)
103
+
104
+ return len(to_delete)
105
+
106
+ async def list_by_session(
107
+ self,
108
+ session_id: str,
109
+ namespace: str | None = None,
110
+ source: ArtifactSource | str | None = None,
111
+ limit: int = 100,
112
+ ) -> list[dict[str, Any]]:
113
+ """List artifacts for a session."""
114
+ key = self._make_key(session_id, namespace)
115
+ artifact_ids = self._session_artifacts.get(key, [])
116
+
117
+ results = []
118
+ for aid in artifact_ids:
119
+ artifact = self._artifacts.get(aid)
120
+ if artifact:
121
+ # Filter by source if specified
122
+ if source and artifact.get("source") != source:
123
+ continue
124
+ results.append(artifact)
125
+
126
+ results.sort(key=lambda x: x.get("created_at", ""), reverse=True)
127
+ return results[:limit]
128
+
129
+
130
+ __all__ = ["InMemoryArtifactBackend"]
@@ -0,0 +1,133 @@
1
+ """Artifact backend types and protocols."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, Literal, Protocol, runtime_checkable
5
+
6
+
7
+ ArtifactSource = Literal["tool", "agent", "user", "system"]
8
+
9
+
10
+ @runtime_checkable
11
+ class ArtifactBackend(Protocol):
12
+ """Protocol for artifact storage.
13
+
14
+ Artifacts are file references produced during agent execution,
15
+ such as generated images, documents, code files, etc.
16
+
17
+ Note: This stores file metadata and path references, not binary data.
18
+ Actual file storage is handled by the file system or object storage.
19
+
20
+ Example usage:
21
+ # Save artifact reference
22
+ artifact_id = await backend.save(
23
+ session_id="sess_123",
24
+ name="report.pdf",
25
+ path="/uploads/sess_123/report.pdf",
26
+ source="tool",
27
+ mime_type="application/pdf",
28
+ invocation_id="inv_456",
29
+ )
30
+
31
+ # Get artifact metadata
32
+ artifact = await backend.get(artifact_id)
33
+
34
+ # List artifacts
35
+ artifacts = await backend.list_by_session("sess_123")
36
+ """
37
+
38
+ async def save(
39
+ self,
40
+ session_id: str,
41
+ name: str,
42
+ path: str,
43
+ source: ArtifactSource | str | None = None,
44
+ mime_type: str | None = None,
45
+ size: int | None = None,
46
+ invocation_id: str | None = None,
47
+ namespace: str | None = None,
48
+ metadata: dict[str, Any] | None = None,
49
+ ) -> str:
50
+ """Save an artifact reference.
51
+
52
+ Args:
53
+ session_id: Session ID
54
+ name: Display name / filename
55
+ path: File path or URL (storage location)
56
+ source: Origin of artifact (tool, agent, user, system)
57
+ mime_type: MIME type (e.g., "image/png")
58
+ size: File size in bytes
59
+ invocation_id: Optional invocation ID for grouping
60
+ namespace: Optional namespace for isolation
61
+ metadata: Optional additional metadata
62
+
63
+ Returns:
64
+ Generated artifact ID
65
+ """
66
+ ...
67
+
68
+ async def get(self, id: str) -> dict[str, Any] | None:
69
+ """Get artifact by ID.
70
+
71
+ Args:
72
+ id: Artifact ID
73
+
74
+ Returns:
75
+ Artifact dict or None if not found:
76
+ {"id": str, "name": str, "path": str, "source": str, ...}
77
+ """
78
+ ...
79
+
80
+ async def delete(self, id: str) -> bool:
81
+ """Delete an artifact reference.
82
+
83
+ Note: This only deletes the metadata, not the actual file.
84
+ File cleanup should be handled separately.
85
+
86
+ Args:
87
+ id: Artifact ID
88
+
89
+ Returns:
90
+ True if deleted, False if not found
91
+ """
92
+ ...
93
+
94
+ async def delete_by_invocation(
95
+ self,
96
+ session_id: str,
97
+ invocation_id: str,
98
+ namespace: str | None = None,
99
+ ) -> int:
100
+ """Delete artifacts by invocation (for revert).
101
+
102
+ Args:
103
+ session_id: Session ID
104
+ invocation_id: Invocation ID to delete
105
+ namespace: Optional namespace filter
106
+
107
+ Returns:
108
+ Number of artifacts deleted
109
+ """
110
+ ...
111
+
112
+ async def list_by_session(
113
+ self,
114
+ session_id: str,
115
+ namespace: str | None = None,
116
+ source: ArtifactSource | str | None = None,
117
+ limit: int = 100,
118
+ ) -> list[dict[str, Any]]:
119
+ """List artifacts for a session.
120
+
121
+ Args:
122
+ session_id: Session ID
123
+ namespace: Optional namespace filter
124
+ source: Optional filter by source
125
+ limit: Max results
126
+
127
+ Returns:
128
+ List of artifact dicts
129
+ """
130
+ ...
131
+
132
+
133
+ __all__ = ["ArtifactBackend", "ArtifactSource"]