agentpool 2.1.9__py3-none-any.whl → 2.2.3__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 (174) hide show
  1. acp/__init__.py +13 -0
  2. acp/bridge/README.md +15 -2
  3. acp/bridge/__init__.py +3 -2
  4. acp/bridge/__main__.py +60 -19
  5. acp/bridge/ws_server.py +173 -0
  6. acp/bridge/ws_server_cli.py +89 -0
  7. acp/notifications.py +2 -1
  8. acp/stdio.py +39 -9
  9. acp/transports.py +362 -2
  10. acp/utils.py +15 -2
  11. agentpool/__init__.py +4 -1
  12. agentpool/agents/__init__.py +2 -0
  13. agentpool/agents/acp_agent/acp_agent.py +203 -88
  14. agentpool/agents/acp_agent/acp_converters.py +46 -21
  15. agentpool/agents/acp_agent/client_handler.py +157 -3
  16. agentpool/agents/acp_agent/session_state.py +4 -1
  17. agentpool/agents/agent.py +314 -107
  18. agentpool/agents/agui_agent/__init__.py +0 -2
  19. agentpool/agents/agui_agent/agui_agent.py +90 -21
  20. agentpool/agents/agui_agent/agui_converters.py +0 -131
  21. agentpool/agents/base_agent.py +163 -1
  22. agentpool/agents/claude_code_agent/claude_code_agent.py +626 -179
  23. agentpool/agents/claude_code_agent/converters.py +71 -3
  24. agentpool/agents/claude_code_agent/history.py +474 -0
  25. agentpool/agents/context.py +40 -0
  26. agentpool/agents/events/__init__.py +2 -0
  27. agentpool/agents/events/builtin_handlers.py +2 -1
  28. agentpool/agents/events/event_emitter.py +29 -2
  29. agentpool/agents/events/events.py +20 -0
  30. agentpool/agents/modes.py +54 -0
  31. agentpool/agents/tool_call_accumulator.py +213 -0
  32. agentpool/common_types.py +21 -0
  33. agentpool/config_resources/__init__.py +38 -1
  34. agentpool/config_resources/claude_code_agent.yml +3 -0
  35. agentpool/delegation/pool.py +37 -29
  36. agentpool/delegation/team.py +1 -0
  37. agentpool/delegation/teamrun.py +1 -0
  38. agentpool/diagnostics/__init__.py +53 -0
  39. agentpool/diagnostics/lsp_manager.py +1593 -0
  40. agentpool/diagnostics/lsp_proxy.py +41 -0
  41. agentpool/diagnostics/lsp_proxy_script.py +229 -0
  42. agentpool/diagnostics/models.py +398 -0
  43. agentpool/mcp_server/__init__.py +0 -2
  44. agentpool/mcp_server/client.py +12 -3
  45. agentpool/mcp_server/manager.py +25 -31
  46. agentpool/mcp_server/registries/official_registry_client.py +25 -0
  47. agentpool/mcp_server/tool_bridge.py +78 -66
  48. agentpool/messaging/__init__.py +0 -2
  49. agentpool/messaging/compaction.py +72 -197
  50. agentpool/messaging/message_history.py +12 -0
  51. agentpool/messaging/messages.py +52 -9
  52. agentpool/messaging/processing.py +3 -1
  53. agentpool/models/acp_agents/base.py +0 -22
  54. agentpool/models/acp_agents/mcp_capable.py +8 -148
  55. agentpool/models/acp_agents/non_mcp.py +129 -72
  56. agentpool/models/agents.py +35 -13
  57. agentpool/models/claude_code_agents.py +33 -2
  58. agentpool/models/manifest.py +43 -0
  59. agentpool/repomap.py +1 -1
  60. agentpool/resource_providers/__init__.py +9 -1
  61. agentpool/resource_providers/aggregating.py +52 -3
  62. agentpool/resource_providers/base.py +57 -1
  63. agentpool/resource_providers/mcp_provider.py +23 -0
  64. agentpool/resource_providers/plan_provider.py +130 -41
  65. agentpool/resource_providers/pool.py +2 -0
  66. agentpool/resource_providers/static.py +2 -0
  67. agentpool/sessions/__init__.py +2 -1
  68. agentpool/sessions/manager.py +31 -2
  69. agentpool/sessions/models.py +50 -0
  70. agentpool/skills/registry.py +13 -8
  71. agentpool/storage/manager.py +217 -1
  72. agentpool/testing.py +537 -19
  73. agentpool/utils/file_watcher.py +269 -0
  74. agentpool/utils/identifiers.py +121 -0
  75. agentpool/utils/pydantic_ai_helpers.py +46 -0
  76. agentpool/utils/streams.py +690 -1
  77. agentpool/utils/subprocess_utils.py +155 -0
  78. agentpool/utils/token_breakdown.py +461 -0
  79. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/METADATA +27 -7
  80. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/RECORD +170 -112
  81. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/WHEEL +1 -1
  82. agentpool_cli/__main__.py +4 -0
  83. agentpool_cli/serve_acp.py +41 -20
  84. agentpool_cli/serve_agui.py +87 -0
  85. agentpool_cli/serve_opencode.py +119 -0
  86. agentpool_commands/__init__.py +30 -0
  87. agentpool_commands/agents.py +74 -1
  88. agentpool_commands/history.py +62 -0
  89. agentpool_commands/mcp.py +176 -0
  90. agentpool_commands/models.py +56 -3
  91. agentpool_commands/tools.py +57 -0
  92. agentpool_commands/utils.py +51 -0
  93. agentpool_config/builtin_tools.py +77 -22
  94. agentpool_config/commands.py +24 -1
  95. agentpool_config/compaction.py +258 -0
  96. agentpool_config/mcp_server.py +131 -1
  97. agentpool_config/storage.py +46 -1
  98. agentpool_config/tools.py +7 -1
  99. agentpool_config/toolsets.py +92 -148
  100. agentpool_server/acp_server/acp_agent.py +134 -150
  101. agentpool_server/acp_server/commands/acp_commands.py +216 -51
  102. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +10 -10
  103. agentpool_server/acp_server/server.py +23 -79
  104. agentpool_server/acp_server/session.py +181 -19
  105. agentpool_server/opencode_server/.rules +95 -0
  106. agentpool_server/opencode_server/ENDPOINTS.md +362 -0
  107. agentpool_server/opencode_server/__init__.py +27 -0
  108. agentpool_server/opencode_server/command_validation.py +172 -0
  109. agentpool_server/opencode_server/converters.py +869 -0
  110. agentpool_server/opencode_server/dependencies.py +24 -0
  111. agentpool_server/opencode_server/input_provider.py +269 -0
  112. agentpool_server/opencode_server/models/__init__.py +228 -0
  113. agentpool_server/opencode_server/models/agent.py +53 -0
  114. agentpool_server/opencode_server/models/app.py +60 -0
  115. agentpool_server/opencode_server/models/base.py +26 -0
  116. agentpool_server/opencode_server/models/common.py +23 -0
  117. agentpool_server/opencode_server/models/config.py +37 -0
  118. agentpool_server/opencode_server/models/events.py +647 -0
  119. agentpool_server/opencode_server/models/file.py +88 -0
  120. agentpool_server/opencode_server/models/mcp.py +25 -0
  121. agentpool_server/opencode_server/models/message.py +162 -0
  122. agentpool_server/opencode_server/models/parts.py +190 -0
  123. agentpool_server/opencode_server/models/provider.py +81 -0
  124. agentpool_server/opencode_server/models/pty.py +43 -0
  125. agentpool_server/opencode_server/models/session.py +99 -0
  126. agentpool_server/opencode_server/routes/__init__.py +25 -0
  127. agentpool_server/opencode_server/routes/agent_routes.py +442 -0
  128. agentpool_server/opencode_server/routes/app_routes.py +139 -0
  129. agentpool_server/opencode_server/routes/config_routes.py +241 -0
  130. agentpool_server/opencode_server/routes/file_routes.py +392 -0
  131. agentpool_server/opencode_server/routes/global_routes.py +94 -0
  132. agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
  133. agentpool_server/opencode_server/routes/message_routes.py +705 -0
  134. agentpool_server/opencode_server/routes/pty_routes.py +299 -0
  135. agentpool_server/opencode_server/routes/session_routes.py +1205 -0
  136. agentpool_server/opencode_server/routes/tui_routes.py +139 -0
  137. agentpool_server/opencode_server/server.py +430 -0
  138. agentpool_server/opencode_server/state.py +121 -0
  139. agentpool_server/opencode_server/time_utils.py +8 -0
  140. agentpool_storage/__init__.py +16 -0
  141. agentpool_storage/base.py +103 -0
  142. agentpool_storage/claude_provider.py +907 -0
  143. agentpool_storage/file_provider.py +129 -0
  144. agentpool_storage/memory_provider.py +61 -0
  145. agentpool_storage/models.py +3 -0
  146. agentpool_storage/opencode_provider.py +730 -0
  147. agentpool_storage/project_store.py +325 -0
  148. agentpool_storage/session_store.py +6 -0
  149. agentpool_storage/sql_provider/__init__.py +4 -2
  150. agentpool_storage/sql_provider/models.py +48 -0
  151. agentpool_storage/sql_provider/sql_provider.py +134 -1
  152. agentpool_storage/sql_provider/utils.py +10 -1
  153. agentpool_storage/text_log_provider.py +1 -0
  154. agentpool_toolsets/builtin/__init__.py +0 -8
  155. agentpool_toolsets/builtin/code.py +95 -56
  156. agentpool_toolsets/builtin/debug.py +16 -21
  157. agentpool_toolsets/builtin/execution_environment.py +99 -103
  158. agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
  159. agentpool_toolsets/builtin/skills.py +86 -4
  160. agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
  161. agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
  162. agentpool_toolsets/fsspec_toolset/grep.py +74 -2
  163. agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
  164. agentpool_toolsets/fsspec_toolset/toolset.py +159 -38
  165. agentpool_toolsets/mcp_discovery/__init__.py +5 -0
  166. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  167. agentpool_toolsets/mcp_discovery/toolset.py +454 -0
  168. agentpool_toolsets/mcp_run_toolset.py +84 -6
  169. agentpool_toolsets/builtin/agent_management.py +0 -239
  170. agentpool_toolsets/builtin/history.py +0 -36
  171. agentpool_toolsets/builtin/integration.py +0 -85
  172. agentpool_toolsets/builtin/tool_management.py +0 -90
  173. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/entry_points.txt +0 -0
  174. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/licenses/LICENSE +0 -0
