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.
- acp/README.md +64 -0
- acp/__init__.py +172 -0
- acp/__main__.py +10 -0
- acp/acp_requests.py +285 -0
- acp/agent/__init__.py +6 -0
- acp/agent/connection.py +256 -0
- acp/agent/implementations/__init__.py +6 -0
- acp/agent/implementations/debug_server/__init__.py +1 -0
- acp/agent/implementations/debug_server/cli.py +79 -0
- acp/agent/implementations/debug_server/debug.html +234 -0
- acp/agent/implementations/debug_server/debug_server.py +496 -0
- acp/agent/implementations/testing.py +91 -0
- acp/agent/protocol.py +65 -0
- acp/bridge/README.md +162 -0
- acp/bridge/__init__.py +6 -0
- acp/bridge/__main__.py +91 -0
- acp/bridge/bridge.py +246 -0
- acp/bridge/py.typed +0 -0
- acp/bridge/settings.py +15 -0
- acp/client/__init__.py +7 -0
- acp/client/connection.py +251 -0
- acp/client/implementations/__init__.py +7 -0
- acp/client/implementations/default_client.py +185 -0
- acp/client/implementations/headless_client.py +266 -0
- acp/client/implementations/noop_client.py +110 -0
- acp/client/protocol.py +61 -0
- acp/connection.py +280 -0
- acp/exceptions.py +46 -0
- acp/filesystem.py +524 -0
- acp/notifications.py +832 -0
- acp/py.typed +0 -0
- acp/schema/__init__.py +265 -0
- acp/schema/agent_plan.py +30 -0
- acp/schema/agent_requests.py +126 -0
- acp/schema/agent_responses.py +256 -0
- acp/schema/base.py +39 -0
- acp/schema/capabilities.py +230 -0
- acp/schema/client_requests.py +247 -0
- acp/schema/client_responses.py +96 -0
- acp/schema/common.py +81 -0
- acp/schema/content_blocks.py +188 -0
- acp/schema/mcp.py +82 -0
- acp/schema/messages.py +171 -0
- acp/schema/notifications.py +82 -0
- acp/schema/protocol_stuff.md +3 -0
- acp/schema/session_state.py +160 -0
- acp/schema/session_updates.py +419 -0
- acp/schema/slash_commands.py +51 -0
- acp/schema/terminal.py +15 -0
- acp/schema/tool_call.py +347 -0
- acp/stdio.py +250 -0
- acp/task/__init__.py +53 -0
- acp/task/debug.py +197 -0
- acp/task/dispatcher.py +93 -0
- acp/task/queue.py +69 -0
- acp/task/sender.py +82 -0
- acp/task/state.py +87 -0
- acp/task/supervisor.py +93 -0
- acp/terminal_handle.py +30 -0
- acp/tool_call_reporter.py +199 -0
- acp/tool_call_state.py +178 -0
- acp/transports.py +104 -0
- acp/utils.py +240 -0
- agentpool/__init__.py +63 -0
- agentpool/__main__.py +7 -0
- agentpool/agents/__init__.py +30 -0
- agentpool/agents/acp_agent/__init__.py +5 -0
- agentpool/agents/acp_agent/acp_agent.py +837 -0
- agentpool/agents/acp_agent/acp_converters.py +294 -0
- agentpool/agents/acp_agent/client_handler.py +317 -0
- agentpool/agents/acp_agent/session_state.py +44 -0
- agentpool/agents/agent.py +1264 -0
- agentpool/agents/agui_agent/__init__.py +19 -0
- agentpool/agents/agui_agent/agui_agent.py +677 -0
- agentpool/agents/agui_agent/agui_converters.py +423 -0
- agentpool/agents/agui_agent/chunk_transformer.py +204 -0
- agentpool/agents/agui_agent/event_types.py +83 -0
- agentpool/agents/agui_agent/helpers.py +192 -0
- agentpool/agents/architect.py +71 -0
- agentpool/agents/base_agent.py +177 -0
- agentpool/agents/claude_code_agent/__init__.py +11 -0
- agentpool/agents/claude_code_agent/claude_code_agent.py +1021 -0
- agentpool/agents/claude_code_agent/converters.py +243 -0
- agentpool/agents/context.py +105 -0
- agentpool/agents/events/__init__.py +61 -0
- agentpool/agents/events/builtin_handlers.py +129 -0
- agentpool/agents/events/event_emitter.py +320 -0
- agentpool/agents/events/events.py +561 -0
- agentpool/agents/events/tts_handlers.py +186 -0
- agentpool/agents/interactions.py +419 -0
- agentpool/agents/slashed_agent.py +244 -0
- agentpool/agents/sys_prompts.py +178 -0
- agentpool/agents/tool_wrapping.py +184 -0
- agentpool/base_provider.py +28 -0
- agentpool/common_types.py +226 -0
- agentpool/config_resources/__init__.py +16 -0
- agentpool/config_resources/acp_assistant.yml +24 -0
- agentpool/config_resources/agents.yml +109 -0
- agentpool/config_resources/agents_template.yml +18 -0
- agentpool/config_resources/agui_test.yml +18 -0
- agentpool/config_resources/claude_code_agent.yml +16 -0
- agentpool/config_resources/claude_style_subagent.md +30 -0
- agentpool/config_resources/external_acp_agents.yml +77 -0
- agentpool/config_resources/opencode_style_subagent.md +19 -0
- agentpool/config_resources/tts_test_agents.yml +78 -0
- agentpool/delegation/__init__.py +8 -0
- agentpool/delegation/base_team.py +504 -0
- agentpool/delegation/message_flow_tracker.py +39 -0
- agentpool/delegation/pool.py +1129 -0
- agentpool/delegation/team.py +325 -0
- agentpool/delegation/teamrun.py +343 -0
- agentpool/docs/__init__.py +5 -0
- agentpool/docs/gen_examples.py +42 -0
- agentpool/docs/utils.py +370 -0
- agentpool/functional/__init__.py +20 -0
- agentpool/functional/py.typed +0 -0
- agentpool/functional/run.py +80 -0
- agentpool/functional/structure.py +136 -0
- agentpool/hooks/__init__.py +20 -0
- agentpool/hooks/agent_hooks.py +247 -0
- agentpool/hooks/base.py +119 -0
- agentpool/hooks/callable.py +140 -0
- agentpool/hooks/command.py +180 -0
- agentpool/hooks/prompt.py +122 -0
- agentpool/jinja_filters.py +132 -0
- agentpool/log.py +224 -0
- agentpool/mcp_server/__init__.py +17 -0
- agentpool/mcp_server/client.py +429 -0
- agentpool/mcp_server/constants.py +32 -0
- agentpool/mcp_server/conversions.py +172 -0
- agentpool/mcp_server/helpers.py +47 -0
- agentpool/mcp_server/manager.py +232 -0
- agentpool/mcp_server/message_handler.py +164 -0
- agentpool/mcp_server/registries/__init__.py +1 -0
- agentpool/mcp_server/registries/official_registry_client.py +345 -0
- agentpool/mcp_server/registries/pulsemcp_client.py +88 -0
- agentpool/mcp_server/tool_bridge.py +548 -0
- agentpool/messaging/__init__.py +58 -0
- agentpool/messaging/compaction.py +928 -0
- agentpool/messaging/connection_manager.py +319 -0
- agentpool/messaging/context.py +66 -0
- agentpool/messaging/event_manager.py +426 -0
- agentpool/messaging/events.py +39 -0
- agentpool/messaging/message_container.py +209 -0
- agentpool/messaging/message_history.py +491 -0
- agentpool/messaging/messagenode.py +377 -0
- agentpool/messaging/messages.py +655 -0
- agentpool/messaging/processing.py +76 -0
- agentpool/mime_utils.py +95 -0
- agentpool/models/__init__.py +21 -0
- agentpool/models/acp_agents/__init__.py +22 -0
- agentpool/models/acp_agents/base.py +308 -0
- agentpool/models/acp_agents/mcp_capable.py +790 -0
- agentpool/models/acp_agents/non_mcp.py +842 -0
- agentpool/models/agents.py +450 -0
- agentpool/models/agui_agents.py +89 -0
- agentpool/models/claude_code_agents.py +238 -0
- agentpool/models/file_agents.py +116 -0
- agentpool/models/file_parsing.py +367 -0
- agentpool/models/manifest.py +658 -0
- agentpool/observability/__init__.py +9 -0
- agentpool/observability/observability_registry.py +97 -0
- agentpool/prompts/__init__.py +1 -0
- agentpool/prompts/base.py +27 -0
- agentpool/prompts/builtin_provider.py +75 -0
- agentpool/prompts/conversion_manager.py +95 -0
- agentpool/prompts/convert.py +96 -0
- agentpool/prompts/manager.py +204 -0
- agentpool/prompts/parts/zed.md +33 -0
- agentpool/prompts/prompts.py +581 -0
- agentpool/py.typed +0 -0
- agentpool/queries/tree-sitter-language-pack/README.md +7 -0
- agentpool/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- agentpool/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- agentpool/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
- agentpool/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- agentpool/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- agentpool/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- agentpool/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- agentpool/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- agentpool/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- agentpool/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- agentpool/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- agentpool/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- agentpool/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- agentpool/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- agentpool/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- agentpool/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
- agentpool/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- agentpool/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- agentpool/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- agentpool/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- agentpool/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- agentpool/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- agentpool/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- agentpool/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- agentpool/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- agentpool/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- agentpool/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- agentpool/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- agentpool/queries/tree-sitter-languages/README.md +24 -0
- agentpool/queries/tree-sitter-languages/c-tags.scm +9 -0
- agentpool/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- agentpool/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- agentpool/queries/tree-sitter-languages/dart-tags.scm +91 -0
- agentpool/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- agentpool/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- agentpool/queries/tree-sitter-languages/elm-tags.scm +19 -0
- agentpool/queries/tree-sitter-languages/fortran-tags.scm +15 -0
- agentpool/queries/tree-sitter-languages/go-tags.scm +30 -0
- agentpool/queries/tree-sitter-languages/haskell-tags.scm +3 -0
- agentpool/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- agentpool/queries/tree-sitter-languages/java-tags.scm +20 -0
- agentpool/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- agentpool/queries/tree-sitter-languages/julia-tags.scm +60 -0
- agentpool/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- agentpool/queries/tree-sitter-languages/matlab-tags.scm +10 -0
- agentpool/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- agentpool/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- agentpool/queries/tree-sitter-languages/php-tags.scm +26 -0
- agentpool/queries/tree-sitter-languages/python-tags.scm +12 -0
- agentpool/queries/tree-sitter-languages/ql-tags.scm +26 -0
- agentpool/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- agentpool/queries/tree-sitter-languages/rust-tags.scm +60 -0
- agentpool/queries/tree-sitter-languages/scala-tags.scm +65 -0
- agentpool/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- agentpool/queries/tree-sitter-languages/zig-tags.scm +3 -0
- agentpool/repomap.py +1231 -0
- agentpool/resource_providers/__init__.py +17 -0
- agentpool/resource_providers/aggregating.py +54 -0
- agentpool/resource_providers/base.py +172 -0
- agentpool/resource_providers/codemode/__init__.py +9 -0
- agentpool/resource_providers/codemode/code_executor.py +215 -0
- agentpool/resource_providers/codemode/default_prompt.py +19 -0
- agentpool/resource_providers/codemode/helpers.py +83 -0
- agentpool/resource_providers/codemode/progress_executor.py +212 -0
- agentpool/resource_providers/codemode/provider.py +150 -0
- agentpool/resource_providers/codemode/remote_mcp_execution.py +143 -0
- agentpool/resource_providers/codemode/remote_provider.py +171 -0
- agentpool/resource_providers/filtering.py +42 -0
- agentpool/resource_providers/mcp_provider.py +246 -0
- agentpool/resource_providers/plan_provider.py +196 -0
- agentpool/resource_providers/pool.py +69 -0
- agentpool/resource_providers/static.py +289 -0
- agentpool/running/__init__.py +20 -0
- agentpool/running/decorators.py +56 -0
- agentpool/running/discovery.py +101 -0
- agentpool/running/executor.py +284 -0
- agentpool/running/injection.py +111 -0
- agentpool/running/py.typed +0 -0
- agentpool/running/run_nodes.py +87 -0
- agentpool/server.py +122 -0
- agentpool/sessions/__init__.py +13 -0
- agentpool/sessions/manager.py +302 -0
- agentpool/sessions/models.py +71 -0
- agentpool/sessions/session.py +239 -0
- agentpool/sessions/store.py +163 -0
- agentpool/skills/__init__.py +5 -0
- agentpool/skills/manager.py +120 -0
- agentpool/skills/registry.py +210 -0
- agentpool/skills/skill.py +36 -0
- agentpool/storage/__init__.py +17 -0
- agentpool/storage/manager.py +419 -0
- agentpool/storage/serialization.py +136 -0
- agentpool/talk/__init__.py +13 -0
- agentpool/talk/registry.py +128 -0
- agentpool/talk/stats.py +159 -0
- agentpool/talk/talk.py +604 -0
- agentpool/tasks/__init__.py +20 -0
- agentpool/tasks/exceptions.py +25 -0
- agentpool/tasks/registry.py +33 -0
- agentpool/testing.py +129 -0
- agentpool/text_templates/__init__.py +39 -0
- agentpool/text_templates/system_prompt.jinja +30 -0
- agentpool/text_templates/tool_call_default.jinja +13 -0
- agentpool/text_templates/tool_call_markdown.jinja +25 -0
- agentpool/text_templates/tool_call_simple.jinja +5 -0
- agentpool/tools/__init__.py +16 -0
- agentpool/tools/base.py +269 -0
- agentpool/tools/exceptions.py +9 -0
- agentpool/tools/manager.py +255 -0
- agentpool/tools/tool_call_info.py +87 -0
- agentpool/ui/__init__.py +2 -0
- agentpool/ui/base.py +89 -0
- agentpool/ui/mock_provider.py +81 -0
- agentpool/ui/stdlib_provider.py +150 -0
- agentpool/utils/__init__.py +44 -0
- agentpool/utils/baseregistry.py +185 -0
- agentpool/utils/count_tokens.py +62 -0
- agentpool/utils/dag.py +184 -0
- agentpool/utils/importing.py +206 -0
- agentpool/utils/inspection.py +334 -0
- agentpool/utils/model_capabilities.py +25 -0
- agentpool/utils/network.py +28 -0
- agentpool/utils/now.py +22 -0
- agentpool/utils/parse_time.py +87 -0
- agentpool/utils/result_utils.py +35 -0
- agentpool/utils/signatures.py +305 -0
- agentpool/utils/streams.py +112 -0
- agentpool/utils/tasks.py +186 -0
- agentpool/vfs_registry.py +250 -0
- agentpool-2.1.9.dist-info/METADATA +336 -0
- agentpool-2.1.9.dist-info/RECORD +474 -0
- agentpool-2.1.9.dist-info/WHEEL +4 -0
- agentpool-2.1.9.dist-info/entry_points.txt +14 -0
- agentpool-2.1.9.dist-info/licenses/LICENSE +22 -0
- agentpool_cli/__init__.py +34 -0
- agentpool_cli/__main__.py +66 -0
- agentpool_cli/agent.py +175 -0
- agentpool_cli/cli_types.py +23 -0
- agentpool_cli/common.py +163 -0
- agentpool_cli/create.py +175 -0
- agentpool_cli/history.py +217 -0
- agentpool_cli/log.py +78 -0
- agentpool_cli/py.typed +0 -0
- agentpool_cli/run.py +84 -0
- agentpool_cli/serve_acp.py +177 -0
- agentpool_cli/serve_api.py +69 -0
- agentpool_cli/serve_mcp.py +74 -0
- agentpool_cli/serve_vercel.py +233 -0
- agentpool_cli/store.py +171 -0
- agentpool_cli/task.py +84 -0
- agentpool_cli/utils.py +104 -0
- agentpool_cli/watch.py +54 -0
- agentpool_commands/__init__.py +180 -0
- agentpool_commands/agents.py +199 -0
- agentpool_commands/base.py +45 -0
- agentpool_commands/commands.py +58 -0
- agentpool_commands/completers.py +110 -0
- agentpool_commands/connections.py +175 -0
- agentpool_commands/markdown_utils.py +31 -0
- agentpool_commands/models.py +62 -0
- agentpool_commands/prompts.py +78 -0
- agentpool_commands/py.typed +0 -0
- agentpool_commands/read.py +77 -0
- agentpool_commands/resources.py +210 -0
- agentpool_commands/session.py +48 -0
- agentpool_commands/tools.py +269 -0
- agentpool_commands/utils.py +189 -0
- agentpool_commands/workers.py +163 -0
- agentpool_config/__init__.py +53 -0
- agentpool_config/builtin_tools.py +265 -0
- agentpool_config/commands.py +237 -0
- agentpool_config/conditions.py +301 -0
- agentpool_config/converters.py +30 -0
- agentpool_config/durable.py +331 -0
- agentpool_config/event_handlers.py +600 -0
- agentpool_config/events.py +153 -0
- agentpool_config/forward_targets.py +251 -0
- agentpool_config/hook_conditions.py +331 -0
- agentpool_config/hooks.py +241 -0
- agentpool_config/jinja.py +206 -0
- agentpool_config/knowledge.py +41 -0
- agentpool_config/loaders.py +350 -0
- agentpool_config/mcp_server.py +243 -0
- agentpool_config/nodes.py +202 -0
- agentpool_config/observability.py +191 -0
- agentpool_config/output_types.py +55 -0
- agentpool_config/pool_server.py +267 -0
- agentpool_config/prompt_hubs.py +105 -0
- agentpool_config/prompts.py +185 -0
- agentpool_config/py.typed +0 -0
- agentpool_config/resources.py +33 -0
- agentpool_config/session.py +119 -0
- agentpool_config/skills.py +17 -0
- agentpool_config/storage.py +288 -0
- agentpool_config/system_prompts.py +190 -0
- agentpool_config/task.py +162 -0
- agentpool_config/teams.py +52 -0
- agentpool_config/tools.py +112 -0
- agentpool_config/toolsets.py +1033 -0
- agentpool_config/workers.py +86 -0
- agentpool_prompts/__init__.py +1 -0
- agentpool_prompts/braintrust_hub.py +235 -0
- agentpool_prompts/fabric.py +75 -0
- agentpool_prompts/langfuse_hub.py +79 -0
- agentpool_prompts/promptlayer_provider.py +59 -0
- agentpool_prompts/py.typed +0 -0
- agentpool_server/__init__.py +9 -0
- agentpool_server/a2a_server/__init__.py +5 -0
- agentpool_server/a2a_server/a2a_types.py +41 -0
- agentpool_server/a2a_server/server.py +190 -0
- agentpool_server/a2a_server/storage.py +81 -0
- agentpool_server/acp_server/__init__.py +22 -0
- agentpool_server/acp_server/acp_agent.py +786 -0
- agentpool_server/acp_server/acp_tools.py +43 -0
- agentpool_server/acp_server/commands/__init__.py +18 -0
- agentpool_server/acp_server/commands/acp_commands.py +594 -0
- agentpool_server/acp_server/commands/debug_commands.py +376 -0
- agentpool_server/acp_server/commands/docs_commands/__init__.py +39 -0
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +169 -0
- agentpool_server/acp_server/commands/docs_commands/get_schema.py +176 -0
- agentpool_server/acp_server/commands/docs_commands/get_source.py +110 -0
- agentpool_server/acp_server/commands/docs_commands/git_diff.py +111 -0
- agentpool_server/acp_server/commands/docs_commands/helpers.py +33 -0
- agentpool_server/acp_server/commands/docs_commands/url_to_markdown.py +90 -0
- agentpool_server/acp_server/commands/spawn.py +210 -0
- agentpool_server/acp_server/converters.py +235 -0
- agentpool_server/acp_server/input_provider.py +338 -0
- agentpool_server/acp_server/server.py +288 -0
- agentpool_server/acp_server/session.py +969 -0
- agentpool_server/acp_server/session_manager.py +313 -0
- agentpool_server/acp_server/syntax_detection.py +250 -0
- agentpool_server/acp_server/zed_tools.md +90 -0
- agentpool_server/aggregating_server.py +309 -0
- agentpool_server/agui_server/__init__.py +11 -0
- agentpool_server/agui_server/server.py +128 -0
- agentpool_server/base.py +189 -0
- agentpool_server/http_server.py +164 -0
- agentpool_server/mcp_server/__init__.py +6 -0
- agentpool_server/mcp_server/server.py +314 -0
- agentpool_server/mcp_server/zed_wrapper.py +110 -0
- agentpool_server/openai_api_server/__init__.py +5 -0
- agentpool_server/openai_api_server/completions/__init__.py +1 -0
- agentpool_server/openai_api_server/completions/helpers.py +81 -0
- agentpool_server/openai_api_server/completions/models.py +98 -0
- agentpool_server/openai_api_server/responses/__init__.py +1 -0
- agentpool_server/openai_api_server/responses/helpers.py +74 -0
- agentpool_server/openai_api_server/responses/models.py +96 -0
- agentpool_server/openai_api_server/server.py +242 -0
- agentpool_server/py.typed +0 -0
- agentpool_storage/__init__.py +9 -0
- agentpool_storage/base.py +310 -0
- agentpool_storage/file_provider.py +378 -0
- agentpool_storage/formatters.py +129 -0
- agentpool_storage/memory_provider.py +396 -0
- agentpool_storage/models.py +108 -0
- agentpool_storage/py.typed +0 -0
- agentpool_storage/session_store.py +262 -0
- agentpool_storage/sql_provider/__init__.py +21 -0
- agentpool_storage/sql_provider/cli.py +146 -0
- agentpool_storage/sql_provider/models.py +249 -0
- agentpool_storage/sql_provider/queries.py +15 -0
- agentpool_storage/sql_provider/sql_provider.py +444 -0
- agentpool_storage/sql_provider/utils.py +234 -0
- agentpool_storage/text_log_provider.py +275 -0
- agentpool_toolsets/__init__.py +15 -0
- agentpool_toolsets/builtin/__init__.py +33 -0
- agentpool_toolsets/builtin/agent_management.py +239 -0
- agentpool_toolsets/builtin/chain.py +288 -0
- agentpool_toolsets/builtin/code.py +398 -0
- agentpool_toolsets/builtin/debug.py +291 -0
- agentpool_toolsets/builtin/execution_environment.py +381 -0
- agentpool_toolsets/builtin/file_edit/__init__.py +11 -0
- agentpool_toolsets/builtin/file_edit/file_edit.py +747 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/__init__.py +5 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/example_usage.py +311 -0
- agentpool_toolsets/builtin/file_edit/fuzzy_matcher/streaming_fuzzy_matcher.py +443 -0
- agentpool_toolsets/builtin/history.py +36 -0
- agentpool_toolsets/builtin/integration.py +85 -0
- agentpool_toolsets/builtin/skills.py +77 -0
- agentpool_toolsets/builtin/subagent_tools.py +324 -0
- agentpool_toolsets/builtin/tool_management.py +90 -0
- agentpool_toolsets/builtin/user_interaction.py +52 -0
- agentpool_toolsets/builtin/workers.py +128 -0
- agentpool_toolsets/composio_toolset.py +96 -0
- agentpool_toolsets/config_creation.py +192 -0
- agentpool_toolsets/entry_points.py +47 -0
- agentpool_toolsets/fsspec_toolset/__init__.py +7 -0
- agentpool_toolsets/fsspec_toolset/diagnostics.py +115 -0
- agentpool_toolsets/fsspec_toolset/grep.py +450 -0
- agentpool_toolsets/fsspec_toolset/helpers.py +631 -0
- agentpool_toolsets/fsspec_toolset/streaming_diff_parser.py +249 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +1384 -0
- agentpool_toolsets/mcp_run_toolset.py +61 -0
- agentpool_toolsets/notifications.py +146 -0
- agentpool_toolsets/openapi.py +118 -0
- agentpool_toolsets/py.typed +0 -0
- agentpool_toolsets/search_toolset.py +202 -0
- agentpool_toolsets/semantic_memory_toolset.py +536 -0
- agentpool_toolsets/streaming_tools.py +265 -0
- agentpool_toolsets/vfs_toolset.py +124 -0
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
"""FSSpec filesystem toolset helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
import difflib
|
|
7
|
+
import re
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from pydantic_ai import ModelRetry
|
|
11
|
+
|
|
12
|
+
from agentpool.log import get_logger
|
|
13
|
+
from agentpool_toolsets.builtin.file_edit.fuzzy_matcher import StreamingFuzzyMatcher
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logger = get_logger(__name__)
|
|
17
|
+
|
|
18
|
+
# Default maximum size for file operations (64KB)
|
|
19
|
+
DEFAULT_MAX_SIZE = 64_000
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class DiffHunk:
|
|
24
|
+
"""A single diff hunk representing one edit operation."""
|
|
25
|
+
|
|
26
|
+
old_text: str
|
|
27
|
+
"""The text to find/replace (context + removed lines)."""
|
|
28
|
+
|
|
29
|
+
new_text: str
|
|
30
|
+
"""The replacement text (context + added lines)."""
|
|
31
|
+
|
|
32
|
+
raw: str
|
|
33
|
+
"""The raw diff text for this hunk."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def parse_locationless_diff(diff_text: str) -> list[DiffHunk]:
|
|
37
|
+
"""Parse a locationless unified diff into old/new text pairs.
|
|
38
|
+
|
|
39
|
+
Handles diff format without line numbers - the location is inferred
|
|
40
|
+
by matching context in the file.
|
|
41
|
+
|
|
42
|
+
Format expected:
|
|
43
|
+
```
|
|
44
|
+
context line (unchanged)
|
|
45
|
+
-removed line
|
|
46
|
+
+added line
|
|
47
|
+
more context
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Multiple hunks are separated by:
|
|
51
|
+
- Blank lines (empty line not starting with space)
|
|
52
|
+
- Non-diff content lines
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
diff_text: The diff text (may contain multiple hunks)
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
List of DiffHunk objects with old_text/new_text pairs
|
|
59
|
+
"""
|
|
60
|
+
hunks: list[DiffHunk] = []
|
|
61
|
+
|
|
62
|
+
# Extract content between <diff> tags if present
|
|
63
|
+
diff_match = re.search(r"<diff>(.*?)</diff>", diff_text, re.DOTALL)
|
|
64
|
+
if diff_match:
|
|
65
|
+
diff_text = diff_match.group(1)
|
|
66
|
+
|
|
67
|
+
# Also handle ```diff ... ``` code blocks
|
|
68
|
+
# Use \n? instead of \s* to avoid consuming leading spaces on first diff line
|
|
69
|
+
code_block_match = re.search(r"```diff\n?(.*?)```", diff_text, re.DOTALL)
|
|
70
|
+
if code_block_match:
|
|
71
|
+
diff_text = code_block_match.group(1)
|
|
72
|
+
|
|
73
|
+
# Strip only leading/trailing newlines, not spaces (which are meaningful in diffs)
|
|
74
|
+
diff_text = diff_text.strip("\n\r")
|
|
75
|
+
lines = diff_text.split("\n")
|
|
76
|
+
|
|
77
|
+
current_hunk_lines: list[str] = []
|
|
78
|
+
|
|
79
|
+
for line in lines:
|
|
80
|
+
# Skip standard diff headers
|
|
81
|
+
if line.startswith(("---", "+++", "@@", "diff --git", "index ")):
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
# Check if this is a diff line (starts with +, -, or space for context)
|
|
85
|
+
is_diff_line = line.startswith(("+", "-", " "))
|
|
86
|
+
|
|
87
|
+
# Empty line (not starting with space) = hunk separator
|
|
88
|
+
if line == "" or (not is_diff_line and not line.strip()):
|
|
89
|
+
if current_hunk_lines:
|
|
90
|
+
hunk = _parse_single_hunk(current_hunk_lines)
|
|
91
|
+
if hunk:
|
|
92
|
+
hunks.append(hunk)
|
|
93
|
+
current_hunk_lines = []
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
# Non-diff content line = hunk separator
|
|
97
|
+
if not is_diff_line and line.strip():
|
|
98
|
+
if current_hunk_lines:
|
|
99
|
+
hunk = _parse_single_hunk(current_hunk_lines)
|
|
100
|
+
if hunk:
|
|
101
|
+
hunks.append(hunk)
|
|
102
|
+
current_hunk_lines = []
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
# Accumulate diff lines
|
|
106
|
+
current_hunk_lines.append(line)
|
|
107
|
+
|
|
108
|
+
# Don't forget the last hunk
|
|
109
|
+
if current_hunk_lines:
|
|
110
|
+
hunk = _parse_single_hunk(current_hunk_lines)
|
|
111
|
+
if hunk:
|
|
112
|
+
hunks.append(hunk)
|
|
113
|
+
|
|
114
|
+
return hunks
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _parse_single_hunk(lines: list[str]) -> DiffHunk | None:
|
|
118
|
+
"""Parse a single diff hunk into old/new text.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
lines: Lines of the hunk (each starting with +, -, or space)
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
DiffHunk or None if the hunk is empty/invalid
|
|
125
|
+
"""
|
|
126
|
+
old_lines: list[str] = []
|
|
127
|
+
new_lines: list[str] = []
|
|
128
|
+
|
|
129
|
+
for line in lines:
|
|
130
|
+
if line.startswith("-"):
|
|
131
|
+
# Removed line - only in old
|
|
132
|
+
old_lines.append(line[1:])
|
|
133
|
+
elif line.startswith("+"):
|
|
134
|
+
# Added line - only in new
|
|
135
|
+
new_lines.append(line[1:])
|
|
136
|
+
elif line.startswith(" "):
|
|
137
|
+
# Context line - in both
|
|
138
|
+
content = line[1:] if len(line) > 1 else ""
|
|
139
|
+
old_lines.append(content)
|
|
140
|
+
new_lines.append(content)
|
|
141
|
+
# Skip lines that don't match the pattern
|
|
142
|
+
|
|
143
|
+
if not old_lines and not new_lines:
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
old_text = "\n".join(old_lines)
|
|
147
|
+
new_text = "\n".join(new_lines)
|
|
148
|
+
raw = "\n".join(lines)
|
|
149
|
+
|
|
150
|
+
return DiffHunk(old_text=old_text, new_text=new_text, raw=raw)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def apply_diff_edits(
|
|
154
|
+
original_content: str,
|
|
155
|
+
diff_response: str,
|
|
156
|
+
*,
|
|
157
|
+
use_fuzzy: bool = True,
|
|
158
|
+
) -> str:
|
|
159
|
+
"""Apply locationless diff edits to content.
|
|
160
|
+
|
|
161
|
+
Parses diff format and applies each hunk using content matching.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
original_content: The original file content
|
|
165
|
+
diff_response: The agent's response containing diffs
|
|
166
|
+
use_fuzzy: Whether to use fuzzy matching for finding locations
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
The modified content
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
ModelRetry: If edits cannot be applied (for agent retry)
|
|
173
|
+
"""
|
|
174
|
+
from agentpool_toolsets.builtin.file_edit import replace_content
|
|
175
|
+
|
|
176
|
+
hunks = parse_locationless_diff(diff_response)
|
|
177
|
+
|
|
178
|
+
if not hunks:
|
|
179
|
+
logger.warning("No diff hunks found in response")
|
|
180
|
+
# Try falling back to structured edits format
|
|
181
|
+
return await apply_structured_edits(original_content, diff_response)
|
|
182
|
+
|
|
183
|
+
content = original_content
|
|
184
|
+
applied_edits = 0
|
|
185
|
+
failed_hunks: list[str] = []
|
|
186
|
+
|
|
187
|
+
for hunk in hunks:
|
|
188
|
+
if not hunk.old_text.strip():
|
|
189
|
+
# Pure insertion - would need line context to place
|
|
190
|
+
# For now, skip pure insertions without context
|
|
191
|
+
logger.warning("Skipping pure insertion hunk (no context)")
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
# Use the existing smart replace with fuzzy matching
|
|
196
|
+
new_content = replace_content(
|
|
197
|
+
content,
|
|
198
|
+
hunk.old_text,
|
|
199
|
+
hunk.new_text,
|
|
200
|
+
replace_all=False,
|
|
201
|
+
)
|
|
202
|
+
content = new_content
|
|
203
|
+
applied_edits += 1
|
|
204
|
+
except ValueError as e:
|
|
205
|
+
# Match failed
|
|
206
|
+
logger.warning("Failed to apply hunk", error=str(e), hunk=hunk.raw[:100])
|
|
207
|
+
failed_hunks.append(hunk.old_text[:50])
|
|
208
|
+
|
|
209
|
+
if applied_edits == 0 and hunks:
|
|
210
|
+
msg = (
|
|
211
|
+
f"None of the {len(hunks)} diff hunks could be applied. "
|
|
212
|
+
"The context lines don't match the current file content. "
|
|
213
|
+
"Please read the file again and provide accurate diff context."
|
|
214
|
+
)
|
|
215
|
+
raise ModelRetry(msg)
|
|
216
|
+
|
|
217
|
+
if failed_hunks:
|
|
218
|
+
logger.warning(
|
|
219
|
+
"Some hunks failed",
|
|
220
|
+
applied=applied_edits,
|
|
221
|
+
failed=len(failed_hunks),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
logger.info("Applied diff edits", applied=applied_edits, total=len(hunks))
|
|
225
|
+
return content
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
async def apply_diff_edits_streaming(
|
|
229
|
+
original_content: str,
|
|
230
|
+
diff_response: str,
|
|
231
|
+
*,
|
|
232
|
+
line_hint: int | None = None,
|
|
233
|
+
) -> str:
|
|
234
|
+
"""Apply locationless diff edits using streaming fuzzy matcher (Zed-style).
|
|
235
|
+
|
|
236
|
+
Alternative to `apply_diff_edits` that uses a dynamic programming based
|
|
237
|
+
fuzzy matcher to locate where edits should be applied. This approach:
|
|
238
|
+
- Uses line-by-line fuzzy matching with Levenshtein distance
|
|
239
|
+
- Handles indentation differences gracefully
|
|
240
|
+
- Can use line hints to disambiguate multiple matches
|
|
241
|
+
- Matches Zed's edit resolution algorithm
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
original_content: The original file content
|
|
245
|
+
diff_response: The agent's response containing diffs
|
|
246
|
+
line_hint: Optional line number hint for disambiguation
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
The modified content
|
|
250
|
+
|
|
251
|
+
Raises:
|
|
252
|
+
ModelRetry: If edits cannot be applied (for agent retry)
|
|
253
|
+
"""
|
|
254
|
+
hunks = parse_locationless_diff(diff_response)
|
|
255
|
+
|
|
256
|
+
if not hunks:
|
|
257
|
+
logger.warning("No diff hunks found in response (streaming)")
|
|
258
|
+
# Try falling back to structured edits format
|
|
259
|
+
return await apply_structured_edits(original_content, diff_response)
|
|
260
|
+
|
|
261
|
+
content = original_content
|
|
262
|
+
applied_edits = 0
|
|
263
|
+
failed_hunks: list[str] = []
|
|
264
|
+
ambiguous_hunks: list[str] = []
|
|
265
|
+
|
|
266
|
+
for hunk in hunks:
|
|
267
|
+
if not hunk.old_text.strip():
|
|
268
|
+
# Pure insertion - skip for now (needs anchor point)
|
|
269
|
+
logger.warning("Skipping pure insertion hunk (no context)")
|
|
270
|
+
continue
|
|
271
|
+
|
|
272
|
+
# Use streaming fuzzy matcher to find where old_text matches
|
|
273
|
+
matcher = StreamingFuzzyMatcher(content)
|
|
274
|
+
|
|
275
|
+
# Feed the old_text lines to the matcher
|
|
276
|
+
# Simulate streaming by pushing line by line
|
|
277
|
+
old_lines = hunk.old_text.split("\n")
|
|
278
|
+
for i, line in enumerate(old_lines):
|
|
279
|
+
# Add newline except for last line
|
|
280
|
+
chunk = line + ("\n" if i < len(old_lines) - 1 else "")
|
|
281
|
+
matcher.push(chunk, line_hint=line_hint)
|
|
282
|
+
|
|
283
|
+
# Get final matches
|
|
284
|
+
matches = matcher.finish()
|
|
285
|
+
|
|
286
|
+
if not matches:
|
|
287
|
+
logger.warning("No match found for hunk", hunk=hunk.old_text[:50])
|
|
288
|
+
failed_hunks.append(hunk.old_text[:50])
|
|
289
|
+
continue
|
|
290
|
+
|
|
291
|
+
# Try to select best match
|
|
292
|
+
best_match = matcher.select_best_match()
|
|
293
|
+
|
|
294
|
+
if best_match is None and len(matches) > 1:
|
|
295
|
+
# Multiple ambiguous matches
|
|
296
|
+
logger.warning(
|
|
297
|
+
"Ambiguous matches for hunk",
|
|
298
|
+
hunk=hunk.old_text[:50],
|
|
299
|
+
match_count=len(matches),
|
|
300
|
+
)
|
|
301
|
+
ambiguous_hunks.append(hunk.old_text[:50])
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
# Use best match or first match
|
|
305
|
+
match_range = best_match or matches[0]
|
|
306
|
+
|
|
307
|
+
# Extract the matched text and replace with new_text
|
|
308
|
+
matched_text = content[match_range.start : match_range.end]
|
|
309
|
+
|
|
310
|
+
# Apply the replacement
|
|
311
|
+
# We need to be careful about indentation - the matcher finds the range,
|
|
312
|
+
# but we should preserve the original indentation structure
|
|
313
|
+
old_indent = _get_leading_indent(matched_text)
|
|
314
|
+
new_indent = _get_leading_indent(hunk.old_text)
|
|
315
|
+
|
|
316
|
+
# Calculate indent delta
|
|
317
|
+
indent_delta = len(old_indent) - len(new_indent)
|
|
318
|
+
|
|
319
|
+
# Reindent new_text to match the file's indentation
|
|
320
|
+
if indent_delta != 0:
|
|
321
|
+
reindented_new = _reindent_text(
|
|
322
|
+
hunk.new_text, indent_delta, old_indent[0] if old_indent else " "
|
|
323
|
+
)
|
|
324
|
+
else:
|
|
325
|
+
reindented_new = hunk.new_text
|
|
326
|
+
|
|
327
|
+
# Apply the edit
|
|
328
|
+
content = content[: match_range.start] + reindented_new + content[match_range.end :]
|
|
329
|
+
applied_edits += 1
|
|
330
|
+
|
|
331
|
+
logger.debug(
|
|
332
|
+
"Applied streaming edit",
|
|
333
|
+
start=match_range.start,
|
|
334
|
+
end=match_range.end,
|
|
335
|
+
old_len=len(matched_text),
|
|
336
|
+
new_len=len(reindented_new),
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
if applied_edits == 0 and hunks:
|
|
340
|
+
if ambiguous_hunks:
|
|
341
|
+
matches_str = ", ".join(ambiguous_hunks[:3])
|
|
342
|
+
msg = (
|
|
343
|
+
f"Edit locations are ambiguous - multiple matches found for: {matches_str}... "
|
|
344
|
+
"Please include more context lines in the diff to uniquely identify the location."
|
|
345
|
+
)
|
|
346
|
+
else:
|
|
347
|
+
msg = (
|
|
348
|
+
f"None of the {len(hunks)} diff hunks could be applied. "
|
|
349
|
+
"The context lines don't match the current file content. "
|
|
350
|
+
"Please read the file again and provide accurate diff context."
|
|
351
|
+
)
|
|
352
|
+
raise ModelRetry(msg)
|
|
353
|
+
|
|
354
|
+
if failed_hunks or ambiguous_hunks:
|
|
355
|
+
logger.warning(
|
|
356
|
+
"Some hunks failed (streaming)",
|
|
357
|
+
applied=applied_edits,
|
|
358
|
+
failed=len(failed_hunks),
|
|
359
|
+
ambiguous=len(ambiguous_hunks),
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
logger.info("Applied diff edits (streaming)", applied=applied_edits, total=len(hunks))
|
|
363
|
+
return content
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _get_leading_indent(text: str) -> str:
|
|
367
|
+
"""Get the leading whitespace of the first non-empty line."""
|
|
368
|
+
for line in text.split("\n"):
|
|
369
|
+
if line.strip():
|
|
370
|
+
return line[: len(line) - len(line.lstrip())]
|
|
371
|
+
return ""
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _reindent_text(text: str, delta: int, indent_char: str = " ") -> str:
|
|
375
|
+
"""Reindent text by adding/removing leading whitespace.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
text: Text to reindent
|
|
379
|
+
delta: Number of characters to add (positive) or remove (negative)
|
|
380
|
+
indent_char: Character to use for indentation (space or tab)
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
Reindented text
|
|
384
|
+
"""
|
|
385
|
+
if delta == 0:
|
|
386
|
+
return text
|
|
387
|
+
|
|
388
|
+
lines = text.split("\n")
|
|
389
|
+
result_lines = []
|
|
390
|
+
|
|
391
|
+
for line in lines:
|
|
392
|
+
if not line.strip():
|
|
393
|
+
# Empty or whitespace-only line - keep as is
|
|
394
|
+
result_lines.append(line)
|
|
395
|
+
continue
|
|
396
|
+
|
|
397
|
+
current_indent = len(line) - len(line.lstrip())
|
|
398
|
+
|
|
399
|
+
if delta > 0:
|
|
400
|
+
# Add indentation
|
|
401
|
+
new_line = (indent_char * delta) + line
|
|
402
|
+
else:
|
|
403
|
+
# Remove indentation (but don't go negative)
|
|
404
|
+
chars_to_remove = min(abs(delta), current_indent)
|
|
405
|
+
new_line = line[chars_to_remove:]
|
|
406
|
+
|
|
407
|
+
result_lines.append(new_line)
|
|
408
|
+
|
|
409
|
+
return "\n".join(result_lines)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
async def apply_structured_edits(original_content: str, edits_response: str) -> str:
|
|
413
|
+
"""Apply structured edits from the agent response."""
|
|
414
|
+
# Parse the edits from the response
|
|
415
|
+
edits_match = re.search(r"<edits>(.*?)</edits>", edits_response, re.DOTALL)
|
|
416
|
+
if not edits_match:
|
|
417
|
+
logger.warning("No edits block found in response")
|
|
418
|
+
return original_content
|
|
419
|
+
|
|
420
|
+
edits_content = edits_match.group(1)
|
|
421
|
+
|
|
422
|
+
# Find all old_text/new_text pairs
|
|
423
|
+
old_text_pattern = r"<old_text[^>]*>(.*?)</old_text>"
|
|
424
|
+
new_text_pattern = r"<new_text>(.*?)</new_text>"
|
|
425
|
+
|
|
426
|
+
old_texts = re.findall(old_text_pattern, edits_content, re.DOTALL)
|
|
427
|
+
new_texts = re.findall(new_text_pattern, edits_content, re.DOTALL)
|
|
428
|
+
|
|
429
|
+
if len(old_texts) != len(new_texts):
|
|
430
|
+
logger.warning("Mismatch between old_text and new_text blocks")
|
|
431
|
+
return original_content
|
|
432
|
+
|
|
433
|
+
# Apply edits sequentially
|
|
434
|
+
content = original_content
|
|
435
|
+
applied_edits = 0
|
|
436
|
+
|
|
437
|
+
failed_matches = []
|
|
438
|
+
multiple_matches = []
|
|
439
|
+
|
|
440
|
+
for old_text, new_text in zip(old_texts, new_texts, strict=False):
|
|
441
|
+
old_cleaned = old_text.strip()
|
|
442
|
+
new_cleaned = new_text.strip()
|
|
443
|
+
|
|
444
|
+
# Check for multiple matches (ambiguity)
|
|
445
|
+
match_count = content.count(old_cleaned)
|
|
446
|
+
if match_count > 1:
|
|
447
|
+
multiple_matches.append(old_cleaned[:50])
|
|
448
|
+
elif match_count == 1:
|
|
449
|
+
content = content.replace(old_cleaned, new_cleaned, 1)
|
|
450
|
+
applied_edits += 1
|
|
451
|
+
else:
|
|
452
|
+
failed_matches.append(old_cleaned[:50])
|
|
453
|
+
|
|
454
|
+
# Raise ModelRetry for specific failure cases
|
|
455
|
+
if applied_edits == 0 and len(old_cleaned) > 0:
|
|
456
|
+
msg = (
|
|
457
|
+
"Some edits were produced but none of them could be applied. "
|
|
458
|
+
"Read the relevant sections of the file again so that "
|
|
459
|
+
"I can perform the requested edits."
|
|
460
|
+
)
|
|
461
|
+
raise ModelRetry(msg)
|
|
462
|
+
|
|
463
|
+
if multiple_matches:
|
|
464
|
+
matches_str = ", ".join(multiple_matches)
|
|
465
|
+
msg = (
|
|
466
|
+
f"<old_text> matches multiple positions in the file: {matches_str}... "
|
|
467
|
+
"Read the relevant sections of the file again and extend <old_text> "
|
|
468
|
+
"to be more specific."
|
|
469
|
+
)
|
|
470
|
+
raise ModelRetry(msg)
|
|
471
|
+
|
|
472
|
+
logger.info("Applied structured edits", num=applied_edits, total=len(old_texts))
|
|
473
|
+
return content
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def get_changed_lines(original_content: str, new_content: str, path: str) -> list[str]:
|
|
477
|
+
old = original_content.splitlines(keepends=True)
|
|
478
|
+
new = new_content.splitlines(keepends=True)
|
|
479
|
+
diff = list(difflib.unified_diff(old, new, fromfile=path, tofile=path, lineterm=""))
|
|
480
|
+
return [line for line in diff if line.startswith(("+", "-"))]
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def get_changed_line_numbers(original_content: str, new_content: str) -> list[int]:
|
|
484
|
+
"""Extract line numbers where changes occurred for ACP UI highlighting.
|
|
485
|
+
|
|
486
|
+
Similar to Claude Code's line tracking for precise change location reporting.
|
|
487
|
+
Returns line numbers in the new content where changes happened.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
original_content: Original file content
|
|
491
|
+
new_content: Modified file content
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
List of line numbers (1-based) where changes occurred in new content
|
|
495
|
+
"""
|
|
496
|
+
old_lines = original_content.splitlines(keepends=True)
|
|
497
|
+
new_lines = new_content.splitlines(keepends=True)
|
|
498
|
+
# Use SequenceMatcher to find changed blocks
|
|
499
|
+
matcher = difflib.SequenceMatcher(None, old_lines, new_lines)
|
|
500
|
+
changed_line_numbers = set()
|
|
501
|
+
for tag, _i1, _i2, j1, j2 in matcher.get_opcodes():
|
|
502
|
+
if tag in ("replace", "insert", "delete"):
|
|
503
|
+
# For replacements and insertions, mark lines in new content
|
|
504
|
+
# For deletions, mark the position where deletion occurred
|
|
505
|
+
if tag == "delete":
|
|
506
|
+
# Mark the line where deletion occurred (or next line if at end)
|
|
507
|
+
line_num = min(j1 + 1, len(new_lines))
|
|
508
|
+
if line_num > 0:
|
|
509
|
+
changed_line_numbers.add(line_num)
|
|
510
|
+
else:
|
|
511
|
+
# Mark all affected lines in new content
|
|
512
|
+
for line_num in range(j1 + 1, j2 + 1): # Convert to 1-based
|
|
513
|
+
changed_line_numbers.add(line_num)
|
|
514
|
+
|
|
515
|
+
return sorted(changed_line_numbers)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def truncate_content(content: str, max_size: int = DEFAULT_MAX_SIZE) -> tuple[str, bool]:
|
|
519
|
+
"""Truncate text content to a maximum size in bytes.
|
|
520
|
+
|
|
521
|
+
Args:
|
|
522
|
+
content: Text content to truncate
|
|
523
|
+
max_size: Maximum size in bytes (default: 64KB)
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
Tuple of (truncated_content, was_truncated)
|
|
527
|
+
"""
|
|
528
|
+
content_bytes = content.encode("utf-8")
|
|
529
|
+
if len(content_bytes) <= max_size:
|
|
530
|
+
return content, False
|
|
531
|
+
|
|
532
|
+
# Truncate at byte boundary and decode safely
|
|
533
|
+
truncated_bytes = content_bytes[:max_size]
|
|
534
|
+
# Avoid breaking UTF-8 sequences by decoding with error handling
|
|
535
|
+
truncated = truncated_bytes.decode("utf-8", errors="ignore")
|
|
536
|
+
return truncated, True
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def truncate_lines(
|
|
540
|
+
lines: list[str], offset: int = 0, limit: int | None = None, max_bytes: int = DEFAULT_MAX_SIZE
|
|
541
|
+
) -> tuple[list[str], bool]:
|
|
542
|
+
"""Truncate lines with offset/limit and byte size constraints.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
lines: List of text lines
|
|
546
|
+
offset: Starting line index (0-based)
|
|
547
|
+
limit: Maximum number of lines to include (None = no limit)
|
|
548
|
+
max_bytes: Maximum total bytes (default: 64KB)
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
Tuple of (truncated_lines, was_truncated)
|
|
552
|
+
"""
|
|
553
|
+
# Apply offset
|
|
554
|
+
start_idx = max(0, offset)
|
|
555
|
+
if start_idx >= len(lines):
|
|
556
|
+
return [], False
|
|
557
|
+
|
|
558
|
+
# Apply line limit
|
|
559
|
+
end_idx = min(len(lines), start_idx + limit) if limit is not None else len(lines)
|
|
560
|
+
|
|
561
|
+
selected_lines = lines[start_idx:end_idx]
|
|
562
|
+
|
|
563
|
+
# Apply byte limit
|
|
564
|
+
result_lines: list[str] = []
|
|
565
|
+
total_bytes = 0
|
|
566
|
+
|
|
567
|
+
for line in selected_lines:
|
|
568
|
+
line_bytes = len(line.encode("utf-8"))
|
|
569
|
+
if total_bytes + line_bytes > max_bytes:
|
|
570
|
+
# Would exceed limit - this is actual truncation
|
|
571
|
+
return result_lines, True
|
|
572
|
+
|
|
573
|
+
result_lines.append(line)
|
|
574
|
+
total_bytes += line_bytes
|
|
575
|
+
|
|
576
|
+
# Successfully returned all requested content - not truncated
|
|
577
|
+
# (byte truncation already handled above with early return)
|
|
578
|
+
return result_lines, False
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def _format_size(size: int) -> str:
|
|
582
|
+
"""Format byte size as human-readable string."""
|
|
583
|
+
if size < 1024: # noqa: PLR2004
|
|
584
|
+
return f"{size} B"
|
|
585
|
+
if size < 1024 * 1024:
|
|
586
|
+
return f"{size / 1024:.1f} KB"
|
|
587
|
+
return f"{size / (1024 * 1024):.1f} MB"
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def format_directory_listing(
|
|
591
|
+
path: str,
|
|
592
|
+
directories: list[dict[str, Any]],
|
|
593
|
+
files: list[dict[str, Any]],
|
|
594
|
+
pattern: str = "*",
|
|
595
|
+
) -> str:
|
|
596
|
+
"""Format directory listing as markdown table.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
path: Base directory path
|
|
600
|
+
directories: List of directory info dicts
|
|
601
|
+
files: List of file info dicts
|
|
602
|
+
pattern: Glob pattern used
|
|
603
|
+
|
|
604
|
+
Returns:
|
|
605
|
+
Formatted markdown string
|
|
606
|
+
"""
|
|
607
|
+
lines = [f"## {path}"]
|
|
608
|
+
if pattern != "*":
|
|
609
|
+
lines.append(f"Pattern: `{pattern}`")
|
|
610
|
+
lines.append("")
|
|
611
|
+
|
|
612
|
+
if not directories and not files:
|
|
613
|
+
lines.append("*Empty directory*")
|
|
614
|
+
return "\n".join(lines)
|
|
615
|
+
|
|
616
|
+
lines.append("| Name | Type | Size |")
|
|
617
|
+
lines.append("|------|------|------|")
|
|
618
|
+
|
|
619
|
+
# Directories first (sorted)
|
|
620
|
+
for d in sorted(directories, key=lambda x: x["name"]):
|
|
621
|
+
lines.append(f"| {d['name']}/ | dir | - |") # noqa: PERF401
|
|
622
|
+
|
|
623
|
+
# Then files (sorted)
|
|
624
|
+
for f in sorted(files, key=lambda x: x["name"]):
|
|
625
|
+
size_str = _format_size(f.get("size", 0))
|
|
626
|
+
lines.append(f"| {f['name']} | file | {size_str} |")
|
|
627
|
+
|
|
628
|
+
lines.append("")
|
|
629
|
+
lines.append(f"*{len(directories)} directories, {len(files)} files*")
|
|
630
|
+
|
|
631
|
+
return "\n".join(lines)
|