agentpool 2.1.9__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.

Potentially problematic release.


This version of agentpool might be problematic. Click here for more details.

Files changed (474) hide show
  1. acp/README.md +64 -0
  2. acp/__init__.py +172 -0
  3. acp/__main__.py +10 -0
  4. acp/acp_requests.py +285 -0
  5. acp/agent/__init__.py +6 -0
  6. acp/agent/connection.py +256 -0
  7. acp/agent/implementations/__init__.py +6 -0
  8. acp/agent/implementations/debug_server/__init__.py +1 -0
  9. acp/agent/implementations/debug_server/cli.py +79 -0
  10. acp/agent/implementations/debug_server/debug.html +234 -0
  11. acp/agent/implementations/debug_server/debug_server.py +496 -0
  12. acp/agent/implementations/testing.py +91 -0
  13. acp/agent/protocol.py +65 -0
  14. acp/bridge/README.md +162 -0
  15. acp/bridge/__init__.py +6 -0
  16. acp/bridge/__main__.py +91 -0
  17. acp/bridge/bridge.py +246 -0
  18. acp/bridge/py.typed +0 -0
  19. acp/bridge/settings.py +15 -0
  20. acp/client/__init__.py +7 -0
  21. acp/client/connection.py +251 -0
  22. acp/client/implementations/__init__.py +7 -0
  23. acp/client/implementations/default_client.py +185 -0
  24. acp/client/implementations/headless_client.py +266 -0
  25. acp/client/implementations/noop_client.py +110 -0
  26. acp/client/protocol.py +61 -0
  27. acp/connection.py +280 -0
  28. acp/exceptions.py +46 -0
  29. acp/filesystem.py +524 -0
  30. acp/notifications.py +832 -0
  31. acp/py.typed +0 -0
  32. acp/schema/__init__.py +265 -0
  33. acp/schema/agent_plan.py +30 -0
  34. acp/schema/agent_requests.py +126 -0
  35. acp/schema/agent_responses.py +256 -0
  36. acp/schema/base.py +39 -0
  37. acp/schema/capabilities.py +230 -0
  38. acp/schema/client_requests.py +247 -0
  39. acp/schema/client_responses.py +96 -0
  40. acp/schema/common.py +81 -0
  41. acp/schema/content_blocks.py +188 -0
  42. acp/schema/mcp.py +82 -0
  43. acp/schema/messages.py +171 -0
  44. acp/schema/notifications.py +82 -0
  45. acp/schema/protocol_stuff.md +3 -0
  46. acp/schema/session_state.py +160 -0
  47. acp/schema/session_updates.py +419 -0
  48. acp/schema/slash_commands.py +51 -0
  49. acp/schema/terminal.py +15 -0
  50. acp/schema/tool_call.py +347 -0
  51. acp/stdio.py +250 -0
  52. acp/task/__init__.py +53 -0
  53. acp/task/debug.py +197 -0
  54. acp/task/dispatcher.py +93 -0
  55. acp/task/queue.py +69 -0
  56. acp/task/sender.py +82 -0
  57. acp/task/state.py +87 -0
  58. acp/task/supervisor.py +93 -0
  59. acp/terminal_handle.py +30 -0
  60. acp/tool_call_reporter.py +199 -0
  61. acp/tool_call_state.py +178 -0
  62. acp/transports.py +104 -0
  63. acp/utils.py +240 -0
  64. agentpool/__init__.py +63 -0
  65. agentpool/__main__.py +7 -0
  66. agentpool/agents/__init__.py +30 -0
  67. agentpool/agents/acp_agent/__init__.py +5 -0
  68. agentpool/agents/acp_agent/acp_agent.py +837 -0
  69. agentpool/agents/acp_agent/acp_converters.py +294 -0
  70. agentpool/agents/acp_agent/client_handler.py +317 -0
  71. agentpool/agents/acp_agent/session_state.py +44 -0
  72. agentpool/agents/agent.py +1264 -0
  73. agentpool/agents/agui_agent/__init__.py +19 -0
  74. agentpool/agents/agui_agent/agui_agent.py +677 -0
  75. agentpool/agents/agui_agent/agui_converters.py +423 -0
  76. agentpool/agents/agui_agent/chunk_transformer.py +204 -0
  77. agentpool/agents/agui_agent/event_types.py +83 -0
  78. agentpool/agents/agui_agent/helpers.py +192 -0
  79. agentpool/agents/architect.py +71 -0
  80. agentpool/agents/base_agent.py +177 -0
  81. agentpool/agents/claude_code_agent/__init__.py +11 -0
  82. agentpool/agents/claude_code_agent/claude_code_agent.py +1021 -0
  83. agentpool/agents/claude_code_agent/converters.py +243 -0
  84. agentpool/agents/context.py +105 -0
  85. agentpool/agents/events/__init__.py +61 -0
  86. agentpool/agents/events/builtin_handlers.py +129 -0
  87. agentpool/agents/events/event_emitter.py +320 -0
  88. agentpool/agents/events/events.py +561 -0
  89. agentpool/agents/events/tts_handlers.py +186 -0
  90. agentpool/agents/interactions.py +419 -0
  91. agentpool/agents/slashed_agent.py +244 -0
  92. agentpool/agents/sys_prompts.py +178 -0
  93. agentpool/agents/tool_wrapping.py +184 -0
  94. agentpool/base_provider.py +28 -0
  95. agentpool/common_types.py +226 -0
  96. agentpool/config_resources/__init__.py +16 -0
  97. agentpool/config_resources/acp_assistant.yml +24 -0
  98. agentpool/config_resources/agents.yml +109 -0
  99. agentpool/config_resources/agents_template.yml +18 -0
  100. agentpool/config_resources/agui_test.yml +18 -0
  101. agentpool/config_resources/claude_code_agent.yml +16 -0
  102. agentpool/config_resources/claude_style_subagent.md +30 -0
  103. agentpool/config_resources/external_acp_agents.yml +77 -0
  104. agentpool/config_resources/opencode_style_subagent.md +19 -0
  105. agentpool/config_resources/tts_test_agents.yml +78 -0
  106. agentpool/delegation/__init__.py +8 -0
  107. agentpool/delegation/base_team.py +504 -0
  108. agentpool/delegation/message_flow_tracker.py +39 -0
  109. agentpool/delegation/pool.py +1129 -0
  110. agentpool/delegation/team.py +325 -0
  111. agentpool/delegation/teamrun.py +343 -0
  112. agentpool/docs/__init__.py +5 -0
  113. agentpool/docs/gen_examples.py +42 -0
  114. agentpool/docs/utils.py +370 -0
  115. agentpool/functional/__init__.py +20 -0
  116. agentpool/functional/py.typed +0 -0
  117. agentpool/functional/run.py +80 -0
  118. agentpool/functional/structure.py +136 -0
  119. agentpool/hooks/__init__.py +20 -0
  120. agentpool/hooks/agent_hooks.py +247 -0
  121. agentpool/hooks/base.py +119 -0
  122. agentpool/hooks/callable.py +140 -0
  123. agentpool/hooks/command.py +180 -0
  124. agentpool/hooks/prompt.py +122 -0
  125. agentpool/jinja_filters.py +132 -0
  126. agentpool/log.py +224 -0
  127. agentpool/mcp_server/__init__.py +17 -0
  128. agentpool/mcp_server/client.py +429 -0
  129. agentpool/mcp_server/constants.py +32 -0
  130. agentpool/mcp_server/conversions.py +172 -0
  131. agentpool/mcp_server/helpers.py +47 -0
  132. agentpool/mcp_server/manager.py +232 -0
  133. agentpool/mcp_server/message_handler.py +164 -0
  134. agentpool/mcp_server/registries/__init__.py +1 -0
  135. agentpool/mcp_server/registries/official_registry_client.py +345 -0
  136. agentpool/mcp_server/registries/pulsemcp_client.py +88 -0
  137. agentpool/mcp_server/tool_bridge.py +548 -0
  138. agentpool/messaging/__init__.py +58 -0
  139. agentpool/messaging/compaction.py +928 -0
  140. agentpool/messaging/connection_manager.py +319 -0
  141. agentpool/messaging/context.py +66 -0
  142. agentpool/messaging/event_manager.py +426 -0
  143. agentpool/messaging/events.py +39 -0
  144. agentpool/messaging/message_container.py +209 -0
  145. agentpool/messaging/message_history.py +491 -0
  146. agentpool/messaging/messagenode.py +377 -0
  147. agentpool/messaging/messages.py +655 -0
  148. agentpool/messaging/processing.py +76 -0
  149. agentpool/mime_utils.py +95 -0
  150. agentpool/models/__init__.py +21 -0
  151. agentpool/models/acp_agents/__init__.py +22 -0
  152. agentpool/models/acp_agents/base.py +308 -0
  153. agentpool/models/acp_agents/mcp_capable.py +790 -0
  154. agentpool/models/acp_agents/non_mcp.py +842 -0
  155. agentpool/models/agents.py +450 -0
  156. agentpool/models/agui_agents.py +89 -0
  157. agentpool/models/claude_code_agents.py +238 -0
  158. agentpool/models/file_agents.py +116 -0
  159. agentpool/models/file_parsing.py +367 -0
  160. agentpool/models/manifest.py +658 -0
  161. agentpool/observability/__init__.py +9 -0
  162. agentpool/observability/observability_registry.py +97 -0
  163. agentpool/prompts/__init__.py +1 -0
  164. agentpool/prompts/base.py +27 -0
  165. agentpool/prompts/builtin_provider.py +75 -0
  166. agentpool/prompts/conversion_manager.py +95 -0
  167. agentpool/prompts/convert.py +96 -0
  168. agentpool/prompts/manager.py +204 -0
  169. agentpool/prompts/parts/zed.md +33 -0
  170. agentpool/prompts/prompts.py +581 -0
  171. agentpool/py.typed +0 -0
  172. agentpool/queries/tree-sitter-language-pack/README.md +7 -0
  173. agentpool/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  174. agentpool/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  175. agentpool/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  176. agentpool/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
  177. agentpool/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  178. agentpool/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  179. agentpool/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  180. agentpool/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  181. agentpool/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  182. agentpool/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  183. agentpool/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  184. agentpool/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  185. agentpool/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  186. agentpool/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  187. agentpool/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  188. agentpool/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  189. agentpool/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  190. agentpool/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
  191. agentpool/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  192. agentpool/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  193. agentpool/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  194. agentpool/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  195. agentpool/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  196. agentpool/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  197. agentpool/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  198. agentpool/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  199. agentpool/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  200. agentpool/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  201. agentpool/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  202. agentpool/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  203. agentpool/queries/tree-sitter-languages/README.md +24 -0
  204. agentpool/queries/tree-sitter-languages/c-tags.scm +9 -0
  205. agentpool/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  206. agentpool/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  207. agentpool/queries/tree-sitter-languages/dart-tags.scm +91 -0
  208. agentpool/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  209. agentpool/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  210. agentpool/queries/tree-sitter-languages/elm-tags.scm +19 -0
  211. agentpool/queries/tree-sitter-languages/fortran-tags.scm +15 -0
  212. agentpool/queries/tree-sitter-languages/go-tags.scm +30 -0
  213. agentpool/queries/tree-sitter-languages/haskell-tags.scm +3 -0
  214. agentpool/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  215. agentpool/queries/tree-sitter-languages/java-tags.scm +20 -0
  216. agentpool/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  217. agentpool/queries/tree-sitter-languages/julia-tags.scm +60 -0
  218. agentpool/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  219. agentpool/queries/tree-sitter-languages/matlab-tags.scm +10 -0
  220. agentpool/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  221. agentpool/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  222. agentpool/queries/tree-sitter-languages/php-tags.scm +26 -0
  223. agentpool/queries/tree-sitter-languages/python-tags.scm +12 -0
  224. agentpool/queries/tree-sitter-languages/ql-tags.scm +26 -0
  225. agentpool/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  226. agentpool/queries/tree-sitter-languages/rust-tags.scm +60 -0
  227. agentpool/queries/tree-sitter-languages/scala-tags.scm +65 -0
  228. agentpool/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  229. agentpool/queries/tree-sitter-languages/zig-tags.scm +3 -0
  230. agentpool/repomap.py +1231 -0
  231. agentpool/resource_providers/__init__.py +17 -0
  232. agentpool/resource_providers/aggregating.py +54 -0
  233. agentpool/resource_providers/base.py +172 -0
  234. agentpool/resource_providers/codemode/__init__.py +9 -0
  235. agentpool/resource_providers/codemode/code_executor.py +215 -0
  236. agentpool/resource_providers/codemode/default_prompt.py +19 -0
  237. agentpool/resource_providers/codemode/helpers.py +83 -0
  238. agentpool/resource_providers/codemode/progress_executor.py +212 -0
  239. agentpool/resource_providers/codemode/provider.py +150 -0
  240. agentpool/resource_providers/codemode/remote_mcp_execution.py +143 -0
  241. agentpool/resource_providers/codemode/remote_provider.py +171 -0
  242. agentpool/resource_providers/filtering.py +42 -0
  243. agentpool/resource_providers/mcp_provider.py +246 -0
  244. agentpool/resource_providers/plan_provider.py +196 -0
  245. agentpool/resource_providers/pool.py +69 -0
  246. agentpool/resource_providers/static.py +289 -0
  247. agentpool/running/__init__.py +20 -0
  248. agentpool/running/decorators.py +56 -0
  249. agentpool/running/discovery.py +101 -0
  250. agentpool/running/executor.py +284 -0
  251. agentpool/running/injection.py +111 -0
  252. agentpool/running/py.typed +0 -0
  253. agentpool/running/run_nodes.py +87 -0
  254. agentpool/server.py +122 -0
  255. agentpool/sessions/__init__.py +13 -0
  256. agentpool/sessions/manager.py +302 -0
  257. agentpool/sessions/models.py +71 -0
  258. agentpool/sessions/session.py +239 -0
  259. agentpool/sessions/store.py +163 -0
  260. agentpool/skills/__init__.py +5 -0
  261. agentpool/skills/manager.py +120 -0
  262. agentpool/skills/registry.py +210 -0
  263. agentpool/skills/skill.py +36 -0
  264. agentpool/storage/__init__.py +17 -0
  265. agentpool/storage/manager.py +419 -0
  266. agentpool/storage/serialization.py +136 -0
  267. agentpool/talk/__init__.py +13 -0
  268. agentpool/talk/registry.py +128 -0
  269. agentpool/talk/stats.py +159 -0
  270. agentpool/talk/talk.py +604 -0
  271. agentpool/tasks/__init__.py +20 -0
  272. agentpool/tasks/exceptions.py +25 -0
  273. agentpool/tasks/registry.py +33 -0
  274. agentpool/testing.py +129 -0
  275. agentpool/text_templates/__init__.py +39 -0
  276. agentpool/text_templates/system_prompt.jinja +30 -0
  277. agentpool/text_templates/tool_call_default.jinja +13 -0
  278. agentpool/text_templates/tool_call_markdown.jinja +25 -0
  279. agentpool/text_templates/tool_call_simple.jinja +5 -0
  280. agentpool/tools/__init__.py +16 -0
  281. agentpool/tools/base.py +269 -0
  282. agentpool/tools/exceptions.py +9 -0
  283. agentpool/tools/manager.py +255 -0
  284. agentpool/tools/tool_call_info.py +87 -0
  285. agentpool/ui/__init__.py +2 -0
  286. agentpool/ui/base.py +89 -0
  287. agentpool/ui/mock_provider.py +81 -0
  288. agentpool/ui/stdlib_provider.py +150 -0
  289. agentpool/utils/__init__.py +44 -0
  290. agentpool/utils/baseregistry.py +185 -0
  291. agentpool/utils/count_tokens.py +62 -0
  292. agentpool/utils/dag.py +184 -0
  293. agentpool/utils/importing.py +206 -0
  294. agentpool/utils/inspection.py +334 -0
  295. agentpool/utils/model_capabilities.py +25 -0
  296. agentpool/utils/network.py +28 -0
  297. agentpool/utils/now.py +22 -0
  298. agentpool/utils/parse_time.py +87 -0
  299. agentpool/utils/result_utils.py +35 -0
  300. agentpool/utils/signatures.py +305 -0
  301. agentpool/utils/streams.py +112 -0
  302. agentpool/utils/tasks.py +186 -0
  303. agentpool/vfs_registry.py +250 -0
  304. agentpool-2.1.9.dist-info/METADATA +336 -0
  305. agentpool-2.1.9.dist-info/RECORD +474 -0
  306. agentpool-2.1.9.dist-info/WHEEL +4 -0
  307. agentpool-2.1.9.dist-info/entry_points.txt +14 -0
  308. agentpool-2.1.9.dist-info/licenses/LICENSE +22 -0
  309. agentpool_cli/__init__.py +34 -0
  310. agentpool_cli/__main__.py +66 -0
  311. agentpool_cli/agent.py +175 -0
  312. agentpool_cli/cli_types.py +23 -0
  313. agentpool_cli/common.py +163 -0
  314. agentpool_cli/create.py +175 -0
  315. agentpool_cli/history.py +217 -0
  316. agentpool_cli/log.py +78 -0
  317. agentpool_cli/py.typed +0 -0
  318. agentpool_cli/run.py +84 -0
  319. agentpool_cli/serve_acp.py +177 -0
  320. agentpool_cli/serve_api.py +69 -0
  321. agentpool_cli/serve_mcp.py +74 -0
  322. agentpool_cli/serve_vercel.py +233 -0
  323. agentpool_cli/store.py +171 -0
  324. agentpool_cli/task.py +84 -0
  325. agentpool_cli/utils.py +104 -0
  326. agentpool_cli/watch.py +54 -0
  327. agentpool_commands/__init__.py +180 -0
  328. agentpool_commands/agents.py +199 -0
  329. agentpool_commands/base.py +45 -0
  330. agentpool_commands/commands.py +58 -0
  331. agentpool_commands/completers.py +110 -0
  332. agentpool_commands/connections.py +175 -0
  333. agentpool_commands/markdown_utils.py +31 -0
  334. agentpool_commands/models.py +62 -0
  335. agentpool_commands/prompts.py +78 -0
  336. agentpool_commands/py.typed +0 -0
  337. agentpool_commands/read.py +77 -0
  338. agentpool_commands/resources.py +210 -0
  339. agentpool_commands/session.py +48 -0
  340. agentpool_commands/tools.py +269 -0
  341. agentpool_commands/utils.py +189 -0
  342. agentpool_commands/workers.py +163 -0
  343. agentpool_config/__init__.py +53 -0
  344. agentpool_config/builtin_tools.py +265 -0
  345. agentpool_config/commands.py +237 -0
  346. agentpool_config/conditions.py +301 -0
  347. agentpool_config/converters.py +30 -0
  348. agentpool_config/durable.py +331 -0
  349. agentpool_config/event_handlers.py +600 -0
  350. agentpool_config/events.py +153 -0
  351. agentpool_config/forward_targets.py +251 -0
  352. agentpool_config/hook_conditions.py +331 -0
  353. agentpool_config/hooks.py +241 -0
  354. agentpool_config/jinja.py +206 -0
  355. agentpool_config/knowledge.py +41 -0
  356. agentpool_config/loaders.py +350 -0
  357. agentpool_config/mcp_server.py +243 -0
  358. agentpool_config/nodes.py +202 -0
  359. agentpool_config/observability.py +191 -0
  360. agentpool_config/output_types.py +55 -0
  361. agentpool_config/pool_server.py +267 -0
  362. agentpool_config/prompt_hubs.py +105 -0
  363. agentpool_config/prompts.py +185 -0
  364. agentpool_config/py.typed +0 -0
  365. agentpool_config/resources.py +33 -0
  366. agentpool_config/session.py +119 -0
  367. agentpool_config/skills.py +17 -0
  368. agentpool_config/storage.py +288 -0
  369. agentpool_config/system_prompts.py +190 -0
  370. agentpool_config/task.py +162 -0
  371. agentpool_config/teams.py +52 -0
  372. agentpool_config/tools.py +112 -0
  373. agentpool_config/toolsets.py +1033 -0
  374. agentpool_config/workers.py +86 -0
  375. agentpool_prompts/__init__.py +1 -0
  376. agentpool_prompts/braintrust_hub.py +235 -0
  377. agentpool_prompts/fabric.py +75 -0
  378. agentpool_prompts/langfuse_hub.py +79 -0
  379. agentpool_prompts/promptlayer_provider.py +59 -0
  380. agentpool_prompts/py.typed +0 -0
  381. agentpool_server/__init__.py +9 -0
  382. agentpool_server/a2a_server/__init__.py +5 -0
  383. agentpool_server/a2a_server/a2a_types.py +41 -0
  384. agentpool_server/a2a_server/server.py +190 -0
  385. agentpool_server/a2a_server/storage.py +81 -0
  386. agentpool_server/acp_server/__init__.py +22 -0
  387. agentpool_server/acp_server/acp_agent.py +786 -0
  388. agentpool_server/acp_server/acp_tools.py +43 -0
  389. agentpool_server/acp_server/commands/__init__.py +18 -0
  390. agentpool_server/acp_server/commands/acp_commands.py +594 -0
  391. agentpool_server/acp_server/commands/debug_commands.py +376 -0
  392. agentpool_server/acp_server/commands/docs_commands/__init__.py +39 -0
  393. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +169 -0
  394. agentpool_server/acp_server/commands/docs_commands/get_schema.py +176 -0
  395. agentpool_server/acp_server/commands/docs_commands/get_source.py +110 -0
  396. agentpool_server/acp_server/commands/docs_commands/git_diff.py +111 -0
  397. agentpool_server/acp_server/commands/docs_commands/helpers.py +33 -0
  398. agentpool_server/acp_server/commands/docs_commands/url_to_markdown.py +90 -0
  399. agentpool_server/acp_server/commands/spawn.py +210 -0
  400. agentpool_server/acp_server/converters.py +235 -0
  401. agentpool_server/acp_server/input_provider.py +338 -0
  402. agentpool_server/acp_server/server.py +288 -0
  403. agentpool_server/acp_server/session.py +969 -0
  404. agentpool_server/acp_server/session_manager.py +313 -0
  405. agentpool_server/acp_server/syntax_detection.py +250 -0
  406. agentpool_server/acp_server/zed_tools.md +90 -0
  407. agentpool_server/aggregating_server.py +309 -0
  408. agentpool_server/agui_server/__init__.py +11 -0
  409. agentpool_server/agui_server/server.py +128 -0
  410. agentpool_server/base.py +189 -0
  411. agentpool_server/http_server.py +164 -0
  412. agentpool_server/mcp_server/__init__.py +6 -0
  413. agentpool_server/mcp_server/server.py +314 -0
  414. agentpool_server/mcp_server/zed_wrapper.py +110 -0
  415. agentpool_server/openai_api_server/__init__.py +5 -0
  416. agentpool_server/openai_api_server/completions/__init__.py +1 -0
  417. agentpool_server/openai_api_server/completions/helpers.py +81 -0
  418. agentpool_server/openai_api_server/completions/models.py +98 -0
  419. agentpool_server/openai_api_server/responses/__init__.py +1 -0
  420. agentpool_server/openai_api_server/responses/helpers.py +74 -0
  421. agentpool_server/openai_api_server/responses/models.py +96 -0
  422. agentpool_server/openai_api_server/server.py +242 -0
  423. agentpool_server/py.typed +0 -0
  424. agentpool_storage/__init__.py +9 -0
  425. agentpool_storage/base.py +310 -0
  426. agentpool_storage/file_provider.py +378 -0
  427. agentpool_storage/formatters.py +129 -0
  428. agentpool_storage/memory_provider.py +396 -0
  429. agentpool_storage/models.py +108 -0
  430. agentpool_storage/py.typed +0 -0
  431. agentpool_storage/session_store.py +262 -0
  432. agentpool_storage/sql_provider/__init__.py +21 -0
  433. agentpool_storage/sql_provider/cli.py +146 -0
  434. agentpool_storage/sql_provider/models.py +249 -0
  435. agentpool_storage/sql_provider/queries.py +15 -0
  436. agentpool_storage/sql_provider/sql_provider.py +444 -0
  437. agentpool_storage/sql_provider/utils.py +234 -0
  438. agentpool_storage/text_log_provider.py +275 -0
  439. agentpool_toolsets/__init__.py +15 -0
  440. agentpool_toolsets/builtin/__init__.py +33 -0
  441. agentpool_toolsets/builtin/agent_management.py +239 -0
  442. agentpool_toolsets/builtin/chain.py +288 -0
  443. agentpool_toolsets/builtin/code.py +398 -0
  444. agentpool_toolsets/builtin/debug.py +291 -0
  445. agentpool_toolsets/builtin/execution_environment.py +381 -0
  446. agentpool_toolsets/builtin/file_edit/__init__.py +11 -0
  447. agentpool_toolsets/builtin/file_edit/file_edit.py +747 -0
  448. agentpool_toolsets/builtin/file_edit/fuzzy_matcher/__init__.py +5 -0
  449. agentpool_toolsets/builtin/file_edit/fuzzy_matcher/example_usage.py +311 -0
  450. agentpool_toolsets/builtin/file_edit/fuzzy_matcher/streaming_fuzzy_matcher.py +443 -0
  451. agentpool_toolsets/builtin/history.py +36 -0
  452. agentpool_toolsets/builtin/integration.py +85 -0
  453. agentpool_toolsets/builtin/skills.py +77 -0
  454. agentpool_toolsets/builtin/subagent_tools.py +324 -0
  455. agentpool_toolsets/builtin/tool_management.py +90 -0
  456. agentpool_toolsets/builtin/user_interaction.py +52 -0
  457. agentpool_toolsets/builtin/workers.py +128 -0
  458. agentpool_toolsets/composio_toolset.py +96 -0
  459. agentpool_toolsets/config_creation.py +192 -0
  460. agentpool_toolsets/entry_points.py +47 -0
  461. agentpool_toolsets/fsspec_toolset/__init__.py +7 -0
  462. agentpool_toolsets/fsspec_toolset/diagnostics.py +115 -0
  463. agentpool_toolsets/fsspec_toolset/grep.py +450 -0
  464. agentpool_toolsets/fsspec_toolset/helpers.py +631 -0
  465. agentpool_toolsets/fsspec_toolset/streaming_diff_parser.py +249 -0
  466. agentpool_toolsets/fsspec_toolset/toolset.py +1384 -0
  467. agentpool_toolsets/mcp_run_toolset.py +61 -0
  468. agentpool_toolsets/notifications.py +146 -0
  469. agentpool_toolsets/openapi.py +118 -0
  470. agentpool_toolsets/py.typed +0 -0
  471. agentpool_toolsets/search_toolset.py +202 -0
  472. agentpool_toolsets/semantic_memory_toolset.py +536 -0
  473. agentpool_toolsets/streaming_tools.py +265 -0
  474. agentpool_toolsets/vfs_toolset.py +124 -0