@@ -2,8 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING
6
-
5
+ from pydantic_ai import ModelRequest, ModelResponse # noqa: TC002
7
6
  from slashed import CommandContext # noqa: TC002
8
7
 
9
8
  from agentpool.messaging.context import NodeContext # noqa: TC001
@@ -12,10 +11,6 @@ from agentpool_config.session import SessionQuery
12
11
  from agentpool_server.acp_server.session import ACPSession # noqa: TC001
13
12
 
14
13
 
15
- if TYPE_CHECKING:
16
- from pydantic_ai import ModelRequest, ModelResponse
17
-
18
-
19
14
  class ListSessionsCommand(NodeCommand):
20
15
  """List all available ACP sessions.
21
16
 
@@ -27,17 +22,23 @@ class ListSessionsCommand(NodeCommand):
27
22
  Options:
28
23
  --active Show only active sessions
29
24
  --stored Show only stored sessions
25
+ --detail Show detailed view (default: compact table)
26
+ --page Page number (1-based, default: 1)
27
+ --per-page Items per page (default: 20)
30
28
  """
31
29
 
32
30
  name = "list-sessions"
33
31
  category = "acp"
34
32
 
35
- async def execute_command(
33
+ async def execute_command( # noqa: PLR0915
36
34
  self,
37
35
  ctx: CommandContext[NodeContext[ACPSession]],
38
36
  *,
39
37
  active: bool = False,
40
38
  stored: bool = False,
39
+ detail: bool = False,
40
+ page: int = 1,
41
+ per_page: int = 20,
41
42
  ) -> None:
42
43
  """List available ACP sessions.
43
44
 
@@ -45,6 +46,9 @@ class ListSessionsCommand(NodeCommand):
45
46
  ctx: Command context with ACP session
46
47
  active: Show only active sessions
47
48
  stored: Show only stored sessions
49
+ detail: Show detailed view instead of compact table
50
+ page: Page number (1-based)
51
+ per_page: Number of items per page
48
52
  """
49
53
  session = ctx.context.data
50
54
  if not session:
@@ -54,6 +58,11 @@ class ListSessionsCommand(NodeCommand):
54
58
  await ctx.output.print("❌ **Session manager not available**")
55
59
  return
56
60
 
61
+ # Validate pagination params
62
+ page = max(page, 1)
63
+ if per_page < 1:
64
+ per_page = 20
65
+
57
66
  # If no filter specified, show both
58
67
  if not active and not stored:
59
68
  active = stored = True
@@ -61,63 +70,117 @@ class ListSessionsCommand(NodeCommand):
61
70
  try:
62
71
  output_lines = ["## 📋 ACP Sessions\n"]
63
72
 
64
- # Show active sessions
73
+ # Collect all sessions to paginate
74
+ all_sessions: list[tuple[str, str, dict[str, str | None]]] = [] # (id, type, info)
75
+
76
+ # Collect active sessions
65
77
  if active:
66
- output_lines.append("### 🟢 Active Sessions")
67
78
  active_sessions = session.manager._active
68
-
69
- if not active_sessions:
70
- output_lines.append("*No active sessions*\n")
71
- else:
72
- for session_id, sess in active_sessions.items():
73
- agent_name = sess.current_agent_name
74
- cwd = sess.cwd or "unknown"
75
- is_current = session_id == session.session_id
76
-
77
- # Get title from SessionData
78
- session_data = await session.manager.session_manager.store.load(session_id)
79
- title = session_data.title if session_data else None
80
-
81
- status = " *(current)*" if is_current else ""
82
- title_text = f": {title}" if title else ""
83
- output_lines.append(f"- **{session_id}**{status}{title_text}")
84
- output_lines.append(f" - Agent: `{agent_name}`")
85
- output_lines.append(f" - Directory: `{cwd}`")
86
- output_lines.append("")
87
-
88
- # Show stored sessions
79
+ for session_id, sess in active_sessions.items():
80
+ session_data = await session.manager.session_manager.store.load(session_id)
81
+ title = session_data.title if session_data else None
82
+ is_current = session_id == session.session_id
83
+ all_sessions.append((
84
+ session_id,
85
+ "active",
86
+ {
87
+ "agent_name": sess.current_agent_name,
88
+ "cwd": sess.cwd or "unknown",
89
+ "title": title,
90
+ "is_current": "yes" if is_current else None,
91
+ "last_active": None,
92
+ },
93
+ ))
94
+
95
+ # Collect stored sessions
89
96
  if stored:
90
- output_lines.append("### 💾 Stored Sessions")
91
-
92
97
  try:
93
98
  stored_session_ids = await session.manager.session_manager.store.list_sessions()
94
- # Filter out active ones if we already showed them
99
+ # Filter out active ones if we already collected them
95
100
  if active:
96
101
  stored_session_ids = [
97
102
  sid for sid in stored_session_ids if sid not in session.manager._active
98
103
  ]
99
104
 
100
- if not stored_session_ids:
101
- output_lines.append("*No stored sessions*\n")
102
- else:
103
- for session_id in stored_session_ids:
104
- session_data = await session.manager.session_manager.store.load(
105
- session_id
106
- )
107
- if session_data:
108
- title_text = f": {session_data.title}" if session_data.title else ""
109
- output_lines.append(f"- **{session_id}**{title_text}")
110
- output_lines.append(f" - Agent: `{session_data.agent_name}`")
111
- output_lines.append(
112
- f" - Directory: `{session_data.cwd or 'unknown'}`"
113
- )
114
- output_lines.append(
115
- f" - Last active: {session_data.last_active.strftime('%Y-%m-%d %H:%M')}" # noqa: E501
116
- )
117
- output_lines.append("")
105
+ for session_id in stored_session_ids:
106
+ session_data = await session.manager.session_manager.store.load(session_id)
107
+ if session_data:
108
+ all_sessions.append((
109
+ session_id,
110
+ "stored",
111
+ {
112
+ "agent_name": session_data.agent_name,
113
+ "cwd": session_data.cwd or "unknown",
114
+ "title": session_data.title,
115
+ "is_current": None,
116
+ "last_active": session_data.last_active.strftime(
117
+ "%Y-%m-%d %H:%M"
118
+ ),
119
+ },
120
+ ))
118
121
  except Exception as e: # noqa: BLE001
119
122
  output_lines.append(f"*Error loading stored sessions: {e}*\n")
120
123
 
124
+ # Calculate pagination
125
+ total_count = len(all_sessions)
126
+ total_pages = (total_count + per_page - 1) // per_page if total_count > 0 else 1
127
+ page = min(page, total_pages)
128
+
129
+ start_idx = (page - 1) * per_page
130
+ end_idx = start_idx + per_page
131
+ page_sessions = all_sessions[start_idx:end_idx]
132
+
133
+ if not page_sessions:
134
+ output_lines.append("*No sessions found*\n")
135
+ elif detail:
136
+ # Detailed view (original format)
137
+ active_in_page = [(s, i) for s, t, i in page_sessions if t == "active"]
138
+ stored_in_page = [(s, i) for s, t, i in page_sessions if t == "stored"]
139
+
140
+ if active_in_page:
141
+ output_lines.append("### 🟢 Active Sessions")
142
+ for session_id, info in active_in_page:
143
+ status = " *(current)*" if info["is_current"] else ""
144
+ title_text = f": {info['title']}" if info["title"] else ""
145
+ output_lines.append(f"- **{session_id}**{status}{title_text}")
146
+ output_lines.append(f" - Agent: `{info['agent_name']}`")
147
+ output_lines.append(f" - Directory: `{info['cwd']}`")
148
+ output_lines.append("")
149
+
150
+ if stored_in_page:
151
+ output_lines.append("### 💾 Stored Sessions")
152
+ for session_id, info in stored_in_page:
153
+ title_text = f": {info['title']}" if info["title"] else ""
154
+ output_lines.append(f"- **{session_id}**{title_text}")
155
+ output_lines.append(f" - Agent: `{info['agent_name']}`")
156
+ output_lines.append(f" - Directory: `{info['cwd']}`")
157
+ if info["last_active"]:
158
+ output_lines.append(f" - Last active: {info['last_active']}")
159
+ output_lines.append("")
160
+ else:
161
+ # Compact table view (default)
162
+ output_lines.append("| Title | Agent | Last Active |")
163
+ output_lines.append("|-------|-------|-------------|")
164
+ for session_id, _session_type, info in page_sessions:
165
+ title = info["title"] or session_id[:16]
166
+ if info["is_current"]:
167
+ title = f"▶️ {title}"
168
+ agent = info["agent_name"]
169
+ last_active = info["last_active"] or "-"
170
+ output_lines.append(f"| {title} | {agent} | {last_active} |")
171
+ output_lines.append("")
172
+
173
+ # Add pagination info
174
+ output_lines.append(f"---\n*Page {page}/{total_pages} ({total_count} total sessions)*")
175
+ if total_pages > 1:
176
+ nav_hints = []
177
+ if page > 1:
178
+ nav_hints.append(f"`/list-sessions --page {page - 1}` for previous")
179
+ if page < total_pages:
180
+ nav_hints.append(f"`/list-sessions --page {page + 1}` for next")
181
+ if nav_hints:
182
+ output_lines.append(f"*{', '.join(nav_hints)}*")
183
+
121
184
  await ctx.output.print("\n".join(output_lines))
122
185
 
123
186
  except Exception as e: # noqa: BLE001
@@ -582,6 +645,107 @@ class SetPoolCommand(NodeCommand):
582
645
  await ctx.output.print(f"❌ **Error switching pool:** {e}")
583
646
 
584
647
 
648
+ class CompactCommand(NodeCommand):
649
+ """Compact the conversation history to reduce context size.
650
+
651
+ Uses the configured compaction pipeline from the agent pool manifest,
652
+ or falls back to a default summarizing pipeline.
653
+
654
+ This will:
655
+ - Apply configured compaction steps (filter, truncate, summarize)
656
+ - Reduce the message history while preserving important context
657
+ - Report the reduction in message count
658
+
659
+ Options:
660
+ --preset <name> Use a specific preset (minimal, balanced, summarizing)
661
+
662
+ Examples:
663
+ /compact
664
+ /compact --preset=minimal
665
+ """
666
+
667
+ name = "compact"
668
+ category = "acp"
669
+
670
+ async def execute_command(
671
+ self,
672
+ ctx: CommandContext[NodeContext[ACPSession]],
673
+ *,
674
+ preset: str | None = None,
675
+ ) -> None:
676
+ """Compact the conversation history.
677
+
678
+ Args:
679
+ ctx: Command context with ACP session
680
+ preset: Optional preset name (minimal, balanced, summarizing)
681
+ """
682
+ session = ctx.context.data
683
+ if not session:
684
+ raise RuntimeError("Session not available in command context")
685
+
686
+ agent = session.agent
687
+
688
+ # Check if there's any history to compact
689
+ if not agent.conversation.get_history():
690
+ await ctx.output.print("📭 **No message history to compact**")
691
+ return
692
+
693
+ try:
694
+ # Get compaction pipeline
695
+ from agentpool.messaging.compaction import (
696
+ balanced_context,
697
+ minimal_context,
698
+ summarizing_context,
699
+ )
700
+
701
+ pipeline = None
702
+
703
+ # Check for preset override
704
+ if preset:
705
+ match preset.lower():
706
+ case "minimal":
707
+ pipeline = minimal_context()
708
+ case "balanced":
709
+ pipeline = balanced_context()
710
+ case "summarizing":
711
+ pipeline = summarizing_context()
712
+ case _:
713
+ await ctx.output.print(
714
+ f"⚠️ **Unknown preset:** `{preset}`\n"
715
+ "Available: minimal, balanced, summarizing"
716
+ )
717
+ return
718
+
719
+ # Fall back to pool's configured pipeline
720
+ if pipeline is None:
721
+ pipeline = session.agent_pool.compaction_pipeline
722
+
723
+ # Fall back to default summarizing pipeline
724
+ if pipeline is None:
725
+ pipeline = summarizing_context()
726
+
727
+ await ctx.output.print("🔄 **Compacting conversation history...**")
728
+
729
+ # Apply the pipeline using shared helper
730
+ from agentpool.messaging.compaction import compact_conversation
731
+
732
+ original_count, compacted_count = await compact_conversation(
733
+ pipeline, agent.conversation
734
+ )
735
+ reduction = original_count - compacted_count
736
+
737
+ await ctx.output.print(
738
+ f"✅ **Compaction complete**\n"
739
+ f"- Messages: {original_count} → {compacted_count} ({reduction} removed)\n"
740
+ f"- Reduction: {reduction / original_count * 100:.1f}%"
741
+ if original_count > 0
742
+ else "✅ **Compaction complete** (no messages)"
743
+ )
744
+
745
+ except Exception as e: # noqa: BLE001
746
+ await ctx.output.print(f"❌ **Error compacting history:** {e}")
747
+
748
+
585
749
  def get_acp_commands() -> list[type[NodeCommand]]:
586
750
  """Get all ACP-specific slash commands."""
587
751
  return [
@@ -591,4 +755,5 @@ def get_acp_commands() -> list[type[NodeCommand]]:
591
755
  DeleteSessionCommand,
592
756
  ListPoolsCommand,
593
757
  SetPoolCommand,
758
+ CompactCommand,
594
759
  ]
@@ -66,10 +66,7 @@ class FetchRepoCommand(NodeCommand):
66
66
  """
67
67
  session = ctx.context.data
68
68
  assert session
69
-
70
- # Generate tool call ID
71
- tool_call_id = f"fetch-repo-{uuid.uuid4().hex[:8]}"
72
-
69
+ tc_id = f"fetch-repo-{uuid.uuid4().hex[:8]}"
73
70
  try:
74
71
  # Build URL
75
72
  base_url = f"https://uithub.com/{repo}"
@@ -113,7 +110,7 @@ class FetchRepoCommand(NodeCommand):
113
110
  display_path += f":{path}"
114
111
 
115
112
  await session.notifications.tool_call_start(
116
- tool_call_id=tool_call_id,
113
+ tool_call_id=tc_id,
117
114
  title=f"Fetching repository: {display_path}",
118
115
  kind="fetch",
119
116
  )
@@ -134,11 +131,10 @@ class FetchRepoCommand(NodeCommand):
134
131
  content=f"Repository contents from {display_path}:\n\n{content}"
135
132
  )
136
133
  session.staged_content.add([staged_part])
137
-
138
134
  # Send successful result - wrap in code block for proper display
139
135
  staged_count = len(session.staged_content)
140
136
  await session.notifications.tool_call_progress(
141
- tool_call_id=tool_call_id,
137
+ tool_call_id=tc_id,
142
138
  status="completed",
143
139
  title=f"Repository {display_path} fetched and staged ({staged_count} total parts)",
144
140
  content=[f"```\n{content}\n```"],
@@ -149,21 +145,25 @@ class FetchRepoCommand(NodeCommand):
149
145
  "HTTP error fetching repository", repo=repo, status=e.response.status_code
150
146
  )
151
147
  await session.notifications.tool_call_progress(
152
- tool_call_id=tool_call_id,
148
+ tool_call_id=tc_id,
153
149
  status="failed",
154
150
  title=f"HTTP {e.response.status_code}: Failed to fetch {repo}",
155
151
  )
156
152
  except httpx.RequestError as e:
157
153
  logger.exception("Request error fetching repository", repo=repo)
158
154
  await session.notifications.tool_call_progress(
159
- tool_call_id=tool_call_id,
155
+ tool_call_id=tc_id,
160
156
  status="failed",
161
157
  title=f"Network error: {e}",
162
158
  )
163
159
  except Exception as e:
164
160
  logger.exception("Unexpected error fetching repository", repo=repo)
165
161
  await session.notifications.tool_call_progress(
166
- tool_call_id=tool_call_id,
162
+ tool_call_id=tc_id,
167
163
  status="failed",
168
164
  title=f"Error: {e}",
169
165
  )
166
+
167
+
168
+ if __name__ == "__main__":
169
+ cmd = FetchRepoCommand()
@@ -7,14 +7,10 @@ the Agent Client Protocol.
7
7
  from __future__ import annotations
8
8
 
9
9
  import asyncio
10
- from datetime import timedelta
11
10
  import functools
12
11
  from typing import TYPE_CHECKING, Any, Self
13
12
 
14
- import logfire
15
-
16
- from acp import AgentSideConnection
17
- from acp.stdio import stdio_streams
13
+ from acp import serve
18
14
  from agentpool import AgentPool
19
15
  from agentpool.log import get_logger
20
16
  from agentpool.models.manifest import AgentsManifest
@@ -23,41 +19,14 @@ from agentpool_server.acp_server.acp_agent import AgentPoolACPAgent
23
19
 
24
20
 
25
21
  if TYPE_CHECKING:
26
- from collections.abc import Sequence
27
-
28
- from tokonomics.model_discovery import ProviderType
29
- from tokonomics.model_discovery.model_info import ModelInfo
30
22
  from upathtools import JoinablePathLike
31
23
 
32
- from acp.schema import ModelInfo as ACPModelInfo
24
+ from acp import Transport
33
25
 
34
26
 
35
27
  logger = get_logger(__name__)
36
28
 
37
29
 
38
- def _convert_to_acp_model_info(
39
- toko_models: Sequence[ModelInfo],
40
- ) -> list[ACPModelInfo]:
41
- """Convert tokonomics ModelInfo list to ACP ModelInfo list.
42
-
43
- Args:
44
- toko_models: List of tokonomics ModelInfo objects
45
-
46
- Returns:
47
- List of ACP ModelInfo objects with pydantic_ai_id as model_id
48
- """
49
- from acp.schema import ModelInfo as ACPModelInfo
50
-
51
- return [
52
- ACPModelInfo(
53
- model_id=model.pydantic_ai_id,
54
- name=f"{model.provider}: {model.name}" if model.provider else model.name,
55
- description=model.format(),
56
- )
57
- for model in toko_models
58
- ]
59
-
60
-
61
30
  class ACPServer(BaseServer):
62
31
  """ACP (Agent Client Protocol) server for agentpool using external library.
63
32
 
@@ -75,13 +44,13 @@ class ACPServer(BaseServer):
75
44
  name: str | None = None,
76
45
  file_access: bool = True,
77
46
  terminal_access: bool = True,
78
- providers: list[ProviderType] | None = None,
79
47
  debug_messages: bool = False,
80
48
  debug_file: str | None = None,
81
49
  debug_commands: bool = False,
82
50
  agent: str | None = None,
83
51
  load_skills: bool = True,
84
52
  config_path: str | None = None,
53
+ transport: Transport = "stdio",
85
54
  ) -> None:
