coco-framework 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coco/__init__.py +3 -0
- coco/agents/__init__.py +9 -0
- coco/agents/_subagent_transformer.py +307 -0
- coco/agents/factory.py +2164 -0
- coco/agents/middleware/__init__.py +100 -0
- coco/agents/middleware/_execution.py +385 -0
- coco/agents/middleware/_redaction.py +454 -0
- coco/agents/middleware/_retry.py +134 -0
- coco/agents/middleware/_trace_policy.py +61 -0
- coco/agents/middleware/context_editing.py +306 -0
- coco/agents/middleware/file_search.py +433 -0
- coco/agents/middleware/human_in_the_loop.py +680 -0
- coco/agents/middleware/internal_call_transformer.py +108 -0
- coco/agents/middleware/model_call_limit.py +267 -0
- coco/agents/middleware/model_fallback.py +417 -0
- coco/agents/middleware/model_retry.py +327 -0
- coco/agents/middleware/pii.py +878 -0
- coco/agents/middleware/provider_tool_search.py +307 -0
- coco/agents/middleware/shell_tool.py +949 -0
- coco/agents/middleware/summarization.py +910 -0
- coco/agents/middleware/todo.py +357 -0
- coco/agents/middleware/tool_call_limit.py +495 -0
- coco/agents/middleware/tool_emulator.py +251 -0
- coco/agents/middleware/tool_error.py +209 -0
- coco/agents/middleware/tool_retry.py +421 -0
- coco/agents/middleware/tool_selection.py +515 -0
- coco/agents/middleware/types.py +2201 -0
- coco/agents/structured_output.py +472 -0
- coco/chat_models/__init__.py +7 -0
- coco/chat_models/base.py +1082 -0
- coco/embeddings/__init__.py +18 -0
- coco/embeddings/base.py +279 -0
- coco/mcp/__init__.py +36 -0
- coco/mcp/adapter.py +245 -0
- coco/mcp/elicitation.py +394 -0
- coco/mcp/tools.py +307 -0
- coco/messages/__init__.py +73 -0
- coco/py.typed +0 -0
- coco/rate_limiters/__init__.py +13 -0
- coco/tools/__init__.py +22 -0
- coco/tools/tool_node.py +20 -0
- coco_anthropic/__init__.py +15 -0
- coco_anthropic/_client_utils.py +81 -0
- coco_anthropic/_compat.py +228 -0
- coco_anthropic/_sdk_compat.py +124 -0
- coco_anthropic/_version.py +3 -0
- coco_anthropic/chat_models.py +3349 -0
- coco_anthropic/data/__init__.py +1 -0
- coco_anthropic/data/_profiles.py +491 -0
- coco_anthropic/data/profile_augmentations.toml +69 -0
- coco_anthropic/experimental.py +140 -0
- coco_anthropic/llms.py +428 -0
- coco_anthropic/middleware/__init__.py +25 -0
- coco_anthropic/middleware/anthropic_tools.py +1197 -0
- coco_anthropic/middleware/bash.py +84 -0
- coco_anthropic/middleware/file_search.py +360 -0
- coco_anthropic/middleware/prompt_caching.py +276 -0
- coco_anthropic/output_parsers.py +93 -0
- coco_anthropic/py.typed +0 -0
- coco_core/__init__.py +20 -0
- coco_core/_api/__init__.py +87 -0
- coco_core/_api/beta_decorator.py +266 -0
- coco_core/_api/deprecation.py +637 -0
- coco_core/_api/internal.py +23 -0
- coco_core/_api/path.py +50 -0
- coco_core/_import_utils.py +41 -0
- coco_core/_protocol.py +625 -0
- coco_core/_security/__init__.py +36 -0
- coco_core/_security/_exceptions.py +9 -0
- coco_core/_security/_policy.py +313 -0
- coco_core/_security/_ssrf_protection.py +155 -0
- coco_core/_security/_transport.py +254 -0
- coco_core/agents.py +258 -0
- coco_core/caches.py +272 -0
- coco_core/callbacks/__init__.py +132 -0
- coco_core/callbacks/base.py +1229 -0
- coco_core/callbacks/file.py +266 -0
- coco_core/callbacks/manager.py +2826 -0
- coco_core/callbacks/stdout.py +123 -0
- coco_core/callbacks/streaming_stdout.py +154 -0
- coco_core/callbacks/usage.py +124 -0
- coco_core/chat_history.py +267 -0
- coco_core/chat_loaders.py +26 -0
- coco_core/chat_sessions.py +19 -0
- coco_core/cross_encoders.py +18 -0
- coco_core/document_loaders/__init__.py +36 -0
- coco_core/document_loaders/base.py +155 -0
- coco_core/document_loaders/blob_loaders.py +38 -0
- coco_core/documents/__init__.py +55 -0
- coco_core/documents/base.py +347 -0
- coco_core/documents/compressor.py +74 -0
- coco_core/documents/transformers.py +79 -0
- coco_core/embeddings/__init__.py +31 -0
- coco_core/embeddings/embeddings.py +78 -0
- coco_core/embeddings/fake.py +129 -0
- coco_core/env.py +22 -0
- coco_core/example_selectors/__init__.py +47 -0
- coco_core/example_selectors/base.py +60 -0
- coco_core/example_selectors/length_based.py +131 -0
- coco_core/example_selectors/semantic_similarity.py +366 -0
- coco_core/exceptions.py +166 -0
- coco_core/globals.py +72 -0
- coco_core/indexing/__init__.py +53 -0
- coco_core/indexing/api.py +954 -0
- coco_core/indexing/base.py +661 -0
- coco_core/indexing/in_memory.py +104 -0
- coco_core/language_models/__init__.py +116 -0
- coco_core/language_models/_compat_bridge.py +844 -0
- coco_core/language_models/_utils.py +343 -0
- coco_core/language_models/base.py +500 -0
- coco_core/language_models/chat_model_stream.py +1525 -0
- coco_core/language_models/chat_models.py +2748 -0
- coco_core/language_models/fake.py +137 -0
- coco_core/language_models/fake_chat_models.py +396 -0
- coco_core/language_models/llms.py +1569 -0
- coco_core/language_models/model_profile.py +183 -0
- coco_core/load/__init__.py +44 -0
- coco_core/load/_validation.py +191 -0
- coco_core/load/dump.py +120 -0
- coco_core/load/load.py +828 -0
- coco_core/load/mapping.py +1097 -0
- coco_core/load/serializable.py +406 -0
- coco_core/messages/__init__.py +198 -0
- coco_core/messages/ai.py +854 -0
- coco_core/messages/base.py +518 -0
- coco_core/messages/block_translators/__init__.py +112 -0
- coco_core/messages/block_translators/anthropic.py +534 -0
- coco_core/messages/block_translators/bedrock.py +108 -0
- coco_core/messages/block_translators/bedrock_converse.py +329 -0
- coco_core/messages/block_translators/coco_v0.py +303 -0
- coco_core/messages/block_translators/google_genai.py +610 -0
- coco_core/messages/block_translators/google_vertexai.py +21 -0
- coco_core/messages/block_translators/groq.py +157 -0
- coco_core/messages/block_translators/openai.py +1089 -0
- coco_core/messages/chat.py +64 -0
- coco_core/messages/content.py +1488 -0
- coco_core/messages/function.py +62 -0
- coco_core/messages/human.py +70 -0
- coco_core/messages/modifier.py +33 -0
- coco_core/messages/system.py +70 -0
- coco_core/messages/tool.py +418 -0
- coco_core/messages/utils.py +2442 -0
- coco_core/output_parsers/__init__.py +101 -0
- coco_core/output_parsers/base.py +361 -0
- coco_core/output_parsers/format_instructions.py +16 -0
- coco_core/output_parsers/json.py +139 -0
- coco_core/output_parsers/list.py +249 -0
- coco_core/output_parsers/openai_functions.py +324 -0
- coco_core/output_parsers/openai_tools.py +383 -0
- coco_core/output_parsers/pydantic.py +141 -0
- coco_core/output_parsers/string.py +63 -0
- coco_core/output_parsers/transform.py +175 -0
- coco_core/output_parsers/xml.py +301 -0
- coco_core/outputs/__init__.py +66 -0
- coco_core/outputs/chat_generation.py +147 -0
- coco_core/outputs/chat_result.py +39 -0
- coco_core/outputs/generation.py +80 -0
- coco_core/outputs/llm_result.py +112 -0
- coco_core/outputs/run_info.py +22 -0
- coco_core/prompt_values.py +161 -0
- coco_core/prompts/__init__.py +101 -0
- coco_core/prompts/base.py +508 -0
- coco_core/prompts/chat.py +1495 -0
- coco_core/prompts/dict.py +177 -0
- coco_core/prompts/few_shot.py +483 -0
- coco_core/prompts/few_shot_with_templates.py +239 -0
- coco_core/prompts/image.py +177 -0
- coco_core/prompts/loading.py +296 -0
- coco_core/prompts/message.py +97 -0
- coco_core/prompts/prompt.py +312 -0
- coco_core/prompts/string.py +399 -0
- coco_core/prompts/structured.py +214 -0
- coco_core/py.typed +0 -0
- coco_core/rate_limiters.py +254 -0
- coco_core/retrievers.py +328 -0
- coco_core/runnables/__init__.py +136 -0
- coco_core/runnables/base.py +6725 -0
- coco_core/runnables/branch.py +457 -0
- coco_core/runnables/config.py +712 -0
- coco_core/runnables/configurable.py +710 -0
- coco_core/runnables/fallbacks.py +663 -0
- coco_core/runnables/graph.py +741 -0
- coco_core/runnables/graph_ascii.py +366 -0
- coco_core/runnables/graph_mermaid.py +503 -0
- coco_core/runnables/graph_png.py +215 -0
- coco_core/runnables/history.py +631 -0
- coco_core/runnables/passthrough.py +849 -0
- coco_core/runnables/retry.py +379 -0
- coco_core/runnables/router.py +239 -0
- coco_core/runnables/schema.py +188 -0
- coco_core/runnables/utils.py +798 -0
- coco_core/stores.py +291 -0
- coco_core/structured_query.py +203 -0
- coco_core/sys_info.py +154 -0
- coco_core/tools/__init__.py +95 -0
- coco_core/tools/base.py +1948 -0
- coco_core/tools/convert.py +497 -0
- coco_core/tools/render.py +67 -0
- coco_core/tools/retriever.py +94 -0
- coco_core/tools/simple.py +208 -0
- coco_core/tools/structured.py +322 -0
- coco_core/tracers/__init__.py +47 -0
- coco_core/tracers/_compat.py +95 -0
- coco_core/tracers/_context.py +337 -0
- coco_core/tracers/_streaming.py +53 -0
- coco_core/tracers/base.py +955 -0
- coco_core/tracers/coco.py +279 -0
- coco_core/tracers/context.py +205 -0
- coco_core/tracers/core.py +766 -0
- coco_core/tracers/event_stream.py +1101 -0
- coco_core/tracers/log_stream.py +765 -0
- coco_core/tracers/memory_stream.py +200 -0
- coco_core/tracers/root_listeners.py +130 -0
- coco_core/tracers/run_collector.py +40 -0
- coco_core/tracers/schemas.py +14 -0
- coco_core/tracers/stdout.py +205 -0
- coco_core/utils/__init__.py +111 -0
- coco_core/utils/_gateway.py +274 -0
- coco_core/utils/_merge.py +218 -0
- coco_core/utils/aiter.py +357 -0
- coco_core/utils/env.py +86 -0
- coco_core/utils/formatting.py +80 -0
- coco_core/utils/function_calling.py +869 -0
- coco_core/utils/html.py +132 -0
- coco_core/utils/image.py +15 -0
- coco_core/utils/input.py +82 -0
- coco_core/utils/interactive_env.py +12 -0
- coco_core/utils/iter.py +229 -0
- coco_core/utils/json.py +230 -0
- coco_core/utils/json_schema.py +273 -0
- coco_core/utils/mustache.py +706 -0
- coco_core/utils/pydantic.py +631 -0
- coco_core/utils/strings.py +69 -0
- coco_core/utils/usage.py +61 -0
- coco_core/utils/utils.py +528 -0
- coco_core/utils/uuid.py +57 -0
- coco_core/vectorstores/__init__.py +53 -0
- coco_core/vectorstores/base.py +1111 -0
- coco_core/vectorstores/in_memory.py +546 -0
- coco_core/vectorstores/utils.py +163 -0
- coco_core/version.py +3 -0
- coco_framework-0.1.0.dist-info/METADATA +184 -0
- coco_framework-0.1.0.dist-info/RECORD +400 -0
- coco_framework-0.1.0.dist-info/WHEEL +4 -0
- coco_framework-0.1.0.dist-info/licenses/LICENSE +24 -0
- coco_framework-0.1.0.dist-info/licenses/NOTICE +15 -0
- coco_graph/_internal/__init__.py +4 -0
- coco_graph/_internal/_cache.py +31 -0
- coco_graph/_internal/_config.py +474 -0
- coco_graph/_internal/_constants.py +140 -0
- coco_graph/_internal/_fields.py +213 -0
- coco_graph/_internal/_future.py +220 -0
- coco_graph/_internal/_pydantic.py +275 -0
- coco_graph/_internal/_queue.py +124 -0
- coco_graph/_internal/_replay.py +90 -0
- coco_graph/_internal/_retry.py +29 -0
- coco_graph/_internal/_runnable.py +969 -0
- coco_graph/_internal/_scratchpad.py +19 -0
- coco_graph/_internal/_serde.py +253 -0
- coco_graph/_internal/_timeout.py +25 -0
- coco_graph/_internal/_typing.py +54 -0
- coco_graph/cache/base/__init__.py +48 -0
- coco_graph/cache/base/py.typed +0 -0
- coco_graph/cache/memory/__init__.py +73 -0
- coco_graph/cache/redis/__init__.py +144 -0
- coco_graph/cache/sqlite/__init__.py +120 -0
- coco_graph/callbacks.py +394 -0
- coco_graph/channels/__init__.py +29 -0
- coco_graph/channels/any_value.py +72 -0
- coco_graph/channels/base.py +121 -0
- coco_graph/channels/binop.py +155 -0
- coco_graph/channels/delta.py +202 -0
- coco_graph/channels/ephemeral_value.py +79 -0
- coco_graph/channels/last_value.py +151 -0
- coco_graph/channels/named_barrier_value.py +167 -0
- coco_graph/channels/topic.py +94 -0
- coco_graph/channels/untracked_value.py +73 -0
- coco_graph/checkpoint/base/__init__.py +861 -0
- coco_graph/checkpoint/base/id.py +109 -0
- coco_graph/checkpoint/base/py.typed +0 -0
- coco_graph/checkpoint/memory/__init__.py +698 -0
- coco_graph/checkpoint/memory/py.typed +0 -0
- coco_graph/checkpoint/postgres/__init__.py +602 -0
- coco_graph/checkpoint/postgres/_ainternal.py +23 -0
- coco_graph/checkpoint/postgres/_internal.py +21 -0
- coco_graph/checkpoint/postgres/aio.py +691 -0
- coco_graph/checkpoint/postgres/base.py +666 -0
- coco_graph/checkpoint/postgres/py.typed +0 -0
- coco_graph/checkpoint/postgres/shallow.py +967 -0
- coco_graph/checkpoint/serde/__init__.py +0 -0
- coco_graph/checkpoint/serde/_msgpack.py +97 -0
- coco_graph/checkpoint/serde/base.py +64 -0
- coco_graph/checkpoint/serde/encrypted.py +80 -0
- coco_graph/checkpoint/serde/event_hooks.py +52 -0
- coco_graph/checkpoint/serde/jsonplus.py +883 -0
- coco_graph/checkpoint/serde/py.typed +0 -0
- coco_graph/checkpoint/serde/types.py +68 -0
- coco_graph/checkpoint/sqlite/__init__.py +645 -0
- coco_graph/checkpoint/sqlite/_delta.py +172 -0
- coco_graph/checkpoint/sqlite/aio.py +738 -0
- coco_graph/checkpoint/sqlite/py.typed +0 -0
- coco_graph/checkpoint/sqlite/utils.py +116 -0
- coco_graph/config.py +196 -0
- coco_graph/constants.py +64 -0
- coco_graph/errors.py +241 -0
- coco_graph/func/__init__.py +620 -0
- coco_graph/graph/__init__.py +12 -0
- coco_graph/graph/_branch.py +225 -0
- coco_graph/graph/_node.py +103 -0
- coco_graph/graph/message.py +437 -0
- coco_graph/graph/state.py +1978 -0
- coco_graph/graph/ui.py +227 -0
- coco_graph/managed/__init__.py +3 -0
- coco_graph/managed/base.py +31 -0
- coco_graph/managed/is_last_step.py +24 -0
- coco_graph/prebuilt/__init__.py +23 -0
- coco_graph/prebuilt/_tool_call_stream.py +117 -0
- coco_graph/prebuilt/_tool_call_transformer.py +165 -0
- coco_graph/prebuilt/chat_agent_executor.py +1015 -0
- coco_graph/prebuilt/interrupt.py +105 -0
- coco_graph/prebuilt/py.typed +0 -0
- coco_graph/prebuilt/tool_node.py +2030 -0
- coco_graph/prebuilt/tool_validator.py +221 -0
- coco_graph/pregel/__init__.py +3 -0
- coco_graph/pregel/_algo.py +1460 -0
- coco_graph/pregel/_call.py +298 -0
- coco_graph/pregel/_checkpoint.py +331 -0
- coco_graph/pregel/_config.py +0 -0
- coco_graph/pregel/_draw.py +294 -0
- coco_graph/pregel/_executor.py +223 -0
- coco_graph/pregel/_io.py +174 -0
- coco_graph/pregel/_log.py +3 -0
- coco_graph/pregel/_loop.py +1988 -0
- coco_graph/pregel/_messages.py +461 -0
- coco_graph/pregel/_read.py +312 -0
- coco_graph/pregel/_retry.py +854 -0
- coco_graph/pregel/_runner.py +941 -0
- coco_graph/pregel/_tools.py +268 -0
- coco_graph/pregel/_utils.py +221 -0
- coco_graph/pregel/_validate.py +120 -0
- coco_graph/pregel/_write.py +192 -0
- coco_graph/pregel/debug.py +302 -0
- coco_graph/pregel/main.py +4364 -0
- coco_graph/pregel/protocol.py +288 -0
- coco_graph/pregel/types.py +38 -0
- coco_graph/py.typed +0 -0
- coco_graph/runtime.py +350 -0
- coco_graph/store/base/__init__.py +1322 -0
- coco_graph/store/base/batch.py +371 -0
- coco_graph/store/base/embed.py +436 -0
- coco_graph/store/base/py.typed +0 -0
- coco_graph/store/memory/__init__.py +592 -0
- coco_graph/store/memory/py.typed +0 -0
- coco_graph/store/postgres/__init__.py +4 -0
- coco_graph/store/postgres/aio.py +592 -0
- coco_graph/store/postgres/base.py +1480 -0
- coco_graph/store/postgres/py.typed +0 -0
- coco_graph/store/sqlite/__init__.py +4 -0
- coco_graph/store/sqlite/aio.py +632 -0
- coco_graph/store/sqlite/base.py +1514 -0
- coco_graph/stream/__init__.py +45 -0
- coco_graph/stream/_convert.py +32 -0
- coco_graph/stream/_mux.py +523 -0
- coco_graph/stream/_types.py +330 -0
- coco_graph/stream/run_stream.py +735 -0
- coco_graph/stream/stream_channel.py +341 -0
- coco_graph/stream/transformers.py +1039 -0
- coco_graph/types.py +1079 -0
- coco_graph/typing.py +48 -0
- coco_graph/utils/__init__.py +1 -0
- coco_graph/utils/config.py +4 -0
- coco_graph/utils/runnable.py +3 -0
- coco_graph/version.py +13 -0
- coco_graph/warnings.py +69 -0
- coco_openai/__init__.py +20 -0
- coco_openai/_compat.py +45 -0
- coco_openai/_version.py +3 -0
- coco_openai/chat_models/__init__.py +6 -0
- coco_openai/chat_models/_client_utils.py +684 -0
- coco_openai/chat_models/_compat.py +525 -0
- coco_openai/chat_models/azure.py +1227 -0
- coco_openai/chat_models/base.py +5654 -0
- coco_openai/chat_models/codex.py +557 -0
- coco_openai/chatgpt_oauth.py +1063 -0
- coco_openai/data/__init__.py +1 -0
- coco_openai/data/_profiles.py +1655 -0
- coco_openai/data/profile_augmentations.toml +127 -0
- coco_openai/embeddings/__init__.py +6 -0
- coco_openai/embeddings/azure.py +240 -0
- coco_openai/embeddings/base.py +820 -0
- coco_openai/llms/__init__.py +6 -0
- coco_openai/llms/azure.py +249 -0
- coco_openai/llms/base.py +919 -0
- coco_openai/middleware/__init__.py +11 -0
- coco_openai/middleware/openai_moderation.py +484 -0
- coco_openai/output_parsers/__init__.py +9 -0
- coco_openai/output_parsers/tools.py +9 -0
- coco_openai/py.typed +0 -0
- coco_openai/tools/__init__.py +5 -0
- coco_openai/tools/custom_tool.py +111 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Anthropic-specific middleware for the Claude bash tool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from coco.agents.middleware.shell_tool import ShellToolMiddleware
|
|
9
|
+
from coco.agents.middleware.types import (
|
|
10
|
+
ModelRequest,
|
|
11
|
+
ModelResponse,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Tool type constants for Anthropic
|
|
15
|
+
BASH_TOOL_TYPE = "bash_20250124"
|
|
16
|
+
BASH_TOOL_NAME = "bash"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ClaudeBashToolMiddleware(ShellToolMiddleware):
|
|
20
|
+
"""Middleware that exposes Anthropic's native bash tool to models."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
workspace_root: str | None = None,
|
|
25
|
+
*,
|
|
26
|
+
startup_commands: tuple[str, ...] | list[str] | str | None = None,
|
|
27
|
+
shutdown_commands: tuple[str, ...] | list[str] | str | None = None,
|
|
28
|
+
execution_policy: Any | None = None,
|
|
29
|
+
redaction_rules: tuple[Any, ...] | list[Any] | None = None,
|
|
30
|
+
tool_description: str | None = None,
|
|
31
|
+
env: dict[str, Any] | None = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
"""Initialize middleware for Claude's native bash tool.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
workspace_root: Base directory for the shell session.
|
|
37
|
+
|
|
38
|
+
If omitted, a temporary directory is created.
|
|
39
|
+
startup_commands: Optional commands executed after the session starts.
|
|
40
|
+
shutdown_commands: Optional commands executed before session shutdown.
|
|
41
|
+
execution_policy: Execution policy controlling timeouts and limits.
|
|
42
|
+
redaction_rules: Optional redaction rules to sanitize output.
|
|
43
|
+
tool_description: Optional override for tool description.
|
|
44
|
+
env: Optional environment variables for the shell session.
|
|
45
|
+
"""
|
|
46
|
+
super().__init__(
|
|
47
|
+
workspace_root=workspace_root,
|
|
48
|
+
startup_commands=startup_commands,
|
|
49
|
+
shutdown_commands=shutdown_commands,
|
|
50
|
+
execution_policy=execution_policy,
|
|
51
|
+
redaction_rules=redaction_rules,
|
|
52
|
+
tool_description=tool_description,
|
|
53
|
+
tool_name=BASH_TOOL_NAME,
|
|
54
|
+
shell_command=("/bin/bash",),
|
|
55
|
+
env=env,
|
|
56
|
+
)
|
|
57
|
+
# Parent class now creates the tool with name "bash" via tool_name parameter
|
|
58
|
+
|
|
59
|
+
def wrap_model_call(
|
|
60
|
+
self,
|
|
61
|
+
request: ModelRequest,
|
|
62
|
+
handler: Callable[[ModelRequest], ModelResponse],
|
|
63
|
+
) -> ModelResponse:
|
|
64
|
+
"""Replace parent's shell tool with Claude's bash descriptor."""
|
|
65
|
+
filtered = [
|
|
66
|
+
t for t in request.tools if getattr(t, "name", None) != BASH_TOOL_NAME
|
|
67
|
+
]
|
|
68
|
+
tools = [*filtered, {"type": BASH_TOOL_TYPE, "name": BASH_TOOL_NAME}]
|
|
69
|
+
return handler(request.override(tools=tools))
|
|
70
|
+
|
|
71
|
+
async def awrap_model_call(
|
|
72
|
+
self,
|
|
73
|
+
request: ModelRequest,
|
|
74
|
+
handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
|
|
75
|
+
) -> ModelResponse:
|
|
76
|
+
"""Async: replace parent's shell tool with Claude's bash descriptor."""
|
|
77
|
+
filtered = [
|
|
78
|
+
t for t in request.tools if getattr(t, "name", None) != BASH_TOOL_NAME
|
|
79
|
+
]
|
|
80
|
+
tools = [*filtered, {"type": BASH_TOOL_TYPE, "name": BASH_TOOL_NAME}]
|
|
81
|
+
return await handler(request.override(tools=tools))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
__all__ = ["ClaudeBashToolMiddleware"]
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"""File search middleware for Anthropic text editor and memory tools.
|
|
2
|
+
|
|
3
|
+
This module provides Glob and Grep search tools that operate on files stored
|
|
4
|
+
in state or filesystem.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import fnmatch
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path, PurePosixPath
|
|
12
|
+
from typing import TYPE_CHECKING, Literal, cast
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from coco.agents.middleware.types import AgentMiddleware
|
|
18
|
+
from coco.tools import ToolRuntime, tool
|
|
19
|
+
|
|
20
|
+
from coco_anthropic.middleware.anthropic_tools import AnthropicToolsState
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _expand_include_patterns(pattern: str) -> list[str] | None:
|
|
24
|
+
"""Expand brace patterns like `*.{py,pyi}` into a list of globs."""
|
|
25
|
+
if "}" in pattern and "{" not in pattern:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
expanded: list[str] = []
|
|
29
|
+
|
|
30
|
+
def _expand(current: str) -> None:
|
|
31
|
+
start = current.find("{")
|
|
32
|
+
if start == -1:
|
|
33
|
+
expanded.append(current)
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
end = current.find("}", start)
|
|
37
|
+
if end == -1:
|
|
38
|
+
msg = f"Unbalanced brace in pattern: '{current}' is missing a '}}'."
|
|
39
|
+
raise ValueError(msg)
|
|
40
|
+
|
|
41
|
+
prefix = current[:start]
|
|
42
|
+
suffix = current[end + 1 :]
|
|
43
|
+
inner = current[start + 1 : end]
|
|
44
|
+
if not inner:
|
|
45
|
+
msg = f"Empty brace expansion in pattern: '{current}'."
|
|
46
|
+
raise ValueError(msg)
|
|
47
|
+
|
|
48
|
+
for option in inner.split(","):
|
|
49
|
+
_expand(prefix + option + suffix)
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
_expand(pattern)
|
|
53
|
+
except ValueError:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
return expanded
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _is_valid_include_pattern(pattern: str) -> bool:
|
|
60
|
+
"""Validate glob pattern used for include filters."""
|
|
61
|
+
if not pattern:
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
if any(char in pattern for char in ("\x00", "\n", "\r")):
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
expanded = _expand_include_patterns(pattern)
|
|
68
|
+
if expanded is None:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
for candidate in expanded:
|
|
73
|
+
re.compile(fnmatch.translate(candidate))
|
|
74
|
+
except re.error:
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _match_include_pattern(basename: str, pattern: str) -> bool:
|
|
81
|
+
"""Return `True` if the basename matches the include pattern."""
|
|
82
|
+
expanded = _expand_include_patterns(pattern)
|
|
83
|
+
if not expanded:
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
return any(fnmatch.fnmatch(basename, candidate) for candidate in expanded)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class StateFileSearchMiddleware(AgentMiddleware):
|
|
90
|
+
"""Provides Glob and Grep search over state-based files.
|
|
91
|
+
|
|
92
|
+
This middleware adds two tools that search through virtual files in state:
|
|
93
|
+
|
|
94
|
+
- Glob: Fast file pattern matching by file path
|
|
95
|
+
- Grep: Fast content search using regular expressions
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
```python
|
|
99
|
+
from coco.agents import create_agent
|
|
100
|
+
from coco.agents.middleware import (
|
|
101
|
+
StateTextEditorToolMiddleware,
|
|
102
|
+
StateFileSearchMiddleware,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
agent = create_agent(
|
|
106
|
+
model=model,
|
|
107
|
+
tools=[],
|
|
108
|
+
middleware=[
|
|
109
|
+
StateTextEditorToolMiddleware(),
|
|
110
|
+
StateFileSearchMiddleware(),
|
|
111
|
+
],
|
|
112
|
+
)
|
|
113
|
+
```
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
state_schema = AnthropicToolsState
|
|
117
|
+
|
|
118
|
+
def __init__(
|
|
119
|
+
self,
|
|
120
|
+
*,
|
|
121
|
+
state_key: str = "text_editor_files",
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Initialize the search middleware.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
state_key: State key to search
|
|
127
|
+
|
|
128
|
+
Use `'memory_files'` to search memory tool files.
|
|
129
|
+
"""
|
|
130
|
+
self.state_key = state_key
|
|
131
|
+
|
|
132
|
+
# Create tool instances
|
|
133
|
+
@tool
|
|
134
|
+
def glob_search( # noqa: D417
|
|
135
|
+
runtime: ToolRuntime[None, AnthropicToolsState],
|
|
136
|
+
pattern: str,
|
|
137
|
+
path: str = "/",
|
|
138
|
+
) -> str:
|
|
139
|
+
"""Fast file pattern matching tool that works with any codebase size.
|
|
140
|
+
|
|
141
|
+
Supports glob patterns like `**/*.js` or `src/**/*.ts`.
|
|
142
|
+
|
|
143
|
+
Returns matching file paths sorted by modification time.
|
|
144
|
+
|
|
145
|
+
Use this tool when you need to find files by name patterns.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
pattern: The glob pattern to match files against.
|
|
149
|
+
path: The directory to search in.
|
|
150
|
+
|
|
151
|
+
If not specified, searches from root.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Newline-separated list of matching file paths, sorted by modification
|
|
155
|
+
time (most recently modified first).
|
|
156
|
+
|
|
157
|
+
Returns `'No files found'` if no matches.
|
|
158
|
+
"""
|
|
159
|
+
return self._handle_glob_search(pattern, path, runtime.state)
|
|
160
|
+
|
|
161
|
+
@tool
|
|
162
|
+
def grep_search( # noqa: D417
|
|
163
|
+
runtime: ToolRuntime[None, AnthropicToolsState],
|
|
164
|
+
pattern: str,
|
|
165
|
+
path: str = "/",
|
|
166
|
+
include: str | None = None,
|
|
167
|
+
output_mode: Literal[
|
|
168
|
+
"files_with_matches", "content", "count"
|
|
169
|
+
] = "files_with_matches",
|
|
170
|
+
) -> str:
|
|
171
|
+
"""Fast content search tool that works with any codebase size.
|
|
172
|
+
|
|
173
|
+
Searches file contents using regular expressions.
|
|
174
|
+
|
|
175
|
+
Supports full regex syntax and filters files by pattern with the include
|
|
176
|
+
parameter.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
pattern: The regular expression pattern to search for in file contents.
|
|
180
|
+
path: The directory to search in. If not specified, searches from root.
|
|
181
|
+
include: File pattern to filter (e.g., `'*.js'`, `'*.{ts,tsx}'`).
|
|
182
|
+
output_mode: Output format.
|
|
183
|
+
|
|
184
|
+
Options:
|
|
185
|
+
|
|
186
|
+
- `'files_with_matches'`: Only file paths containing matches
|
|
187
|
+
- `'content'`: Matching lines with file:line:content format
|
|
188
|
+
- `'count'`: Count of matches per file
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Search results formatted according to `output_mode`.
|
|
192
|
+
|
|
193
|
+
Returns `'No matches found'` if no results.
|
|
194
|
+
"""
|
|
195
|
+
return self._handle_grep_search(
|
|
196
|
+
pattern, path, include, output_mode, runtime.state
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
self.glob_search = glob_search
|
|
200
|
+
self.grep_search = grep_search
|
|
201
|
+
self.tools = [glob_search, grep_search]
|
|
202
|
+
|
|
203
|
+
def _handle_glob_search(
|
|
204
|
+
self,
|
|
205
|
+
pattern: str,
|
|
206
|
+
path: str,
|
|
207
|
+
state: AnthropicToolsState,
|
|
208
|
+
) -> str:
|
|
209
|
+
"""Handle glob search operation.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
pattern: The glob pattern to match files against.
|
|
213
|
+
path: The directory to search in.
|
|
214
|
+
state: The current agent state.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Newline-separated list of matching file paths, sorted by modification
|
|
218
|
+
time (most recently modified first).
|
|
219
|
+
|
|
220
|
+
Returns `'No files found'` if no matches.
|
|
221
|
+
"""
|
|
222
|
+
# Normalize base path
|
|
223
|
+
base_path = path if path.startswith("/") else "/" + path
|
|
224
|
+
|
|
225
|
+
# Get files from state
|
|
226
|
+
files = cast("dict[str, Any]", state.get(self.state_key, {}))
|
|
227
|
+
|
|
228
|
+
# Match files
|
|
229
|
+
matches = []
|
|
230
|
+
for file_path, file_data in files.items():
|
|
231
|
+
if file_path.startswith(base_path):
|
|
232
|
+
# Get relative path from base
|
|
233
|
+
if base_path == "/":
|
|
234
|
+
relative = file_path[1:] # Remove leading /
|
|
235
|
+
elif file_path == base_path:
|
|
236
|
+
relative = Path(file_path).name
|
|
237
|
+
elif file_path.startswith(base_path + "/"):
|
|
238
|
+
relative = file_path[len(base_path) + 1 :]
|
|
239
|
+
else:
|
|
240
|
+
continue
|
|
241
|
+
|
|
242
|
+
# Match against pattern
|
|
243
|
+
# Handle ** pattern which requires special care
|
|
244
|
+
# PurePosixPath.match doesn't match single-level paths
|
|
245
|
+
# against **/pattern
|
|
246
|
+
is_match = PurePosixPath(relative).match(pattern)
|
|
247
|
+
if not is_match and pattern.startswith("**/"):
|
|
248
|
+
# Also try matching without the **/ prefix for files in base dir
|
|
249
|
+
is_match = PurePosixPath(relative).match(pattern[3:])
|
|
250
|
+
|
|
251
|
+
if is_match:
|
|
252
|
+
matches.append((file_path, file_data["modified_at"]))
|
|
253
|
+
|
|
254
|
+
if not matches:
|
|
255
|
+
return "No files found"
|
|
256
|
+
|
|
257
|
+
# Sort by modification time
|
|
258
|
+
matches.sort(key=lambda x: x[1], reverse=True)
|
|
259
|
+
file_paths = [path for path, _ in matches]
|
|
260
|
+
|
|
261
|
+
return "\n".join(file_paths)
|
|
262
|
+
|
|
263
|
+
def _handle_grep_search(
|
|
264
|
+
self,
|
|
265
|
+
pattern: str,
|
|
266
|
+
path: str,
|
|
267
|
+
include: str | None,
|
|
268
|
+
output_mode: str,
|
|
269
|
+
state: AnthropicToolsState,
|
|
270
|
+
) -> str:
|
|
271
|
+
"""Handle grep search operation.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
pattern: The regular expression pattern to search for in file contents.
|
|
275
|
+
path: The directory to search in.
|
|
276
|
+
include: File pattern to filter (e.g., `'*.js'`, `'*.{ts,tsx}'`).
|
|
277
|
+
output_mode: Output format.
|
|
278
|
+
state: The current agent state.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Search results formatted according to `output_mode`.
|
|
282
|
+
|
|
283
|
+
Returns `'No matches found'` if no results.
|
|
284
|
+
"""
|
|
285
|
+
# Normalize base path
|
|
286
|
+
base_path = path if path.startswith("/") else "/" + path
|
|
287
|
+
|
|
288
|
+
# Compile regex pattern (for validation)
|
|
289
|
+
try:
|
|
290
|
+
regex = re.compile(pattern)
|
|
291
|
+
except re.error as e:
|
|
292
|
+
return f"Invalid regex pattern: {e}"
|
|
293
|
+
|
|
294
|
+
if include and not _is_valid_include_pattern(include):
|
|
295
|
+
return "Invalid include pattern"
|
|
296
|
+
|
|
297
|
+
# Search files
|
|
298
|
+
files = cast("dict[str, Any]", state.get(self.state_key, {}))
|
|
299
|
+
results: dict[str, list[tuple[int, str]]] = {}
|
|
300
|
+
|
|
301
|
+
for file_path, file_data in files.items():
|
|
302
|
+
if (
|
|
303
|
+
base_path != "/" # noqa: PLR1714
|
|
304
|
+
and file_path != base_path
|
|
305
|
+
and not file_path.startswith(base_path + "/")
|
|
306
|
+
):
|
|
307
|
+
continue
|
|
308
|
+
|
|
309
|
+
# Check include filter
|
|
310
|
+
if include:
|
|
311
|
+
basename = Path(file_path).name
|
|
312
|
+
if not _match_include_pattern(basename, include):
|
|
313
|
+
continue
|
|
314
|
+
|
|
315
|
+
# Search file content
|
|
316
|
+
for line_num, line in enumerate(file_data["content"], 1):
|
|
317
|
+
if regex.search(line):
|
|
318
|
+
if file_path not in results:
|
|
319
|
+
results[file_path] = []
|
|
320
|
+
results[file_path].append((line_num, line))
|
|
321
|
+
|
|
322
|
+
if not results:
|
|
323
|
+
return "No matches found"
|
|
324
|
+
|
|
325
|
+
# Format output based on mode
|
|
326
|
+
return self._format_grep_results(results, output_mode)
|
|
327
|
+
|
|
328
|
+
def _format_grep_results(
|
|
329
|
+
self,
|
|
330
|
+
results: dict[str, list[tuple[int, str]]],
|
|
331
|
+
output_mode: str,
|
|
332
|
+
) -> str:
|
|
333
|
+
"""Format grep results based on output mode."""
|
|
334
|
+
if output_mode == "files_with_matches":
|
|
335
|
+
# Just return file paths
|
|
336
|
+
return "\n".join(sorted(results.keys()))
|
|
337
|
+
|
|
338
|
+
if output_mode == "content":
|
|
339
|
+
# Return file:line:content format
|
|
340
|
+
lines = []
|
|
341
|
+
for file_path in sorted(results.keys()):
|
|
342
|
+
for line_num, line in results[file_path]:
|
|
343
|
+
lines.append(f"{file_path}:{line_num}:{line}")
|
|
344
|
+
return "\n".join(lines)
|
|
345
|
+
|
|
346
|
+
if output_mode == "count":
|
|
347
|
+
# Return file:count format
|
|
348
|
+
lines = []
|
|
349
|
+
for file_path in sorted(results.keys()):
|
|
350
|
+
count = len(results[file_path])
|
|
351
|
+
lines.append(f"{file_path}:{count}")
|
|
352
|
+
return "\n".join(lines)
|
|
353
|
+
|
|
354
|
+
# Default to files_with_matches
|
|
355
|
+
return "\n".join(sorted(results.keys()))
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
__all__ = [
|
|
359
|
+
"StateFileSearchMiddleware",
|
|
360
|
+
]
|