agentpool 2.1.9__py3-none-any.whl → 2.5.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.
Files changed (311) hide show
  1. acp/__init__.py +13 -4
  2. acp/acp_requests.py +20 -77
  3. acp/agent/connection.py +8 -0
  4. acp/agent/implementations/debug_server/debug_server.py +6 -2
  5. acp/agent/protocol.py +6 -0
  6. acp/bridge/README.md +15 -2
  7. acp/bridge/__init__.py +3 -2
  8. acp/bridge/__main__.py +60 -19
  9. acp/bridge/ws_server.py +173 -0
  10. acp/bridge/ws_server_cli.py +89 -0
  11. acp/client/connection.py +38 -29
  12. acp/client/implementations/default_client.py +3 -2
  13. acp/client/implementations/headless_client.py +2 -2
  14. acp/connection.py +2 -2
  15. acp/notifications.py +20 -50
  16. acp/schema/__init__.py +2 -0
  17. acp/schema/agent_responses.py +21 -0
  18. acp/schema/client_requests.py +3 -3
  19. acp/schema/session_state.py +63 -29
  20. acp/stdio.py +39 -9
  21. acp/task/supervisor.py +2 -2
  22. acp/transports.py +362 -2
  23. acp/utils.py +17 -4
  24. agentpool/__init__.py +6 -1
  25. agentpool/agents/__init__.py +2 -0
  26. agentpool/agents/acp_agent/acp_agent.py +407 -277
  27. agentpool/agents/acp_agent/acp_converters.py +196 -38
  28. agentpool/agents/acp_agent/client_handler.py +191 -26
  29. agentpool/agents/acp_agent/session_state.py +17 -6
  30. agentpool/agents/agent.py +607 -572
  31. agentpool/agents/agui_agent/__init__.py +0 -2
  32. agentpool/agents/agui_agent/agui_agent.py +176 -110
  33. agentpool/agents/agui_agent/agui_converters.py +0 -131
  34. agentpool/agents/agui_agent/helpers.py +3 -4
  35. agentpool/agents/base_agent.py +632 -17
  36. agentpool/agents/claude_code_agent/FORKING.md +191 -0
  37. agentpool/agents/claude_code_agent/__init__.py +13 -1
  38. agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
  39. agentpool/agents/claude_code_agent/converters.py +74 -143
  40. agentpool/agents/claude_code_agent/history.py +474 -0
  41. agentpool/agents/claude_code_agent/models.py +77 -0
  42. agentpool/agents/claude_code_agent/static_info.py +100 -0
  43. agentpool/agents/claude_code_agent/usage.py +242 -0
  44. agentpool/agents/context.py +40 -0
  45. agentpool/agents/events/__init__.py +24 -0
  46. agentpool/agents/events/builtin_handlers.py +67 -1
  47. agentpool/agents/events/event_emitter.py +32 -2
  48. agentpool/agents/events/events.py +104 -3
  49. agentpool/agents/events/infer_info.py +145 -0
  50. agentpool/agents/events/processors.py +254 -0
  51. agentpool/agents/interactions.py +41 -6
  52. agentpool/agents/modes.py +67 -0
  53. agentpool/agents/slashed_agent.py +5 -4
  54. agentpool/agents/tool_call_accumulator.py +213 -0
  55. agentpool/agents/tool_wrapping.py +18 -6
  56. agentpool/common_types.py +56 -21
  57. agentpool/config_resources/__init__.py +38 -1
  58. agentpool/config_resources/acp_assistant.yml +2 -2
  59. agentpool/config_resources/agents.yml +3 -0
  60. agentpool/config_resources/agents_template.yml +1 -0
  61. agentpool/config_resources/claude_code_agent.yml +10 -6
  62. agentpool/config_resources/external_acp_agents.yml +2 -1
  63. agentpool/delegation/base_team.py +4 -30
  64. agentpool/delegation/pool.py +136 -289
  65. agentpool/delegation/team.py +58 -57
  66. agentpool/delegation/teamrun.py +51 -55
  67. agentpool/diagnostics/__init__.py +53 -0
  68. agentpool/diagnostics/lsp_manager.py +1593 -0
  69. agentpool/diagnostics/lsp_proxy.py +41 -0
  70. agentpool/diagnostics/lsp_proxy_script.py +229 -0
  71. agentpool/diagnostics/models.py +398 -0
  72. agentpool/functional/run.py +10 -4
  73. agentpool/mcp_server/__init__.py +0 -2
  74. agentpool/mcp_server/client.py +76 -32
  75. agentpool/mcp_server/conversions.py +54 -13
  76. agentpool/mcp_server/manager.py +34 -54
  77. agentpool/mcp_server/registries/official_registry_client.py +35 -1
  78. agentpool/mcp_server/tool_bridge.py +186 -139
  79. agentpool/messaging/__init__.py +0 -2
  80. agentpool/messaging/compaction.py +72 -197
  81. agentpool/messaging/connection_manager.py +11 -10
  82. agentpool/messaging/event_manager.py +5 -5
  83. agentpool/messaging/message_container.py +6 -30
  84. agentpool/messaging/message_history.py +99 -8
  85. agentpool/messaging/messagenode.py +52 -14
  86. agentpool/messaging/messages.py +54 -35
  87. agentpool/messaging/processing.py +12 -22
  88. agentpool/models/__init__.py +1 -1
  89. agentpool/models/acp_agents/base.py +6 -24
  90. agentpool/models/acp_agents/mcp_capable.py +126 -157
  91. agentpool/models/acp_agents/non_mcp.py +129 -95
  92. agentpool/models/agents.py +98 -76
  93. agentpool/models/agui_agents.py +1 -1
  94. agentpool/models/claude_code_agents.py +144 -19
  95. agentpool/models/file_parsing.py +0 -1
  96. agentpool/models/manifest.py +113 -50
  97. agentpool/prompts/conversion_manager.py +1 -1
  98. agentpool/prompts/prompts.py +5 -2
  99. agentpool/repomap.py +1 -1
  100. agentpool/resource_providers/__init__.py +11 -1
  101. agentpool/resource_providers/aggregating.py +56 -5
  102. agentpool/resource_providers/base.py +70 -4
  103. agentpool/resource_providers/codemode/code_executor.py +72 -5
  104. agentpool/resource_providers/codemode/helpers.py +2 -2
  105. agentpool/resource_providers/codemode/provider.py +64 -12
  106. agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
  107. agentpool/resource_providers/codemode/remote_provider.py +9 -12
  108. agentpool/resource_providers/filtering.py +3 -1
  109. agentpool/resource_providers/mcp_provider.py +89 -12
  110. agentpool/resource_providers/plan_provider.py +228 -46
  111. agentpool/resource_providers/pool.py +7 -3
  112. agentpool/resource_providers/resource_info.py +111 -0
  113. agentpool/resource_providers/static.py +4 -2
  114. agentpool/sessions/__init__.py +4 -1
  115. agentpool/sessions/manager.py +33 -5
  116. agentpool/sessions/models.py +59 -6
  117. agentpool/sessions/protocol.py +28 -0
  118. agentpool/sessions/session.py +11 -55
  119. agentpool/skills/registry.py +13 -8
  120. agentpool/storage/manager.py +572 -49
  121. agentpool/talk/registry.py +4 -4
  122. agentpool/talk/talk.py +9 -10
  123. agentpool/testing.py +538 -20
  124. agentpool/tool_impls/__init__.py +6 -0
  125. agentpool/tool_impls/agent_cli/__init__.py +42 -0
  126. agentpool/tool_impls/agent_cli/tool.py +95 -0
  127. agentpool/tool_impls/bash/__init__.py +64 -0
  128. agentpool/tool_impls/bash/helpers.py +35 -0
  129. agentpool/tool_impls/bash/tool.py +171 -0
  130. agentpool/tool_impls/delete_path/__init__.py +70 -0
  131. agentpool/tool_impls/delete_path/tool.py +142 -0
  132. agentpool/tool_impls/download_file/__init__.py +80 -0
  133. agentpool/tool_impls/download_file/tool.py +183 -0
  134. agentpool/tool_impls/execute_code/__init__.py +55 -0
  135. agentpool/tool_impls/execute_code/tool.py +163 -0
  136. agentpool/tool_impls/grep/__init__.py +80 -0
  137. agentpool/tool_impls/grep/tool.py +200 -0
  138. agentpool/tool_impls/list_directory/__init__.py +73 -0
  139. agentpool/tool_impls/list_directory/tool.py +197 -0
  140. agentpool/tool_impls/question/__init__.py +42 -0
  141. agentpool/tool_impls/question/tool.py +127 -0
  142. agentpool/tool_impls/read/__init__.py +104 -0
  143. agentpool/tool_impls/read/tool.py +305 -0
  144. agentpool/tools/__init__.py +2 -1
  145. agentpool/tools/base.py +114 -34
  146. agentpool/tools/manager.py +57 -1
  147. agentpool/ui/base.py +2 -2
  148. agentpool/ui/mock_provider.py +2 -2
  149. agentpool/ui/stdlib_provider.py +2 -2
  150. agentpool/utils/file_watcher.py +269 -0
  151. agentpool/utils/identifiers.py +121 -0
  152. agentpool/utils/pydantic_ai_helpers.py +46 -0
  153. agentpool/utils/streams.py +616 -2
  154. agentpool/utils/subprocess_utils.py +155 -0
  155. agentpool/utils/token_breakdown.py +461 -0
  156. agentpool/vfs_registry.py +7 -2
  157. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
  158. agentpool-2.5.0.dist-info/RECORD +579 -0
  159. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
  160. agentpool_cli/__main__.py +24 -0
  161. agentpool_cli/create.py +1 -1
  162. agentpool_cli/serve_acp.py +100 -21
  163. agentpool_cli/serve_agui.py +87 -0
  164. agentpool_cli/serve_opencode.py +119 -0
  165. agentpool_cli/ui.py +557 -0
  166. agentpool_commands/__init__.py +42 -5
  167. agentpool_commands/agents.py +75 -2
  168. agentpool_commands/history.py +62 -0
  169. agentpool_commands/mcp.py +176 -0
  170. agentpool_commands/models.py +56 -3
  171. agentpool_commands/pool.py +260 -0
  172. agentpool_commands/session.py +1 -1
  173. agentpool_commands/text_sharing/__init__.py +119 -0
  174. agentpool_commands/text_sharing/base.py +123 -0
  175. agentpool_commands/text_sharing/github_gist.py +80 -0
  176. agentpool_commands/text_sharing/opencode.py +462 -0
  177. agentpool_commands/text_sharing/paste_rs.py +59 -0
  178. agentpool_commands/text_sharing/pastebin.py +116 -0
  179. agentpool_commands/text_sharing/shittycodingagent.py +112 -0
  180. agentpool_commands/tools.py +57 -0
  181. agentpool_commands/utils.py +80 -30
  182. agentpool_config/__init__.py +30 -2
  183. agentpool_config/agentpool_tools.py +498 -0
  184. agentpool_config/builtin_tools.py +77 -22
  185. agentpool_config/commands.py +24 -1
  186. agentpool_config/compaction.py +258 -0
  187. agentpool_config/converters.py +1 -1
  188. agentpool_config/event_handlers.py +42 -0
  189. agentpool_config/events.py +1 -1
  190. agentpool_config/forward_targets.py +1 -4
  191. agentpool_config/jinja.py +3 -3
  192. agentpool_config/mcp_server.py +132 -6
  193. agentpool_config/nodes.py +1 -1
  194. agentpool_config/observability.py +44 -0
  195. agentpool_config/session.py +0 -3
  196. agentpool_config/storage.py +82 -38
  197. agentpool_config/task.py +3 -3
  198. agentpool_config/tools.py +11 -22
  199. agentpool_config/toolsets.py +109 -233
  200. agentpool_server/a2a_server/agent_worker.py +307 -0
  201. agentpool_server/a2a_server/server.py +23 -18
  202. agentpool_server/acp_server/acp_agent.py +234 -181
  203. agentpool_server/acp_server/commands/acp_commands.py +151 -156
  204. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
  205. agentpool_server/acp_server/event_converter.py +651 -0
  206. agentpool_server/acp_server/input_provider.py +53 -10
  207. agentpool_server/acp_server/server.py +24 -90
  208. agentpool_server/acp_server/session.py +173 -331
  209. agentpool_server/acp_server/session_manager.py +8 -34
  210. agentpool_server/agui_server/server.py +3 -1
  211. agentpool_server/mcp_server/server.py +5 -2
  212. agentpool_server/opencode_server/.rules +95 -0
  213. agentpool_server/opencode_server/ENDPOINTS.md +401 -0
  214. agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
  215. agentpool_server/opencode_server/__init__.py +19 -0
  216. agentpool_server/opencode_server/command_validation.py +172 -0
  217. agentpool_server/opencode_server/converters.py +975 -0
  218. agentpool_server/opencode_server/dependencies.py +24 -0
  219. agentpool_server/opencode_server/input_provider.py +421 -0
  220. agentpool_server/opencode_server/models/__init__.py +250 -0
  221. agentpool_server/opencode_server/models/agent.py +53 -0
  222. agentpool_server/opencode_server/models/app.py +72 -0
  223. agentpool_server/opencode_server/models/base.py +26 -0
  224. agentpool_server/opencode_server/models/common.py +23 -0
  225. agentpool_server/opencode_server/models/config.py +37 -0
  226. agentpool_server/opencode_server/models/events.py +821 -0
  227. agentpool_server/opencode_server/models/file.py +88 -0
  228. agentpool_server/opencode_server/models/mcp.py +44 -0
  229. agentpool_server/opencode_server/models/message.py +179 -0
  230. agentpool_server/opencode_server/models/parts.py +323 -0
  231. agentpool_server/opencode_server/models/provider.py +81 -0
  232. agentpool_server/opencode_server/models/pty.py +43 -0
  233. agentpool_server/opencode_server/models/question.py +56 -0
  234. agentpool_server/opencode_server/models/session.py +111 -0
  235. agentpool_server/opencode_server/routes/__init__.py +29 -0
  236. agentpool_server/opencode_server/routes/agent_routes.py +473 -0
  237. agentpool_server/opencode_server/routes/app_routes.py +202 -0
  238. agentpool_server/opencode_server/routes/config_routes.py +302 -0
  239. agentpool_server/opencode_server/routes/file_routes.py +571 -0
  240. agentpool_server/opencode_server/routes/global_routes.py +94 -0
  241. agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
  242. agentpool_server/opencode_server/routes/message_routes.py +761 -0
  243. agentpool_server/opencode_server/routes/permission_routes.py +63 -0
  244. agentpool_server/opencode_server/routes/pty_routes.py +300 -0
  245. agentpool_server/opencode_server/routes/question_routes.py +128 -0
  246. agentpool_server/opencode_server/routes/session_routes.py +1276 -0
  247. agentpool_server/opencode_server/routes/tui_routes.py +139 -0
  248. agentpool_server/opencode_server/server.py +475 -0
  249. agentpool_server/opencode_server/state.py +151 -0
  250. agentpool_server/opencode_server/time_utils.py +8 -0
  251. agentpool_storage/__init__.py +12 -0
  252. agentpool_storage/base.py +184 -2
  253. agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
  254. agentpool_storage/claude_provider/__init__.py +42 -0
  255. agentpool_storage/claude_provider/provider.py +1089 -0
  256. agentpool_storage/file_provider.py +278 -15
  257. agentpool_storage/memory_provider.py +193 -12
  258. agentpool_storage/models.py +3 -0
  259. agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
  260. agentpool_storage/opencode_provider/__init__.py +16 -0
  261. agentpool_storage/opencode_provider/helpers.py +414 -0
  262. agentpool_storage/opencode_provider/provider.py +895 -0
  263. agentpool_storage/project_store.py +325 -0
  264. agentpool_storage/session_store.py +26 -6
  265. agentpool_storage/sql_provider/__init__.py +4 -2
  266. agentpool_storage/sql_provider/models.py +48 -0
  267. agentpool_storage/sql_provider/sql_provider.py +269 -3
  268. agentpool_storage/sql_provider/utils.py +12 -13
  269. agentpool_storage/zed_provider/__init__.py +16 -0
  270. agentpool_storage/zed_provider/helpers.py +281 -0
  271. agentpool_storage/zed_provider/models.py +130 -0
  272. agentpool_storage/zed_provider/provider.py +442 -0
  273. agentpool_storage/zed_provider.py +803 -0
  274. agentpool_toolsets/__init__.py +0 -2
  275. agentpool_toolsets/builtin/__init__.py +2 -12
  276. agentpool_toolsets/builtin/code.py +96 -57
  277. agentpool_toolsets/builtin/debug.py +118 -48
  278. agentpool_toolsets/builtin/execution_environment.py +115 -230
  279. agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
  280. agentpool_toolsets/builtin/skills.py +9 -4
  281. agentpool_toolsets/builtin/subagent_tools.py +64 -51
  282. agentpool_toolsets/builtin/workers.py +4 -2
  283. agentpool_toolsets/composio_toolset.py +2 -2
  284. agentpool_toolsets/entry_points.py +3 -1
  285. agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
  286. agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
  287. agentpool_toolsets/fsspec_toolset/grep.py +99 -7
  288. agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
  289. agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
  290. agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
  291. agentpool_toolsets/mcp_discovery/__init__.py +5 -0
  292. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  293. agentpool_toolsets/mcp_discovery/toolset.py +511 -0
  294. agentpool_toolsets/mcp_run_toolset.py +87 -12
  295. agentpool_toolsets/notifications.py +33 -33
  296. agentpool_toolsets/openapi.py +3 -1
  297. agentpool_toolsets/search_toolset.py +3 -1
  298. agentpool-2.1.9.dist-info/RECORD +0 -474
  299. agentpool_config/resources.py +0 -33
  300. agentpool_server/acp_server/acp_tools.py +0 -43
  301. agentpool_server/acp_server/commands/spawn.py +0 -210
  302. agentpool_storage/text_log_provider.py +0 -275
  303. agentpool_toolsets/builtin/agent_management.py +0 -239
  304. agentpool_toolsets/builtin/chain.py +0 -288
  305. agentpool_toolsets/builtin/history.py +0 -36
  306. agentpool_toolsets/builtin/integration.py +0 -85
  307. agentpool_toolsets/builtin/tool_management.py +0 -90
  308. agentpool_toolsets/builtin/user_interaction.py +0 -52
  309. agentpool_toolsets/semantic_memory_toolset.py +0 -536
  310. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
  311. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,90 +0,0 @@