86
55
  """Initialize ACP server with configuration.
87
56
 
@@ -90,27 +59,24 @@ class ACPServer(BaseServer):
90
59
  name: Optional Server name (auto-generated if None)
91
60
  file_access: Whether to support file access operations
92
61
  terminal_access: Whether to support terminal access operations
93
- providers: List of providers to use for model discovery (None = openrouter)
94
62
  debug_messages: Whether to enable debug message logging
95
63
  debug_file: File path for debug message logging
96
64
  debug_commands: Whether to enable debug slash commands for testing
97
65
  agent: Optional specific agent name to use (defaults to first agent)
98
66
  load_skills: Whether to load client-side skills from .claude/skills
99
67
  config_path: Path to the configuration file (for tracking/hot-switching)
68
+ transport: Transport configuration ("stdio", "websocket", or transport object)
100
69
  """
101
70
  super().__init__(pool, name=name, raise_exceptions=True)
102
71
  self.file_access = file_access
103
72
  self.terminal_access = terminal_access
104
- self.providers = providers or ["openai", "anthropic", "gemini"]
105
73
  self.debug_messages = debug_messages
106
74
  self.debug_file = debug_file
107
75
  self.debug_commands = debug_commands
108
76
  self.agent = agent
109
77
  self.load_skills = load_skills