agentpool/repomap.py ADDED
@@ -0,0 +1,1231 @@
1
+ """Repository map generation using tree-sitter for code analysis.
2
+
3
+ Adapted from aider's repomap module with full type annotations.
4
+ Uses async fsspec filesystem for non-blocking IO operations.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections import Counter, defaultdict
10
+ from collections.abc import Callable
11
+ import colorsys
12
+ from dataclasses import dataclass
13
+ from importlib import resources
14
+ import math
15
+ import os
16
+ from pathlib import Path, PurePosixPath
17
+ import random
18
+ from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, cast
19
+
20
+ import anyio
21
+ from fsspec.asyn import AsyncFileSystem
22
+ from upathtools import is_directory
23
+
24
+
25
+ if TYPE_CHECKING:
26
+ from collections.abc import AsyncIterator, Sequence
27
+
28
+ from fsspec import AbstractFileSystem
29
+ import rustworkx as rx
30
+
31
+
32
+ # Important files that should be prioritized in repo map
33
+ ROOT_IMPORTANT_FILES: list[str] = [
34
+ "requirements.txt",
35
+ "setup.py",
36
+ "pyproject.toml",
37
+ "package.json",
38
+ "Cargo.toml",
39
+ "go.mod",
40
+ "build.gradle",
41
+ "pom.xml",
42
+ "Makefile",
43
+ "CMakeLists.txt",
44
+ "Gemfile",
45
+ "composer.json",
46
+ ".env.example",
47
+ "Dockerfile",
48
+ "docker-compose.yml",
49
+ "README.md",
50
+ "README.rst",
51
+ "README",
52
+ ]
53
+
54
+ NORMALIZED_ROOT_IMPORTANT_FILES: set[str] = set(ROOT_IMPORTANT_FILES)
55
+
56
+ # Type aliases
57
+ type TokenCounter = Callable[[str], int]
58
+
59
+
60
+ class Tag(NamedTuple):
61
+ """Represents a code tag (definition or reference)."""
62
+
63
+ rel_fname: str
64
+ fname: str
65
+ line: int
66
+ name: str
67
+ kind: str
68
+ end_line: int = -1
69
+ signature_end_line: int = -1
70
+
71
+
72
+ type RankedTag = Tag | tuple[str]
73
+
74
+
75
+ @dataclass
76
+ class RepoMapResult:
77
+ """Result of repository map generation with metadata."""
78
+
79
+ content: str
80
+ total_files_processed: int
81
+ total_tags_found: int
82
+ total_files_with_tags: int
83
+ included_files: int
84
+ included_tags: int
85
+ truncated: bool
86
+ coverage_ratio: float
87
+
88
+
89
+ @dataclass
90
+ class FileInfo:
91
+ """Information about a file from fsspec."""
92
+
93
+ path: str
94
+ size: int
95
+ mtime: float | None = None
96
+ type: str = "file"
97
+
98
+
99
+ CACHE_VERSION = 5
100
+
101
+ # Thresholds
102
+ MIN_TOKEN_SAMPLE_SIZE: int = 256
103
+ MIN_IDENT_LENGTH: int = 4
104
+ MAX_DEFINERS_THRESHOLD: int = 5
105
+
106
+
107
+ def is_important(fname: str) -> bool:
108
+ """Check if a file is considered important (like config files)."""
109
+ if fname in NORMALIZED_ROOT_IMPORTANT_FILES:
110
+ return True
111
+ basename = PurePosixPath(fname).name
112
+ return basename in NORMALIZED_ROOT_IMPORTANT_FILES
113
+
114
+
115
+ def get_rel_path(path: str, root: str) -> str:
116
+ """Get relative path from root."""
117
+ if path.startswith(root):
118
+ rel = path[len(root) :]
119
+ return rel.lstrip("/")
120
+ return path
121
+
122
+
123
+ class RepoMap:
124
+ """Generates a map of a repository's code structure using tree-sitter.
125
+
126
+ Uses async fsspec filesystem for non-blocking IO operations.
127
+ """
128
+
129
+ TAGS_CACHE_DIR: ClassVar[str] = f".agentpool.tags.cache.v{CACHE_VERSION}"
130
+ warned_files: ClassVar[set[str]] = set()
131
+
132
+ def __init__(
133
+ self,
134
+ fs: AbstractFileSystem,
135
+ root_path: str | None = None,
136
+ *,
137
+ max_tokens: int = 1024,
138
+ max_line_length: int = 250,
139
+ token_counter: TokenCounter | None = None,
140
+ ) -> None:
141
+ """Initialize RepoMap.
142
+
143
+ Args:
144
+ fs: Async fsspec filesystem instance.
145
+ root_path: Root directory path in the filesystem.
146
+ max_tokens: Maximum tokens for the generated map.
147
+ max_line_length: Maximum character length for output lines.
148
+ token_counter: Callable to count tokens. Defaults to len(text) / 4.
149
+ """
150
+ from fsspec.implementations.asyn_wrapper import AsyncFileSystemWrapper
151
+
152
+ self.fs = fs if isinstance(fs, AsyncFileSystem) else AsyncFileSystemWrapper(fs)
153
+ self.root_path = root_path.rstrip("/") if root_path else self.fs.root_marker
154
+ self.max_tokens = max_tokens
155
+ self.max_line_length = max_line_length
156
+ self._token_counter = token_counter
157
+
158
+ self.tree_cache: dict[tuple[str, tuple[int, ...], float | None], str] = {}
159
+ self.tree_context_cache: dict[str, dict[str, Any]] = {}
160
+ self.TAGS_CACHE: dict[str, Any] = {}
161
+
162
+ def token_count(self, text: str) -> float:
163
+ """Estimate token count for text."""
164
+ if self._token_counter:
165
+ len_text = len(text)
166
+ if len_text < MIN_TOKEN_SAMPLE_SIZE:
167
+ return self._token_counter(text)
168
+
169
+ lines = text.splitlines(keepends=True)
170
+ num_lines = len(lines)
171
+ step = num_lines // 100 or 1
172
+ sampled_lines = lines[::step]
173
+ sample_text = "".join(sampled_lines)
174
+ sample_tokens = self._token_counter(sample_text)
175
+ return sample_tokens / len(sample_text) * len_text
176
+
177
+ return len(text) / 4
178
+
179
+ async def _cat_file(self, path: str) -> str | None:
180
+ """Read file content as text."""
181
+ try:
182
+ content = await self.fs._cat_file(path)
183
+ if isinstance(content, bytes):
184
+ return content.decode("utf-8")
185
+ except (OSError, UnicodeDecodeError):
186
+ return None
187
+ else:
188
+ return content # type: ignore[no-any-return]
189
+
190
+ async def _info(self, path: str) -> FileInfo | None:
191
+ """Get file info."""
192
+ try:
193
+ info = await self.fs._info(path)
194
+ return FileInfo(
195
+ path=info.get("name", path),
196
+ size=info.get("size", 0),
197
+ mtime=info.get("mtime"),
198
+ type=info.get("type", "file"),
199
+ )
200
+ except (OSError, FileNotFoundError):
201
+ return None
202
+
203
+ async def _ls(self, path: str, detail: bool = True) -> list[dict[str, Any]]:
204
+ """List directory contents."""
205
+ try:
206
+ return await self.fs._ls(path, detail=detail) # type: ignore[no-any-return]
207
+ except (OSError, FileNotFoundError):
208
+ return []
209
+
210
+ async def find_files(self, path: str, pattern: str = "**/*.py") -> list[str]:
211
+ """Find files matching pattern recursively."""
212
+ results: list[str] = []
213
+
214
+ async def _recurse(current_path: str) -> None:
215
+ entries = await self._ls(current_path, detail=True)
216
+ for entry in entries:
217
+ entry_path = entry.get("name", "")
218
+ entry_type = entry.get("type", "")
219
+
220
+ if await is_directory(self.fs, entry_path, entry_type=entry_type):
221
+ await _recurse(entry_path)
222
+ # It's a file - process it
223
+ elif pattern == "**/*.py":
224
+ if entry_path.endswith(".py"):
225
+ results.append(entry_path)
226
+ else:
227
+ results.append(entry_path)
228
+
229
+ await _recurse(path)
230
+ return results
231
+
232
+ async def get_file_map(
233
+ self,
234
+ fname: str,
235
+ max_tokens: int = 2048,
236
+ ) -> str | None:
237
+ """Generate a structure map for a single file.
238
+
239
+ Unlike get_map which uses PageRank across multiple files, this method
240
+ shows all definitions in a single file with line numbers.
241
+
242
+ Args:
243
+ fname: Absolute path to the file
244
+ max_tokens: Maximum tokens for output (approximate)
245
+
246
+ Returns:
247
+ Formatted structure map or None if no tags found
248
+ """
249
+ rel_fname = get_rel_path(fname, self.root_path)
250
+
251
+ # Get all definition tags for this file
252
+ tags = await self._get_tags(fname, rel_fname)
253
+ def_tags = [t for t in tags if t.kind == "def"]
254
+
255
+ if not def_tags:
256
+ return None
257
+
258
+ # Build line ranges for rendering
259
+ lois: list[int] = []
260
+ line_ranges: dict[int, int] = {}
261
+
262
+ for tag in def_tags:
263
+ if tag.signature_end_line >= tag.line:
264
+ lois.extend(range(tag.line, tag.signature_end_line + 1))
265
+ else:
266
+ lois.append(tag.line)
267
+ if tag.end_line >= 0:
268
+ line_ranges[tag.line] = tag.end_line
269
+
270
+ # Render the tree
271
+ tree_output = await self._render_tree(fname, rel_fname, lois, line_ranges)
272
+
273
+ # Add header with file info
274
+ info = await self._info(fname)
275
+ size_info = f", {info.size} bytes" if info else ""
276
+ lines = (await self._cat_file(fname) or "").count("\n") + 1
277
+ tokens = self.token_count(tree_output)
278
+
279
+ header = (
280
+ f"# File: {rel_fname} ({lines} lines{size_info})\n"
281
+ f"# Structure map ({tokens} tokens). Use offset/limit to read sections.\n\n"
282
+ )
283
+
284
+ result = header + f"{rel_fname}:\n" + tree_output
285
+
286
+ # Truncate if needed
287
+ max_chars = max_tokens * 4
288
+ if len(result) > max_chars:
289
+ result = result[:max_chars] + "\n... [truncated]\n"
290
+
291
+ return result
292
+
293
+ async def get_map(
294
+ self,
295
+ files: Sequence[str],
296
+ *,
297
+ exclude: set[str] | None = None,
298
+ boost_files: set[str] | None = None,
299
+ boost_idents: set[str] | None = None,
300
+ ) -> str | None:
301
+ """Generate a repository map for the given files.
302
+
303
+ Args:
304
+ files: File paths to include in the map.
305
+ exclude: Files to exclude from the map output (but still used for ranking).
306
+ boost_files: Files to boost in ranking.
307
+ boost_idents: Identifiers to boost in ranking.
308
+ """
309
+ if not files:
310
+ return None
311
+
312
+ exclude = exclude or set()
313
+ boost_files = boost_files or set()
314
+ boost_idents = boost_idents or set()
315
+
316
+ return await self._get_ranked_tags_map(
317
+ files=files,
318
+ exclude=exclude,
319
+ boost_files=boost_files,
320
+ boost_idents=boost_idents,
321
+ )
322
+
323
+ async def get_map_with_metadata(
324
+ self,
325
+ files: Sequence[str],
326
+ *,
327
+ exclude: set[str] | None = None,
328
+ boost_files: set[str] | None = None,
329
+ boost_idents: set[str] | None = None,
330
+ ) -> RepoMapResult:
331
+ """Generate a repository map with detailed metadata.
332
+
333
+ Args:
334
+ files: File paths to include in the map.
335
+ exclude: Files to exclude from the map output (but still used for ranking).
336
+ boost_files: Files to boost in ranking.
337
+ boost_idents: Identifiers to boost in ranking.
338
+ """
339
+ import re
340
+
341
+ if not files:
342
+ return RepoMapResult(
343
+ content="",
344
+ total_files_processed=0,
345
+ total_tags_found=0,
346
+ total_files_with_tags=0,
347
+ included_files=0,
348
+ included_tags=0,
349
+ truncated=False,
350
+ coverage_ratio=0.0,
351
+ )
352
+
353
+ exclude = exclude or set()
354
+ boost_files = boost_files or set()
355
+ boost_idents = boost_idents or set()
356
+
357
+ ranked_tags = await self._get_ranked_tags(
358
+ files=files,
359
+ exclude=exclude,
360
+ boost_files=boost_files,
361
+ boost_idents=boost_idents,
362
+ )
363
+
364
+ total_tags = len([tag for tag in ranked_tags if isinstance(tag, Tag)])
365
+ all_files_with_tags = {tag.fname if isinstance(tag, Tag) else tag[0] for tag in ranked_tags}
366
+ total_files_with_tags = len(all_files_with_tags)
367
+
368
+ content = await self._get_ranked_tags_map(
369
+ files=files,
370
+ exclude=exclude,
371
+ boost_files=boost_files,
372
+ boost_idents=boost_idents,
373
+ )
374
+
375
+ if content:
376
+ included_files = len(set(re.findall(r"^([^:\s]+):", content, re.MULTILINE)))
377
+ included_tags = content.count(" def ") + content.count("class ")
378
+ else:
379
+ included_files = included_tags = 0
380
+
381
+ coverage_ratio = (
382
+ included_files / total_files_with_tags if total_files_with_tags > 0 else 0.0
383
+ )
384
+ truncated = included_files < total_files_with_tags or included_tags < total_tags
385
+
386
+ return RepoMapResult(
387
+ content=content or "",
388
+ total_files_processed=len(files),
389
+ total_tags_found=total_tags,
390
+ total_files_with_tags=total_files_with_tags,
391
+ included_files=included_files,
392
+ included_tags=included_tags,
393
+ truncated=truncated,
394
+ coverage_ratio=coverage_ratio,
395
+ )
396
+
397
+ async def _get_tags(self, fname: str, rel_fname: str) -> list[Tag]:
398
+ """Get tags for a file, using cache when possible."""
399
+ info = await self._info(fname)
400
+ if info is None:
401
+ return []
402
+
403
+ file_mtime = info.mtime
404
+ cache_key = fname
405
+
406
+ cached = self.TAGS_CACHE.get(cache_key)
407
+ if cached is not None and cached.get("mtime") == file_mtime:
408
+ return cast(list[Tag], cached["data"])
409
+
410
+ data = [tag async for tag in self._get_tags_raw(fname, rel_fname)]
411
+
412
+ self.TAGS_CACHE[cache_key] = {"mtime": file_mtime, "data": data}
413
+ return data
414
+
415
+ async def _get_tags_raw(self, fname: str, rel_fname: str) -> AsyncIterator[Tag]:
416
+ """Extract tags from a file using tree-sitter."""
417
+ from grep_ast import filename_to_lang # type: ignore[import-untyped]
418
+ from grep_ast.tsl import get_language, get_parser # type: ignore[import-untyped]
419
+ from pygments.lexers import guess_lexer_for_filename
420
+ from pygments.token import Token
421
+ from tree_sitter import Query, QueryCursor
422
+
423
+ lang = filename_to_lang(fname)
424
+ if not lang:
425
+ return
426
+
427
+ try:
428
+ language = get_language(lang) # pyright: ignore[reportArgumentType]
429
+ parser = get_parser(lang) # pyright: ignore[reportArgumentType]
430
+ except Exception: # noqa: BLE001
431
+ return
432
+
433
+ query_scm = get_scm_fname(lang)
434
+ if not query_scm or not query_scm.exists():
435
+ return
436
+ query_scm_text = query_scm.read_text("utf-8")
437
+
438
+ code = await self._cat_file(fname)
439
+ if not code:
440
+ return
441
+
442
+ tree = parser.parse(bytes(code, "utf-8"))
443
+ query = Query(language, query_scm_text)
444
+ cursor = QueryCursor(query)
445
+
446
+ saw: set[str] = set()
447
+ all_nodes: list[tuple[Any, str]] = []
448
+
449
+ for _pattern_index, captures_dict in cursor.matches(tree.root_node):
450
+ for tag, nodes in captures_dict.items():
451
+ all_nodes.extend((node, tag) for node in nodes)
452
+
453
+ for node, tag in all_nodes:
454
+ if tag.startswith("name.definition."):
455
+ kind = "def"
456
+ elif tag.startswith("name.reference."):
457
+ kind = "ref"
458
+ else:
459
+ continue
460
+
461
+ saw.add(kind)
462
+ name = node.text.decode("utf-8")
463
+ line = node.start_point[0]
464
+
465
+ end_line = -1
466
+ signature_end_line = -1
467
+ if kind == "def" and node.parent is not None:
468
+ end_line = node.parent.end_point[0]
469
+ for child in node.parent.children:
470
+ if child.type in ("block", "body", "compound_statement"):
471
+ signature_end_line = child.start_point[0] - 1
472
+ break
473
+ signature_end_line = max(signature_end_line, line)
474
+
475
+ yield Tag(
476
+ rel_fname=rel_fname,
477
+ fname=fname,
478
+ name=name,
479
+ kind=kind,
480
+ line=line,
481
+ end_line=end_line,
482
+ signature_end_line=signature_end_line,
483
+ )
484
+
485
+ if "ref" in saw or "def" in saw:
486
+ return
487
+
488
+ try:
489
+ lexer = guess_lexer_for_filename(fname, code)
490
+ except Exception: # noqa: BLE001
491
+ return
492
+
493
+ tokens = list(lexer.get_tokens(code))
494
+ name_tokens = [token[1] for token in tokens if token[0] in Token.Name] # type: ignore[comparison-overlap]
495
+
496
+ for token in name_tokens:
497
+ yield Tag(rel_fname=rel_fname, fname=fname, name=token, kind="ref", line=-1)
498
+
499
+ async def _get_ranked_tags( # noqa: PLR0915
500
+ self,
501
+ files: Sequence[str],
502
+ exclude: set[str],
503
+ boost_files: set[str],
504
+ boost_idents: set[str],
505
+ ) -> list[RankedTag]:
506
+ """Rank tags using PageRank algorithm."""
507
+ import rustworkx as rx
508
+
509
+ defines: defaultdict[str, set[str]] = defaultdict(set)
510
+ references: defaultdict[str, list[str]] = defaultdict(list)
511
+ definitions: defaultdict[tuple[str, str], set[Tag]] = defaultdict(set)
512
+ personalization: dict[str, float] = {}
513
+ exclude_rel_fnames: set[str] = set()
514
+
515
+ sorted_fnames = sorted(files)
516
+ personalize = 100 / len(sorted_fnames) if sorted_fnames else 0
517
+
518
+ for fname in sorted_fnames:
519
+ info = await self._info(fname)
520
+ if info is None or info.type != "file":
521
+ if fname not in self.warned_files:
522
+ self.warned_files.add(fname)
523
+ continue
524
+
525
+ rel_fname = get_rel_path(fname, self.root_path)
526
+ current_pers = 0.0
527
+
528
+ if fname in exclude:
529
+ current_pers += personalize
530
+ exclude_rel_fnames.add(rel_fname)
531
+
532
+ if fname in boost_files or rel_fname in boost_files:
533
+ current_pers = max(current_pers, personalize)
534
+
535
+ path_obj = PurePosixPath(rel_fname)
536
+ path_components = set(path_obj.parts)
537
+ basename_with_ext = path_obj.name
538
+ basename_without_ext = path_obj.stem
539
+ components_to_check = path_components.union({basename_with_ext, basename_without_ext})
540
+
541
+ if components_to_check.intersection(boost_idents):
542
+ current_pers += personalize
543
+
544
+ if current_pers > 0:
545
+ personalization[rel_fname] = current_pers
546
+
547
+ tags = await self._get_tags(fname, rel_fname)
548
+ for tag in tags:
549
+ if tag.kind == "def":
550
+ defines[tag.name].add(rel_fname)
551
+ key = (rel_fname, tag.name)
552
+ definitions[key].add(tag)
553
+ elif tag.kind == "ref":
554
+ references[tag.name].append(rel_fname)
555
+
556
+ if not references:
557
+ references = defaultdict(list, {k: list(v) for k, v in defines.items()})
558
+
559
+ idents = set(defines.keys()).intersection(set(references.keys()))
560
+
561
+ graph: rx.PyDiGraph[str, dict[str, Any]] = rx.PyDiGraph(multigraph=True)
562
+ node_to_idx: dict[str, int] = {}
563
+ idx_to_node: dict[int, str] = {}
564
+
565
+ def get_or_add_node(name: str) -> int:
566
+ if name not in node_to_idx:
567
+ idx = graph.add_node(name)
568
+ node_to_idx[name] = idx
569
+ idx_to_node[idx] = name
570
+ return node_to_idx[name]
571
+
572
+ for ident in defines:
573
+ if ident in references:
574
+ continue
575
+ for definer in defines[ident]:
576
+ idx = get_or_add_node(definer)
577
+ graph.add_edge(idx, idx, {"weight": 0.1, "ident": ident})
578
+
579
+ for ident in idents:
580
+ definers = defines[ident]
581
+ mul = 1.0
582
+
583
+ is_snake = ("_" in ident) and any(c.isalpha() for c in ident)
584
+ is_kebab = ("-" in ident) and any(c.isalpha() for c in ident)
585
+ is_camel = any(c.isupper() for c in ident) and any(c.islower() for c in ident)
586
+
587
+ if ident in boost_idents:
588
+ mul *= 10
589
+ if (is_snake or is_kebab or is_camel) and len(ident) >= MIN_IDENT_LENGTH:
590
+ mul *= 10
591
+ if ident.startswith("_"):
592
+ mul *= 0.1
593
+ if len(defines[ident]) > MAX_DEFINERS_THRESHOLD:
594
+ mul *= 0.1
595
+
596
+ for referencer, num_refs in Counter(references[ident]).items():
597
+ for definer in definers:
598
+ use_mul = mul
599
+ if referencer in exclude_rel_fnames:
600
+ use_mul *= 50
601
+ scaled_refs = math.sqrt(num_refs)
602
+ src_idx = get_or_add_node(referencer)
603
+ dst_idx = get_or_add_node(definer)
604
+ graph.add_edge(
605
+ src_idx, dst_idx, {"weight": use_mul * scaled_refs, "ident": ident}
606
+ )
607
+
608
+ if not graph.num_nodes():
609
+ return []
610
+
611
+ pers_idx: dict[int, float] | None = None
612
+ if personalization:
613
+ pers_idx = {
614
+ node_to_idx[name]: val
615
+ for name, val in personalization.items()
616
+ if name in node_to_idx
617
+ }
618
+
619
+ try:
620
+ ranked_idx = rx.pagerank(
621
+ graph,
622
+ weight_fn=lambda e: e["weight"],
623
+ personalization=pers_idx,
624
+ dangling=pers_idx,
625
+ )
626
+ except ZeroDivisionError:
627
+ try:
628
+ ranked_idx = rx.pagerank(graph, weight_fn=lambda e: e["weight"])
629
+ except ZeroDivisionError:
630
+ return []
631
+
632
+ ranked: dict[str, float] = {idx_to_node[idx]: rank for idx, rank in ranked_idx.items()}
633
+
634
+ ranked_definitions: defaultdict[tuple[str, str], float] = defaultdict(float)
635
+ for src_idx in graph.node_indices():
636
+ src_name = idx_to_node[src_idx]
637
+ src_rank = ranked[src_name]
638
+ out_edges = graph.out_edges(src_idx)
639
+ total_weight = sum(edge_data["weight"] for _, _, edge_data in out_edges)
640
+ if total_weight == 0:
641
+ continue
642
+ for _, dst_idx, edge_data in out_edges:
643
+ edge_rank = src_rank * edge_data["weight"] / total_weight
644
+ ident = edge_data["ident"]
645
+ dst_name = idx_to_node[dst_idx]
646
+ ranked_definitions[(dst_name, ident)] += edge_rank
647
+
648
+ ranked_tags: list[RankedTag] = []
649
+ sorted_definitions = sorted(
650
+ ranked_definitions.items(), reverse=True, key=lambda x: (x[1], x[0])
651
+ )
652
+
653
+ for (fname, ident), _rank in sorted_definitions:
654
+ if fname in exclude_rel_fnames:
655
+ continue
656
+ ranked_tags += list(definitions.get((fname, ident), []))
657
+
658
+ rel_fnames_without_tags = {get_rel_path(fname, self.root_path) for fname in files}
659
+ for fname in exclude:
660
+ rel = get_rel_path(fname, self.root_path)
661
+ rel_fnames_without_tags.discard(rel)
662
+
663
+ fnames_already_included = {
664
+ tag.rel_fname if isinstance(tag, Tag) else tag[0] for tag in ranked_tags
665
+ }
666
+
667
+ top_rank = sorted([(rank, node) for (node, rank) in ranked.items()], reverse=True)
668
+ for _rank, fname in top_rank:
669
+ if fname in rel_fnames_without_tags:
670
+ rel_fnames_without_tags.remove(fname)
671
+ if fname not in fnames_already_included:
672
+ ranked_tags.append((fname,))
673
+
674
+ for fname in rel_fnames_without_tags:
675
+ ranked_tags.append((fname,))
676
+
677
+ return ranked_tags
678
+
679
+ async def _get_ranked_tags_map(
680
+ self,
681
+ files: Sequence[str],
682
+ exclude: set[str],
683
+ boost_files: set[str],
684
+ boost_idents: set[str],
685
+ max_tokens: int | None = None,
686
+ ) -> str | None:
687
+ """Generate ranked tags map within token budget."""
688
+ max_tokens = max_tokens or self.max_tokens
689
+
690
+ ranked_tags = await self._get_ranked_tags(
691
+ files=files,
692
+ exclude=exclude,
693
+ boost_files=boost_files,
694
+ boost_idents=boost_idents,
695
+ )
696
+
697
+ if not ranked_tags:
698
+ return None
699
+
700
+ num_tags = len(ranked_tags)
701
+ lower_bound = 0
702
+ upper_bound = num_tags
703
+ best_tree: str | None = None
704
+ best_tree_tokens: float = 0
705
+ exclude_rel_fnames = {get_rel_path(fname, self.root_path) for fname in exclude}
706
+ self.tree_cache = {}
707
+
708
+ middle = min(int(max_tokens // 25), num_tags)
709
+
710
+ while lower_bound <= upper_bound:
711
+ tree = await self._to_tree(ranked_tags[:middle], exclude_rel_fnames)
712
+ num_tokens = self.token_count(tree)
713
+ pct_err = abs(num_tokens - max_tokens) / max_tokens if max_tokens else 0
714
+ ok_err = 0.15
715
+
716
+ if (num_tokens <= max_tokens and num_tokens > best_tree_tokens) or pct_err < ok_err:
717
+ best_tree = tree
718
+ best_tree_tokens = num_tokens
719
+ if pct_err < ok_err:
720
+ break
721
+
722
+ if num_tokens < max_tokens:
723
+ lower_bound = middle + 1
724
+ else:
725
+ upper_bound = middle - 1
726
+
727
+ middle = (lower_bound + upper_bound) // 2
728
+
729
+ return best_tree
730
+
731
+ async def _render_tree(
732
+ self,
733
+ abs_fname: str,
734
+ rel_fname: str,
735
+ lois: list[int],
736
+ line_ranges: dict[int, int] | None = None,
737
+ ) -> str:
738
+ """Render a tree representation of a file with lines of interest."""
739
+ import re
740
+
741
+ from grep_ast import TreeContext
742
+
743
+ if line_ranges is None:
744
+ line_ranges = {}
745
+
746
+ info = await self._info(abs_fname)
747
+ mtime = info.mtime if info else None
748
+
749
+ key = (rel_fname, tuple(sorted(lois)), mtime)
750
+ if key in self.tree_cache:
751
+ return self.tree_cache[key]
752
+
753
+ cached = self.tree_context_cache.get(rel_fname)
754
+ if cached is None or cached["mtime"] != mtime:
755
+ code = await self._cat_file(abs_fname) or ""
756
+ if not code.endswith("\n"):
757
+ code += "\n"
758
+
759
+ context = TreeContext(
760
+ rel_fname,
761
+ code,
762
+ child_context=False,
763
+ last_line=False,
764
+ margin=0,
765
+ mark_lois=False,
766
+ loi_pad=0,
767
+ show_top_of_file_parent_scope=False,
768
+ )
769
+ self.tree_context_cache[rel_fname] = {"context": context, "mtime": mtime}
770
+
771
+ context = self.tree_context_cache[rel_fname]["context"]
772
+ context.lines_of_interest = set()
773
+ context.add_lines_of_interest(lois)
774
+ context.add_context()
775
+ res: str = context.format()
776
+
777
+ code = await self._cat_file(abs_fname) or ""
778
+ code_lines = code.splitlines()
779
+ lois_set = set(lois)
780
+
781
+ def_pattern = re.compile(r"^(.*?)(class\s+\w+|def\s+\w+|async\s+def\s+\w+)")
782
+
783
+ result_lines = []
784
+ for output_line in res.splitlines():
785
+ modified_line = output_line
786
+ match = def_pattern.search(output_line)
787
+ if match:
788
+ stripped = output_line.lstrip("│ \t")
789
+ for line_num in lois_set:
790
+ if line_num < len(code_lines):
791
+ orig_line = code_lines[line_num].strip()
792
+ if orig_line and stripped.startswith(orig_line.split("(")[0].split(":")[0]):
793
+ name_match = re.search(
794
+ r"(class\s+\w+|def\s+\w+|async\s+def\s+\w+)", output_line
795
+ )
796
+ if name_match:
797
+ start_line_display = line_num + 1
798
+ end_line = line_ranges.get(line_num, -1)
799
+ if end_line >= 0 and end_line != line_num:
800
+ end_line_display = end_line + 1
801
+ line_info = f" # [{start_line_display}-{end_line_display}]"
802
+ else:
803
+ line_info = f" # [{start_line_display}]"
804
+ modified_line = f"{output_line}{line_info}"
805
+ break
806
+ result_lines.append(modified_line)
807
+
808
+ res = "\n".join(result_lines)
809
+ if result_lines:
810
+ res += "\n"
811
+
812
+ self.tree_cache[key] = res
813
+ return res
814
+
815
+ async def _to_tree(self, tags: list[RankedTag], exclude_rel_fnames: set[str]) -> str:
816
+ """Convert ranked tags to a tree representation."""
817
+ if not tags:
818
+ return ""
819
+
820
+ cur_fname: str | None = None
821
+ cur_abs_fname: str | None = None
822
+ lois: list[int] | None = None
823
+ line_ranges: dict[int, int] | None = None
824
+ output = ""
825
+
826
+ dummy_tag: tuple[None] = (None,)
827
+ for tag in [*sorted(tags, key=lambda t: str(t[0]) if t[0] else ""), dummy_tag]:
828
+ this_rel_fname = tag[0] if isinstance(tag, Tag) else (tag[0] if tag[0] else None)
829
+
830
+ if isinstance(tag, Tag):
831
+ this_rel_fname = tag.rel_fname
832
+ elif tag[0] is not None:
833
+ this_rel_fname = tag[0]
834
+ else:
835
+ this_rel_fname = None
836
+
837
+ if this_rel_fname and this_rel_fname in exclude_rel_fnames:
838
+ continue
839
+
840
+ if this_rel_fname != cur_fname:
841
+ if lois is not None and cur_fname and cur_abs_fname:
842
+ output += "\n"
843
+ output += cur_fname + ":\n"
844
+ output += await self._render_tree(cur_abs_fname, cur_fname, lois, line_ranges)
845
+ lois = None
846
+ line_ranges = None
847
+ elif cur_fname:
848
+ output += "\n" + cur_fname + "\n"
849
+
850
+ if isinstance(tag, Tag):
851
+ lois = []
852
+ line_ranges = {}
853
+ cur_abs_fname = tag.fname
854
+ cur_fname = this_rel_fname
855
+
856
+ if lois is not None and line_ranges is not None and isinstance(tag, Tag):
857
+ if tag.signature_end_line >= tag.line:
858
+ lois.extend(range(tag.line, tag.signature_end_line + 1))
859
+ else:
860
+ lois.append(tag.line)
861
+ if tag.end_line >= 0:
862
+ line_ranges[tag.line] = tag.end_line
863
+
864
+ return "\n".join([line[: self.max_line_length] for line in output.splitlines()]) + "\n"
865
+
866
+
867
+ def get_random_color() -> str:
868
+ """Generate a random pastel color."""
869
+ hue = random.random()
870
+ r, g, b = (int(x * 255) for x in colorsys.hsv_to_rgb(hue, 1, 0.75))
871
+ return f"#{r:02x}{g:02x}{b:02x}"
872
+
873
+
874
+ def get_scm_fname(lang: str) -> Path | None:
875
+ """Get the path to the SCM query file for a language."""
876
+ package = __package__ or "agentpool"
877
+ subdir = "tree-sitter-language-pack"
878
+ try:
879
+ path = resources.files(package).joinpath("queries", subdir, f"{lang}-tags.scm")
880
+ if path.is_file():
881
+ return Path(str(path))
882
+ except KeyError:
883
+ pass
884
+
885
+ subdir = "tree-sitter-languages"
886
+ try:
887
+ path = resources.files(package).joinpath("queries", subdir, f"{lang}-tags.scm")
888
+ return Path(str(path))
889
+ except KeyError:
890
+ return None
891
+
892
+
893
+ def get_supported_languages() -> set[str]:
894
+ """Get set of languages that have tree-sitter tag support."""
895
+ from grep_ast.parsers import PARSERS # type: ignore[import-untyped]
896
+
897
+ supported = set()
898
+ for lang in set(PARSERS.values()):
899
+ scm = get_scm_fname(lang)
900
+ if scm and scm.exists():
901
+ supported.add(lang)
902
+ return supported
903
+
904
+
905
+ def is_language_supported(fname: str) -> bool:
906
+ """Check if a file's language supports tree-sitter tags."""
907
+ from grep_ast import filename_to_lang
908
+
909
+ lang = filename_to_lang(fname)
910
+ if not lang:
911
+ return False
912
+ scm = get_scm_fname(lang)
913
+ return scm is not None and scm.exists()
914
+
915
+
916
+ def get_tags_from_content(content: str, filename: str) -> list[Tag]:
917
+ """Extract tags from content without filesystem IO.
918
+
919
+ Args:
920
+ content: File content as string
921
+ filename: Filename for language detection (e.g. "foo.py")
922
+
923
+ Returns:
924
+ List of Tag objects (definitions and references)
925
+ """
926
+ from grep_ast import filename_to_lang
927
+ from grep_ast.tsl import get_language, get_parser
928
+ from pygments.lexers import guess_lexer_for_filename
929
+ from pygments.token import Token
930
+ from tree_sitter import Query, QueryCursor
931
+ from tree_sitter_language_pack import SupportedLanguage
932
+
933
+ lang = cast(SupportedLanguage, filename_to_lang(filename))
934
+ if not lang:
935
+ return []
936
+
937
+ try:
938
+ language = get_language(lang)
939
+ parser = get_parser(lang)
940
+ except Exception: # noqa: BLE001
941
+ return []
942
+
943
+ query_scm = get_scm_fname(lang)
944
+ if not query_scm or not query_scm.exists():
945
+ return []
946
+ query_scm_text = query_scm.read_text("utf-8")
947
+
948
+ tree = parser.parse(bytes(content, "utf-8"))
949
+ query = Query(language, query_scm_text)
950
+ cursor = QueryCursor(query)
951
+
952
+ tags: list[Tag] = []
953
+ saw: set[str] = set()
954
+ all_nodes: list[tuple[Any, str]] = []
955
+
956
+ for _pattern_index, captures_dict in cursor.matches(tree.root_node):
957
+ for tag, nodes in captures_dict.items():
958
+ all_nodes.extend((node, tag) for node in nodes)
959
+
960
+ for node, tag in all_nodes:
961
+ if tag.startswith("name.definition."):
962
+ kind = "def"
963
+ elif tag.startswith("name.reference."):
964
+ kind = "ref"
965
+ else:
966
+ continue
967
+
968
+ saw.add(kind)
969
+ name = node.text.decode("utf-8")
970
+ line = node.start_point[0]
971
+
972
+ end_line = -1
973
+ signature_end_line = -1
974
+ if kind == "def" and node.parent is not None:
975
+ end_line = node.parent.end_point[0]
976
+ for child in node.parent.children:
977
+ if child.type in ("block", "body", "compound_statement"):
978
+ signature_end_line = child.start_point[0] - 1
979
+ break
980
+ signature_end_line = max(signature_end_line, line)
981
+
982
+ tags.append(
983
+ Tag(
984
+ rel_fname=filename,
985
+ fname=filename,
986
+ name=name,
987
+ kind=kind,
988
+ line=line,
989
+ end_line=end_line,
990
+ signature_end_line=signature_end_line,
991
+ )
992
+ )
993
+
994
+ if "ref" in saw or "def" in saw:
995
+ return tags
996
+
997
+ # Fallback to pygments lexer for references
998
+ try:
999
+ lexer = guess_lexer_for_filename(filename, content)
1000
+ except Exception: # noqa: BLE001
1001
+ return tags
1002
+
1003
+ tokens = list(lexer.get_tokens(content))
1004
+ name_tokens = [token[1] for token in tokens if token[0] in Token.Name] # type: ignore[comparison-overlap]
1005
+
1006
+ for token in name_tokens:
1007
+ tags.append(Tag(rel_fname=filename, fname=filename, name=token, kind="ref", line=-1)) # noqa: PERF401
1008
+
1009
+ return tags
1010
+
1011
+
1012
+ def get_file_map_from_content( # noqa: PLR0915
1013
+ content: str,
1014
+ filename: str,
1015
+ max_tokens: int = 2048,
1016
+ ) -> str | None:
1017
+ """Generate structure map from content without filesystem IO.
1018
+
1019
+ Pure function for generating a file structure map when you already
1020
+ have the content in memory (e.g., from ACP embedded resources).
1021
+
1022
+ Args:
1023
+ content: File content as string
1024
+ filename: Filename for language detection (e.g. "foo.py", "src/main.rs")
1025
+ max_tokens: Maximum tokens for output (approximate)
1026
+
1027
+ Returns:
1028
+ Formatted structure map or None if language not supported
1029
+ """
1030
+ from grep_ast import TreeContext
1031
+
1032
+ if not is_language_supported(filename):
1033
+ return None
1034
+
1035
+ # Get definition tags
1036
+ tags = get_tags_from_content(content, filename)
1037
+ def_tags = [t for t in tags if t.kind == "def"]
1038
+
1039
+ if not def_tags:
1040
+ return None
1041
+
1042
+ # Build line ranges for rendering
1043
+ lois: list[int] = []
1044
+ line_ranges: dict[int, int] = {}
1045
+
1046
+ for tag in def_tags:
1047
+ if tag.signature_end_line >= tag.line:
1048
+ lois.extend(range(tag.line, tag.signature_end_line + 1))
1049
+ else:
1050
+ lois.append(tag.line)
1051
+ if tag.end_line >= 0:
1052
+ line_ranges[tag.line] = tag.end_line
1053
+
1054
+ # Render tree using TreeContext
1055
+ code = content if content.endswith("\n") else content + "\n"
1056
+ context = TreeContext(
1057
+ filename,
1058
+ code,
1059
+ child_context=False,
1060
+ last_line=False,
1061
+ margin=0,
1062
+ mark_lois=False,
1063
+ loi_pad=0,
1064
+ show_top_of_file_parent_scope=False,
1065
+ )
1066
+ context.add_lines_of_interest(lois)
1067
+ context.add_context()
1068
+ tree_output: str = context.format()
1069
+
1070
+ # Add line number annotations to definitions
1071
+ import re
1072
+
1073
+ code_lines = content.splitlines()
1074
+ lois_set = set(lois)
1075
+ def_pattern = re.compile(r"^(.*?)(class\s+\w+|def\s+\w+|async\s+def\s+\w+)")
1076
+
1077
+ result_lines = []
1078
+ for output_line in tree_output.splitlines():
1079
+ modified_line = output_line
1080
+ match = def_pattern.search(output_line)
1081
+ if match:
1082
+ stripped = output_line.lstrip("│ \t")
1083
+ for line_num in lois_set:
1084
+ if line_num < len(code_lines):
1085
+ orig_line = code_lines[line_num].strip()
1086
+ if orig_line and stripped.startswith(orig_line.split("(")[0].split(":")[0]):
1087
+ name_match = re.search(
1088
+ r"(class\s+\w+|def\s+\w+|async\s+def\s+\w+)", output_line
1089
+ )
1090
+ if name_match:
1091
+ start_line_display = line_num + 1
1092
+ end_line = line_ranges.get(line_num, -1)
1093
+ if end_line >= 0 and end_line != line_num:
1094
+ end_line_display = end_line + 1
1095
+ line_info = f" # [{start_line_display}-{end_line_display}]"
1096
+ else:
1097
+ line_info = f" # [{start_line_display}]"
1098
+ modified_line = f"{output_line}{line_info}"
1099
+ break
1100
+ result_lines.append(modified_line)
1101
+
1102
+ tree_output = "\n".join(result_lines)
1103
+ if result_lines:
1104
+ tree_output += "\n"
1105
+
1106
+ # Build final output with header
1107
+ lines = content.count("\n") + 1
1108
+ tokens_approx = len(tree_output) // 4
1109
+
1110
+ header = (
1111
+ f"# File: {filename} ({lines} lines)\n"
1112
+ f"# Structure map (~{tokens_approx} tokens). Use read_file with line/limit for details.\n\n"
1113
+ )
1114
+
1115
+ result = header + f"{filename}:\n" + tree_output
1116
+
1117
+ # Truncate if needed
1118
+ max_chars = max_tokens * 4
1119
+ if len(result) > max_chars:
1120
+ result = result[:max_chars] + "\n... [truncated]\n"
1121
+
1122
+ return result
1123
+
1124
+
1125
+ def truncate_with_notice(
1126
+ path: str,
1127
+ content: str,
1128
+ head_lines: int = 100,
1129
+ tail_lines: int = 50,
1130
+ ) -> str:
1131
+ """Show head + tail for files where structure map isn't supported.
1132
+
1133
+ Args:
1134
+ path: File path for display
1135
+ content: Full file content
1136
+ head_lines: Number of lines to show from start
1137
+ tail_lines: Number of lines to show from end
1138
+
1139
+ Returns:
1140
+ Truncated content with notice about omitted lines
1141
+ """
1142
+ lines = content.splitlines()
1143
+ total = len(lines)
1144
+
1145
+ if total <= head_lines + tail_lines:
1146
+ return content
1147
+
1148
+ head = lines[:head_lines]
1149
+ tail = lines[-tail_lines:]
1150
+ omitted = total - head_lines - tail_lines
1151
+
1152
+ return (
1153
+ f"# File: {path} ({total} lines)\n"
1154
+ f"# Showing first {head_lines} and last {tail_lines} lines "
1155
+ f"(language not supported for structure map)\n\n"
1156
+ + "\n".join(head)
1157
+ + f"\n\n... [{omitted} lines omitted] ...\n\n"
1158
+ + "\n".join(tail)
1159
+ + "\n\n# Use offset/limit params to read specific sections"
1160
+ )
1161
+
1162
+
1163
+ def get_supported_languages_md() -> str:
1164
+ """Generate markdown table of supported languages."""
1165
+ from grep_ast.parsers import PARSERS
1166
+
1167
+ res = "| Language | Extension | Repo Map |\n"
1168
+ res += "|----------|-----------|----------|\n"
1169
+
1170
+ data = sorted((lang, ext) for ext, lang in PARSERS.items())
1171
+ for lang, ext in data:
1172
+ fn = get_scm_fname(lang)
1173
+ repo_map = "✓" if fn and fn.exists() else ""
1174
+ res += f"| {lang:20} | {ext:20} | {repo_map:^8} |\n"
1175
+
1176
+ res += "\n"
1177
+ return res
1178
+
1179
+
1180
+ async def find_src_files(fs: AbstractFileSystem, directory: str) -> list[str]:
1181
+ """Find all source files in a directory using async fsspec."""
1182
+ results: list[str] = []
1183
+ from fsspec.implementations.asyn_wrapper import AsyncFileSystemWrapper
1184
+
1185
+ fs = fs if isinstance(fs, AsyncFileSystem) else AsyncFileSystemWrapper(fs)
1186
+
1187
+ async def _recurse(path: str) -> None:
1188
+ try:
1189
+ entries = await fs._ls(path, detail=True)
1190
+ except (OSError, FileNotFoundError):
1191
+ return
1192
+
1193
+ for entry in entries:
1194
+ entry_path = entry.get("name", "")
1195
+ entry_type = entry.get("type", "")
1196
+
1197
+ if await is_directory(fs, entry_path, entry_type=entry_type):
1198
+ await _recurse(entry_path)
1199
+ else:
1200
+ results.append(entry_path)
1201
+
1202
+ await _recurse(directory)
1203
+ return results
1204
+
1205
+
1206
+ if __name__ == "__main__":
1207
+
1208
+ async def main() -> None:
1209
+ from fsspec.implementations.github import GithubFileSystem
1210
+
1211
+ gh_token = os.environ.get("GH_TOKEN")
1212
+ fs = GithubFileSystem(
1213
+ org="phil65", repo="epregistry", sha="main", username="phil65", token=gh_token
1214
+ )
1215
+ root_path = "src/epregistry"
1216
+ all_py_files = [f for f in await find_src_files(fs, root_path) if f.endswith(".py")]
1217
+ rm = RepoMap(fs=fs, root_path=root_path, max_tokens=4000)
1218
+ result = await rm.get_map_with_metadata(all_py_files)
1219
+ print("\n" + "=" * 80)
1220
+ print("REPOSITORY MAP METADATA")
1221
+ print("=" * 80)
1222
+ print(result)
1223
+ if result.content:
1224
+ print("\n" + "=" * 80)
1225
+ print("REPOSITORY MAP")
1226
+ print("=" * 80)
1227
+ print(result.content)
1228
+ else:
1229
+ print("No repository map generated")
1230
+
1231
+ anyio.run(main)