1
- """Provider for tool management tools."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import TYPE_CHECKING, Any
6
-
7
- from agentpool.agents.context import AgentContext # noqa: TC001
8
- from agentpool.resource_providers import StaticResourceProvider
9
- from agentpool.tools.base import Tool
10
-
11
-
12
- if TYPE_CHECKING:
13
- from collections.abc import Callable
14
-
15
-
16
- async def register_tool( # noqa: D417
17
- ctx: AgentContext,
18
- tool: str | Callable[..., Any],
19
- name: str | None = None,
20
- description: str | None = None,
21
- enabled: bool = True,
22
- ) -> str:
23
- """Register a new tool from callable or import path.
24
-
25
- Args:
26
- tool: Callable function or import path string to register as tool
27
- name: Optional name override for the tool
28
- description: Optional description override for the tool
29
- enabled: Whether the tool should be enabled initially
30
-
31
- Returns:
32
- Confirmation message with registered tool name
33
- """
34
- # Create tool from callable/import path
35
- tool_obj = Tool.from_callable(
36
- tool,
37
- name_override=name,
38
- description_override=description,
39
- source="dynamic",
40
- enabled=enabled,
41
- )
42
-
43
- # Register with the agent's tool manager
44
- registered_tool = ctx.agent.tools.register_tool(tool_obj)
45
-
46
- return f"Successfully registered tool: {registered_tool.name}"
47
-
48
-
49
- async def register_code_tool( # noqa: D417
50
- ctx: AgentContext,
51
- code: str,
52
- name: str | None = None,
53
- description: str | None = None,
54
- enabled: bool = True,
55
- ) -> str:
56
- """Register a new tool from code string.
57
-
58
- Args:
59
- code: Python code string containing a callable function
60
- name: Optional name override for the tool
61
- description: Optional description override for the tool
62
- enabled: Whether the tool should be enabled initially
63
-
64
- Returns:
65
- Confirmation message with registered tool name
66
- """
67
- # Create tool from code
68
- tool_obj = Tool.from_code(
69
- code,
70
- name=name,
71
- description=description,
72
- )
73
- tool_obj.enabled = enabled
74
- tool_obj.source = "dynamic"
75
-
76
- # Register with the agent's tool manager
77
- registered_tool = ctx.agent.tools.register_tool(tool_obj)
78
-
79
- return f"Successfully registered code tool: {registered_tool.name}"
80
-
81
-
82
- class ToolManagementTools(StaticResourceProvider):
83
- """Provider for tool management tools."""
84
-
85
- def __init__(self, name: str = "tool_management") -> None:
86
- super().__init__(name=name)
87
- self._tools = [
88
- self.create_tool(register_tool, category="other"),
89
- self.create_tool(register_code_tool, category="other"),
90
- ]
@@ -1,52 +0,0 @@
1
- """Provider for user interaction tools."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import Any, assert_never
6
-
7
- from agentpool.agents.context import AgentContext # noqa: TC001
8
- from agentpool.resource_providers import StaticResourceProvider
9
-
10
-
11
- async def ask_user( # noqa: D417
12
- ctx: AgentContext,
13
- prompt: str,
14
- response_schema: dict[str, Any] | None = None,
15
- ) -> str:
16
- """Allow LLM to ask user a clarifying question during processing.
17
-
18
- This tool enables agents to ask users for additional information or clarification
19
- when needed to complete a task effectively.
20
-
21
- Args:
22
- prompt: Question to ask the user
23
- response_schema: Optional JSON schema for structured response (defaults to string)
24
-
25
- Returns:
26
- The user's response as a string
27
- """
28
- from mcp.types import ElicitRequestFormParams, ElicitResult, ErrorData
29
-
30
- schema = response_schema or {"type": "string"} # string schema if no none provided
31
- params = ElicitRequestFormParams(message=prompt, requestedSchema=schema)
32
- result = await ctx.handle_elicitation(params)
33
-
34
- match result:
35
- case ElicitResult(action="accept", content=content):
36
- return str(content)
37
- case ElicitResult(action="cancel"):
38
- return "User cancelled the request"
39
- case ElicitResult():
40
- return "User declined to answer"
41
- case ErrorData(message=message):
42
- return f"Error: {message}"
43
- case _ as unreachable:
44
- assert_never(unreachable)
45
-
46
-
47
- class UserInteractionTools(StaticResourceProvider):
48
- """Provider for user interaction tools."""
49
-
50
- def __init__(self, name: str = "user_interaction") -> None:
51
- super().__init__(name=name)
52
- self._tools = [self.create_tool(ask_user, category="other", open_world=True)]
@@ -1,536 +0,0 @@
1
- """Semantic memory toolset using TypeAgent's KnowPro for knowledge processing."""
2
-
3
- from __future__ import annotations
4
-
5
- from dataclasses import dataclass
6
- from datetime import UTC, datetime
7
- import os
8
- import re
9
- import time
10
- from typing import TYPE_CHECKING, Any, Literal, Self
11
-
12
- from agentpool.resource_providers import ResourceProvider
13
-
14
-
15
- if TYPE_CHECKING:
16
- from types import TracebackType
17
-
18
- from typeagent.knowpro import answers, query, searchlang
19
- from typeagent.knowpro.answer_response_schema import AnswerResponse
20
- from typeagent.knowpro.conversation_base import ConversationBase
21
- from typeagent.knowpro.search_query_schema import SearchQuery
22
- from typeagent.podcasts import podcast
23
- from typeagent.storage.memory.semrefindex import TermToSemanticRefIndex
24
- import typechat
25
-
26
- from agentpool.agents import Agent
27
- from agentpool.agents.acp_agent import ACPAgent
28
- from agentpool.common_types import ModelType
29
- from agentpool.tools.base import Tool
30
-
31
-
32
- class AgentTypeChatModel:
33
- """TypeChat language model backed by agentpool Agent.
34
-
35
- Implements the typechat.TypeChatLanguageModel protocol using an Agent
36
- for LLM completions instead of direct API calls.
37
- """
38
-
39
- def __init__(self, agent: Agent[Any, str] | ACPAgent) -> None:
40
- """Initialize with an Agent for completions.
41
-
42
- Args:
43
- agent: The agentpool Agent to use for LLM calls
44
- """
45
- self.agent = agent
46
-
47
- async def complete(
48
- self, prompt: str | list[typechat.PromptSection]
49
- ) -> typechat.Success[str] | typechat.Failure:
50
- """Request completion from the Agent.
51
-
52
- Args:
53
- prompt: Either a string prompt or list of PromptSection dicts
54
-
55
- Returns:
56
- Success with response text or Failure with error message
57
- """
58
- import typechat
59
-
60
- try:
61
- # Convert prompt sections to a single string if needed
62
- if isinstance(prompt, list):
63
- # Combine sections into a conversation-style prompt
64
- parts: list[str] = []
65
- for section in prompt:
66
- role = section["role"]
67
- content = section["content"]
68
- match role:
69
- case "system":
70
- parts.append(f"[System]: {content}")
71
- case "user":
72
- parts.append(f"[User]: {content}")
73
- case "assistant":
74
- parts.append(f"[Assistant]: {content}")
75
- prompt_text = "\n\n".join(parts)
76
- else:
77
- prompt_text = prompt
78
-
79
- # Run the agent and get response
80
- result = await self.agent.run(prompt_text)
81
- return typechat.Success(result.data)
82
-
83
- except Exception as e: # noqa: BLE001
84
- return typechat.Failure(f"Agent completion failed: {e!r}")
85
-
86
-
87
- @dataclass
88
- class ProcessingContext:
89
- """Context for TypeAgent knowledge processing."""
90
-
91
- lang_search_options: searchlang.LanguageSearchOptions
92
- answer_context_options: answers.AnswerContextOptions
93
- query_context: query.QueryEvalContext[podcast.PodcastMessage, TermToSemanticRefIndex]
94
- query_translator: typechat.TypeChatJsonTranslator[SearchQuery]
95
- answer_translator: typechat.TypeChatJsonTranslator[AnswerResponse]
96
-
97
-
98
- @dataclass
99
- class QueryResponse:
100
- """Response from a knowledge query."""
101
-
102
- success: bool
103
- answer: str
104
- time_ms: int
105
-
106
-
107
- @dataclass
108
- class IngestResponse:
109
- """Response from ingesting content into the knowledge base."""
110
-
111
- success: bool
112
- messages_added: int
113
- semantic_refs_added: int
114
- error: str | None = None
115
-
116
-
117
- class SemanticMemoryTools(ResourceProvider):
118
- """Provider for semantic memory / knowledge processing tools.
119
-
120
- Uses TypeAgent's KnowPro for:
121
- - Semantic indexing of conversations/transcripts
122
- - Natural language search queries
123
- - Structured answer generation
124
- """
125
-
126
- def __init__(
127
- self,
128
- model: ModelType = None,
129
- dbname: str | None = None,
130
- name: str = "semantic_memory",
131
- ) -> None:
132
- """Initialize semantic memory tools.
133
-
134
- Args:
135
- model: Model to use for LLM sampling (query translation, answers)
136
- dbname: SQLite database path, or None for in-memory storage
137
- name: Provider name
138
- """
139
- super().__init__(name=name)
140
- self.model = model
141
- self.dbname = dbname
142
- self._agent: Agent[Any, str] | None = None
143
- self._context: ProcessingContext | None = None
144
- self._tools: list[Tool] | None = None
145
-
146
- async def __aenter__(self) -> Self:
147
- """Initialize the agent and TypeAgent context."""
148
- from agentpool import Agent
149
-
150
- # Create minimal agent for LLM sampling
151
- self._agent = Agent(model=self.model, name=f"{self.name}-sampler")
152
- await self._agent.__aenter__()
153
- self._context = await self._make_context() # Build TypeAgent processing context
154
- return self
155
-
156
- async def __aexit__(
157
- self,
158
- exc_type: type[BaseException] | None,
159
- exc_val: BaseException | None,
160
- exc_tb: TracebackType | None,
161
- ) -> None:
162
- """Cleanup agent resources."""
163
- if self._agent:
164
- await self._agent.__aexit__(exc_type, exc_val, exc_tb)
165
- self._agent = None
166
- self._context = None
167
-
168
- async def _make_context(self) -> ProcessingContext:
169
- """Create TypeAgent processing context with our Agent-backed model."""
170
- from typeagent.aitools import utils
171
- from typeagent.knowpro import answers, searchlang
172
- from typeagent.knowpro.answer_response_schema import AnswerResponse
173
- from typeagent.knowpro.convsettings import ConversationSettings
174
- from typeagent.knowpro.search_query_schema import SearchQuery
175
- from typeagent.podcasts import podcast
176
- from typeagent.storage.utils import create_storage_provider
177
-
178
- if self._agent is None:
179
- msg = "Agent not initialized"
180
- raise RuntimeError(msg)
181
-
182
- settings = ConversationSettings()
183
- # Set up storage provider (SQLite or memory)
184
- settings.storage_provider = await create_storage_provider(
185
- settings.message_text_index_settings,
186
- settings.related_term_index_settings,
187
- self.dbname,
188
- podcast.PodcastMessage,
189
- )
190
- lang_search_options = searchlang.LanguageSearchOptions(
191
- compile_options=searchlang.LanguageQueryCompileOptions(),
192
- max_message_matches=25,
193
- )
194
- answer_context_options = answers.AnswerContextOptions(entities_top_k=50, topics_top_k=50)
195
-
196
- query_context = await self._load_conversation_index(settings) # Load / create conv index
197
- # Create Agent-backed TypeChat model
198
- model = AgentTypeChatModel(self._agent)
199
- # Create translators for structured extraction
200
- query_translator = utils.create_translator(model, SearchQuery)
201
- answer_translator = utils.create_translator(model, AnswerResponse)
202
- return ProcessingContext(
203
- lang_search_options=lang_search_options,
204
- answer_context_options=answer_context_options,
205
- query_context=query_context,
206
- query_translator=query_translator,
207
- answer_translator=answer_translator,
208
- )
209
-
210
- async def _load_conversation_index(
211
- self,
212
- settings: Any,
213
- ) -> query.QueryEvalContext[podcast.PodcastMessage, Any]:
214
- """Load conversation index from database or create empty one."""
215
- from typeagent.knowpro import query
216
- from typeagent.podcasts import podcast
217
-
218
- if self.dbname is None:
219
- # Try loading from default test data, or create empty
220
- try:
221
- conversation = await podcast.Podcast.read_from_file(
222
- "testdata/Episode_53_AdrianTchaikovsky_index",
223
- settings,
224
- )
225
- except FileNotFoundError:
226
- conversation = await podcast.Podcast.create(settings)
227
- else:
228
- conversation = await podcast.Podcast.create(settings)
229
-
230
- self._conversation = conversation
231
- return query.QueryEvalContext(conversation)
232
-
233
- @property
234
- def conversation(self) -> ConversationBase[Any] | None:
235
- """Get the current conversation/knowledge base."""
236
- return getattr(self, "_conversation", None)
237
-
238
- async def get_tools(self) -> list[Tool]:
239
- """Get available semantic memory tools."""
240
- if self._tools is not None:
241
- return self._tools
242
-
243
- self._tools = [
244
- self.create_tool(self.query_knowledge, read_only=True, idempotent=True),
245
- self.create_tool(self.ingest_transcript),
246
- self.create_tool(self.ingest_text),
247
- ]
248
- return self._tools
249
-
250
- async def query_knowledge(self, question: str) -> QueryResponse:
251
- """Query the knowledge base with a natural language question.
252
-
253
- Returns an answer synthesized from indexed conversations and documents.
254
-
255
- Args:
256
- question: Natural language question to answer
257
-
258
- Returns:
259
- QueryResponse with success status, answer text, and timing
260
- """
261
- from typeagent.knowpro import answers, searchlang
262
- import typechat
263
-
264
- if self._context is None:
265
- return QueryResponse(success=False, answer="Semantic memory not initialized", time_ms=0)
266
- t0 = time.time()
267
- question = question.strip()
268
- if not question:
269
- dt = int((time.time() - t0) * 1000)
270
- return QueryResponse(success=False, answer="No question provided", time_ms=dt)
271
-
272
- # Stage 1-3: LLM -> proto-query, compile, execute
273
- result = await searchlang.search_conversation_with_language(
274
- self._context.query_context.conversation,
275
- self._context.query_translator,
276
- question,
277
- self._context.lang_search_options,
278
- )
279
-
280
- if isinstance(result, typechat.Failure):
281
- dt = int((time.time() - t0) * 1000)
282
- return QueryResponse(success=False, answer=result.message, time_ms=dt)
283
-
284
- # Stage 3a-4: ordinals -> messages/semrefs, LLM -> answer
285
- _, combined_answer = await answers.generate_answers(
286
- self._context.answer_translator,
287
- result.value,
288
- self._context.query_context.conversation,
289
- question,
290
- options=self._context.answer_context_options,
291
- )
292
-
293
- dt = int((time.time() - t0) * 1000)
294
-
295
- match combined_answer.type:
296
- case "NoAnswer":
297
- answer = combined_answer.whyNoAnswer or "No answer found"
298
- return QueryResponse(success=False, answer=answer, time_ms=dt)
299
- case "Answered":
300
- answer = combined_answer.answer or ""
301
- return QueryResponse(success=True, answer=answer, time_ms=dt)
302
- case _:
303
- return QueryResponse(success=False, answer="Unexpected response type", time_ms=dt)
304
-
305
- async def ingest_transcript(
306
- self,
307
- file_path: str,
308
- name: str | None = None,
309
- fmt: Literal["auto", "txt", "vtt"] = "auto",
310
- ) -> IngestResponse:
311
- """Ingest a transcript file into the knowledge base.
312
-
313
- Supports plain text (.txt) and WebVTT (.vtt) formats.
314
- The content will be indexed for semantic search.
315
-
316
- Args:
317
- file_path: Path to the transcript file
318
- name: Optional name for the transcript (defaults to filename)
319
- fmt: File format - "auto" detects from extension, or specify "txt"/"vtt"
320
-
321
- Returns:
322
- IngestResponse with counts of added messages and semantic refs
323
- """
324
- if self._context is None or self.conversation is None:
325
- return IngestResponse(
326
- success=False,
327
- messages_added=0,
328
- semantic_refs_added=0,
329
- error="Semantic memory not initialized",
330
- )
331
-
332
- # Detect format
333
- if fmt == "auto":
334
- ext = os.path.splitext(file_path)[1].lower() # noqa: PTH122
335
- fmt = "vtt" if ext == ".vtt" else "txt"
336
-
337
- try:
338
- if fmt == "vtt":
339
- result = await self._ingest_vtt_file(file_path, name)
340
- else:
341
- result = await self._ingest_text_file(file_path, name)
342
- except Exception as e: # noqa: BLE001
343
- return IngestResponse(
344
- success=False,
345
- messages_added=0,
346
- semantic_refs_added=0,
347
- error=str(e),
348
- )
349
- else:
350
- return result
351
-
352
- async def _ingest_vtt_file(self, file_path: str, name: str | None) -> IngestResponse:
353
- """Ingest a WebVTT file."""
354
- from datetime import timedelta
355
-
356
- from typeagent.knowpro.universal_message import (
357
- UNIX_EPOCH,
358
- ConversationMessage,
359
- ConversationMessageMeta,
360
- format_timestamp_utc,
361
- )
362
- import webvtt # type: ignore[import-untyped]
363
-
364
- assert self.conversation
365
- vtt = webvtt.read(file_path)
366
- messages: list[ConversationMessage] = []
367
-
368
- for caption in vtt:
369
- if not caption.text.strip():
370
- continue
371
-
372
- # Parse voice tags for speaker detection
373
- from typeagent.transcripts.transcript_ingest import (
374
- parse_voice_tags,
375
- webvtt_timestamp_to_seconds,
376
- )
377
-
378
- raw_text = getattr(caption, "raw_text", caption.text)
379
- voice_segments = parse_voice_tags(raw_text)
380
-
381
- for speaker, text in voice_segments:
382
- if not text.strip():
383
- continue
384
-
385
- offset_seconds = webvtt_timestamp_to_seconds(caption.start)
386
- ts = format_timestamp_utc(UNIX_EPOCH + timedelta(seconds=offset_seconds))
387
- metadata = ConversationMessageMeta(speaker=speaker, recipients=[])
388
- message = ConversationMessage(text_chunks=[text], metadata=metadata, timestamp=ts)
389
- messages.append(message)
390
- if not messages:
391
- return IngestResponse(
392
- success=False,
393
- messages_added=0,
394
- semantic_refs_added=0,
395
- error="No messages found in VTT file",
396
- )
397
-
398
- result = await self.conversation.add_messages_with_indexing(messages)
399
- return IngestResponse(
400
- success=True,
401
- messages_added=result.messages_added,
402
- semantic_refs_added=result.semrefs_added,
403
- )
404
-
405
- async def _ingest_text_file(self, file_path: str, name: str | None) -> IngestResponse:
406
- """Ingest a plain text transcript file."""
407
- from typeagent.knowpro.universal_message import (
408
- UNIX_EPOCH,
409
- ConversationMessage,
410
- ConversationMessageMeta,
411
- format_timestamp_utc,
412
- )
413
-
414
- with open(file_path, encoding="utf-8") as f: # noqa: PTH123
415
- lines = f.readlines()
416
-
417
- # Parse transcript lines with speaker detection
418
- speaker_pattern = re.compile(r"^\s*(?P<speaker>[A-Z][A-Z\s]*?):\s*(?P<text>.*)$")
419
-
420
- messages: list[ConversationMessage] = []
421
- current_speaker: str | None = None
422
- current_chunks: list[str] = []
423
- assert self.conversation
424
- for line in lines:
425
- stripped = line.strip()
426
- if not stripped:
427
- continue
428
-
429
- match = speaker_pattern.match(stripped)
430
- if match:
431
- # Save previous message if exists
432
- if current_chunks:
433
- meta = ConversationMessageMeta(speaker=current_speaker, recipients=[])
434
- ts = format_timestamp_utc(UNIX_EPOCH)
435
- chunks = [" ".join(current_chunks)]
436
- message = ConversationMessage(text_chunks=chunks, metadata=meta, timestamp=ts)
437
- messages.append(message)
438
- current_speaker = match.group("speaker").strip()
439
- current_chunks = [match.group("text").strip()]
440
- elif current_chunks:
441
- current_chunks.append(stripped)
442
- else:
443
- # No speaker detected, use None
444
- current_chunks = [stripped]
445
-
446
- # Don't forget last message
447
- if current_chunks:
448
- metadata = ConversationMessageMeta(speaker=current_speaker, recipients=[])
449
- chunks = [" ".join(current_chunks)]
450
- ts = format_timestamp_utc(UNIX_EPOCH)
451
- message = ConversationMessage(text_chunks=chunks, metadata=metadata, timestamp=ts)
452
- messages.append(message)
453
-
454
- if not messages:
455
- err = "No messages found in text file"
456
- return IngestResponse(success=False, messages_added=0, semantic_refs_added=0, error=err)
457
- result = await self.conversation.add_messages_with_indexing(messages)
458
- return IngestResponse(
459
- success=True,
460
- messages_added=result.messages_added,
461
- semantic_refs_added=result.semrefs_added,
462
- )
463
-
464
- async def ingest_text(
465
- self,
466
- content: str,
467
- speaker: str | None = None,
468
- timestamp: str | None = None,
469
- ) -> IngestResponse:
470
- """Ingest raw text content into the knowledge base.
471
-
472
- Useful for adding content from memory, APIs, or other sources.
473
- Optionally specify a speaker name for attribution.
474
-
475
- Args:
476
- content: The text content to ingest
477
- speaker: Optional speaker/source attribution
478
- timestamp: Optional ISO timestamp (defaults to now)
479
-
480
- Returns:
481
- IngestResponse with counts of added messages and semantic refs
482
- """
483
- from typeagent.knowpro.universal_message import (
484
- ConversationMessage,
485
- ConversationMessageMeta,
486
- format_timestamp_utc,
487
- )
488
-
489
- if self._context is None or self.conversation is None:
490
- return IngestResponse(
491
- success=False,
492
- messages_added=0,
493
- semantic_refs_added=0,
494
- error="Semantic memory not initialized",
495
- )
496
-
497
- content = content.strip()
498
- if not content:
499
- return IngestResponse(
500
- success=False,
501
- messages_added=0,
502
- semantic_refs_added=0,
503
- error="No content provided",
504
- )
505
-
506
- # Use provided timestamp or current time
507
- if timestamp is None:
508
- timestamp = format_timestamp_utc(datetime.now(UTC))
509
-
510
- meta = ConversationMessageMeta(speaker=speaker, recipients=[])
511
- message = ConversationMessage(text_chunks=[content], metadata=meta, timestamp=timestamp)
512
- try:
513
- result = await self.conversation.add_messages_with_indexing([message])
514
- return IngestResponse(
515
- success=True,
516
- messages_added=result.messages_added,
517
- semantic_refs_added=result.semrefs_added,
518
- )
519
- except Exception as e: # noqa: BLE001
520
- return IngestResponse(
521
- success=False,
522
- messages_added=0,
523
- semantic_refs_added=0,
524
- error=str(e),
525
- )
526
-
527
-
528
- if __name__ == "__main__":
529
- import anyio
530
-
531
- async def main() -> None:
532
- async with SemanticMemoryTools(model="openai:gpt-4o-mini") as tools:
533
- fns = await tools.get_tools()
534
- print(f"Available tools: {[t.name for t in fns]}")
535
-
536
- anyio.run(main)