110
78
  self.config_path = config_path
111
-
112
- self._available_models: list[ACPModelInfo] = []
113
- self._models_initialized = False
79
+ self.transport: Transport = transport
114
80
 
115
81
  @classmethod
116
82
  def from_config(
@@ -119,12 +85,12 @@ class ACPServer(BaseServer):
119
85
  *,
120
86
  file_access: bool = True,
121
87
  terminal_access: bool = True,
122
- providers: list[ProviderType] | None = None,
123
88
  debug_messages: bool = False,
124
89
  debug_file: str | None = None,
125
90
  debug_commands: bool = False,
126
91
  agent: str | None = None,
127
92
  load_skills: bool = True,
93
+ transport: Transport = "stdio",
128
94
  ) -> Self:
129
95
  """Create ACP server from existing agentpool configuration.
130
96
 
@@ -132,12 +98,12 @@ class ACPServer(BaseServer):
132
98
  config_path: Path to agentpool YAML config file
133
99
  file_access: Enable file system access
134
100
  terminal_access: Enable terminal access
135
- providers: List of provider types to use for model discovery
136
101
  debug_messages: Enable saving JSON messages to file
137
102
  debug_file: Path to debug file
138
103
  debug_commands: Enable debug slash commands for testing
139
104
  agent: Optional specific agent name to use (defaults to first agent)
140
105
  load_skills: Whether to load client-side skills from .claude/skills
106
+ transport: Transport configuration ("stdio", "websocket", or transport object)
141
107
 
142
108
  Returns:
143
109
  Configured ACP server instance with agent pool from config
@@ -148,13 +114,13 @@ class ACPServer(BaseServer):
148
114
  pool,
149
115
  file_access=file_access,
150
116
  terminal_access=terminal_access,
151
- providers=providers,
152
117
  debug_messages=debug_messages,
153
118
  debug_file=debug_file or "acp-debug.jsonl" if debug_messages else None,
154
119
  debug_commands=debug_commands,
155
120
  agent=agent,
156
121
  load_skills=load_skills,
157
122
  config_path=str(config_path),
123
+ transport=transport,
158
124
  )
159
125
  agent_names = list(server.pool.agents.keys())
160
126
 
@@ -170,13 +136,13 @@ class ACPServer(BaseServer):
170
136
 
171
137
  async def _start_async(self) -> None:
172
138
  """Start the ACP server (blocking async - runs until stopped)."""
173
- agent_names = list(self.pool.agents.keys())
174
- self.log.info("Starting ACP server on stdio", agent_names=agent_names)
175
- await self._initialize_models() # Initialize models on first run
139
+ transport_name = (
140
+ type(self.transport).__name__ if not isinstance(self.transport, str) else self.transport
141
+ )
142
+ self.log.info("Starting ACP server", transport=transport_name)
176
143
  create_acp_agent = functools.partial(
177
144
  AgentPoolACPAgent,
178
145
  agent_pool=self.pool,
179
- available_models=self._available_models,
180
146
  file_access=self.file_access,
181
147
  terminal_access=self.terminal_access,
182
148
  debug_commands=self.debug_commands,
@@ -184,21 +150,24 @@ class ACPServer(BaseServer):
184
150
  load_skills=self.load_skills,
185
151
  server=self,
186
152
  )
187
- reader, writer = await stdio_streams()
188
- file = self.debug_file if self.debug_messages else None
189
- conn = AgentSideConnection(create_acp_agent, writer, reader, debug_file=file)
153
+
154
+ debug_file = self.debug_file if self.debug_messages else None
190
155
  self.log.info("ACP server started", file=self.file_access, terminal=self.terminal_access)
191
- try: # Keep the connection alive until shutdown
192
- await self._shutdown_event.wait()
156
+
157
+ try:
158
+ await serve(
159
+ create_acp_agent,
160
+ transport=self.transport,
161
+ shutdown_event=self._shutdown_event,
162
+ debug_file=debug_file,
163
+ )
193
164
  except asyncio.CancelledError:
194
165
  self.log.info("ACP server shutdown requested")
195
166
  raise
196
167
  except KeyboardInterrupt:
197
168
  self.log.info("ACP server shutdown requested")
198
169
  except Exception:
199
- self.log.exception("Connection receive task failed")
200
- finally:
201
- await conn.close()
170
+ self.log.exception("ACP server error")
202
171
 
203
172
  async def swap_pool(
204
173
  self,
@@ -261,28 +230,3 @@ class ACPServer(BaseServer):
261
230
 
262
231
  self.log.info("Pool swapped successfully", agent_names=agent_names, default_agent=agent)
263
232
  return agent_names
264
-
265
- @logfire.instrument("ACP: Initializing models.")
266
- async def _initialize_models(self) -> None:
267
- """Initialize available models using tokonomics model discovery.
268
-
269
- Converts tokonomics ModelInfo to ACP ModelInfo format at startup
270
- so all downstream code works with ACP types consistently.
271
- """
272
- from tokonomics.model_discovery import get_all_models
273
-
274
- if self._models_initialized:
275
- return
276
- try:
277
- self.log.info("Discovering available models...")
278
- delta = timedelta(days=200)
279
- toko_models = await get_all_models(providers=self.providers, max_age=delta)
280
- # Convert to ACP format once at startup
281
- self._available_models = _convert_to_acp_model_info(toko_models)
282
- self._models_initialized = True
283
- self.log.info("Discovered models", count=len(self._available_models))
284
- except Exception:
285
- self.log.exception("Failed to discover models")
286
- self._available_models = []
287
- finally:
288
- self._models_initialized = True