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
coco/__init__.py
ADDED
coco/agents/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Entrypoint to building [Agents](https://docs.langchain.com/oss/python/langchain/agents) with Coco.""" # noqa: E501
|
|
2
|
+
|
|
3
|
+
from coco.agents.factory import create_agent
|
|
4
|
+
from coco.agents.middleware.types import AgentState
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"AgentState",
|
|
8
|
+
"create_agent",
|
|
9
|
+
]
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"""Surface nested named agents as typed `run.subagents` handles.
|
|
2
|
+
|
|
3
|
+
Detects subagents via the `lc_agent_name` transition that coco_graph's base
|
|
4
|
+
`_TasksLifecycleBase` now computes. `create_agent(name=...)` binds
|
|
5
|
+
`lc_agent_name` into the run config; the base transformer records, per
|
|
6
|
+
namespace, the `lc_agent_name` seen on each task start (first-write-wins).
|
|
7
|
+
|
|
8
|
+
A subagent boundary is a nested run whose `lc_agent_name` is set *and* differs
|
|
9
|
+
from its parent namespace's `lc_agent_name`. Plain subgraphs inherit the
|
|
10
|
+
parent's name (so they compare equal and are excluded); unnamed agents have
|
|
11
|
+
`lc_agent_name == None` (also excluded). For genuine subagents the base also
|
|
12
|
+
recovers the originating tool call and exposes it as a `cause`
|
|
13
|
+
(`{"type": "toolCall", "tool_call_id": ...}`) via `self._pending_cause`, joined
|
|
14
|
+
from the parent task's pending tool calls.
|
|
15
|
+
|
|
16
|
+
This transformer gates on that boundary and surfaces a typed handle on
|
|
17
|
+
`run.subagents`, then forwards child-scope events into the handle's mux so the
|
|
18
|
+
nested run can be consumed independently.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
25
|
+
|
|
26
|
+
from coco_graph.stream.run_stream import (
|
|
27
|
+
AsyncSubgraphRunStream,
|
|
28
|
+
SubgraphRunStream,
|
|
29
|
+
)
|
|
30
|
+
from coco_graph.stream.stream_channel import StreamChannel
|
|
31
|
+
from coco_graph.stream.transformers import (
|
|
32
|
+
SubgraphStatus,
|
|
33
|
+
_TasksLifecycleBase,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from coco_core._protocol import LifecycleCause
|
|
38
|
+
from coco_graph.stream._mux import StreamMux
|
|
39
|
+
from coco_graph.stream._types import ProtocolEvent
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class SubagentRunStream(SubgraphRunStream):
|
|
45
|
+
"""Typed sync handle for a nested named-agent execution.
|
|
46
|
+
|
|
47
|
+
Surfaces on `run.subagents` when a nested run's `lc_agent_name` differs
|
|
48
|
+
from its parent's (i.e., a `create_agent(name=...)` dispatched from a tool).
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
mux: StreamMux,
|
|
54
|
+
*,
|
|
55
|
+
path: tuple[str, ...],
|
|
56
|
+
graph_name: str | None = None,
|
|
57
|
+
trigger_call_id: str | None = None,
|
|
58
|
+
cause: LifecycleCause | None = None,
|
|
59
|
+
) -> None:
|
|
60
|
+
super().__init__(
|
|
61
|
+
mux,
|
|
62
|
+
path=path,
|
|
63
|
+
graph_name=graph_name,
|
|
64
|
+
trigger_call_id=trigger_call_id,
|
|
65
|
+
)
|
|
66
|
+
self._cause = cause
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def name(self) -> str | None:
|
|
70
|
+
"""Subagent name (the nested run's `lc_agent_name`)."""
|
|
71
|
+
return self.graph_name
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def cause(self) -> LifecycleCause | None:
|
|
75
|
+
"""Causation edge — the tool call that triggered this subagent.
|
|
76
|
+
|
|
77
|
+
Returns the `LifecycleCause` recovered by the base transformer (a
|
|
78
|
+
`{"type": "toolCall", "tool_call_id": ...}` dict) when the originating
|
|
79
|
+
tool call could be joined, else `None`.
|
|
80
|
+
"""
|
|
81
|
+
return self._cause
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class AsyncSubagentRunStream(AsyncSubgraphRunStream):
|
|
85
|
+
"""Typed async handle for a nested named-agent execution."""
|
|
86
|
+
|
|
87
|
+
def __init__(
|
|
88
|
+
self,
|
|
89
|
+
mux: StreamMux,
|
|
90
|
+
*,
|
|
91
|
+
path: tuple[str, ...],
|
|
92
|
+
graph_name: str | None = None,
|
|
93
|
+
trigger_call_id: str | None = None,
|
|
94
|
+
cause: LifecycleCause | None = None,
|
|
95
|
+
) -> None:
|
|
96
|
+
super().__init__(
|
|
97
|
+
mux,
|
|
98
|
+
path=path,
|
|
99
|
+
graph_name=graph_name,
|
|
100
|
+
trigger_call_id=trigger_call_id,
|
|
101
|
+
)
|
|
102
|
+
self._cause = cause
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def name(self) -> str | None:
|
|
106
|
+
"""Subagent name (the nested run's `lc_agent_name`)."""
|
|
107
|
+
return self.graph_name
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def cause(self) -> LifecycleCause | None:
|
|
111
|
+
"""Causation edge — the tool call that triggered this subagent.
|
|
112
|
+
|
|
113
|
+
Returns the `LifecycleCause` recovered by the base transformer (a
|
|
114
|
+
`{"type": "toolCall", "tool_call_id": ...}` dict) when the originating
|
|
115
|
+
tool call could be joined, else `None`.
|
|
116
|
+
"""
|
|
117
|
+
return self._cause
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class SubagentTransformer(_TasksLifecycleBase):
|
|
121
|
+
"""Promote nested named agents into typed handles on `run.subagents`.
|
|
122
|
+
|
|
123
|
+
The base `_TasksLifecycleBase` records each namespace's `lc_agent_name`
|
|
124
|
+
(set by `create_agent(name=...)`) and, on every task start, fires
|
|
125
|
+
`_on_started` with the resolved `graph_name` and a `cause` for genuine
|
|
126
|
+
subagent boundaries. This transformer gates on that boundary using the
|
|
127
|
+
inherited `_lc_by_ns` map: a nested run is a subagent when it carries an
|
|
128
|
+
`lc_agent_name`. Same-named nested agents (e.g. a subagent that invokes
|
|
129
|
+
itself) are surfaced; unnamed agents (`None`) are excluded. Trade-off: a
|
|
130
|
+
non-agent subgraph that inherited the parent's name will also surface.
|
|
131
|
+
|
|
132
|
+
On the first matching task start it builds a child mux and emits a typed
|
|
133
|
+
handle on `run.subagents`, then forwards subsequent child-scope events into
|
|
134
|
+
that handle so the nested run can be consumed independently.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
_native: ClassVar[bool] = True
|
|
138
|
+
# Overrides `aprocess` but also runs unchanged on the sync lane via
|
|
139
|
+
# `process`, so it must not be forced into an async-only run.
|
|
140
|
+
supports_sync: ClassVar[bool] = True
|
|
141
|
+
|
|
142
|
+
def __init__(self, scope: tuple[str, ...] = ()) -> None:
|
|
143
|
+
super().__init__(scope)
|
|
144
|
+
self._log: StreamChannel[SubagentRunStream | AsyncSubagentRunStream] = StreamChannel()
|
|
145
|
+
self._handles: dict[tuple[str, ...], SubagentRunStream | AsyncSubagentRunStream] = {}
|
|
146
|
+
self._mux: StreamMux | None = None
|
|
147
|
+
|
|
148
|
+
def init(self) -> dict[str, Any]:
|
|
149
|
+
return {"subagents": self._log}
|
|
150
|
+
|
|
151
|
+
def _on_register(self, mux: StreamMux) -> None:
|
|
152
|
+
self._mux = mux
|
|
153
|
+
|
|
154
|
+
def _should_track(self, ns: tuple[str, ...]) -> bool:
|
|
155
|
+
depth = len(self.scope)
|
|
156
|
+
return len(ns) == depth + 1 and ns[:depth] == self.scope
|
|
157
|
+
|
|
158
|
+
def _on_started(
|
|
159
|
+
self,
|
|
160
|
+
ns: tuple[str, ...],
|
|
161
|
+
graph_name: str | None,
|
|
162
|
+
trigger_call_id: str | None,
|
|
163
|
+
) -> None:
|
|
164
|
+
# coco_graph >=1.2.4 delivers the triggering `cause` via the base's
|
|
165
|
+
# `self._pending_cause` instance state rather than an `_on_started`
|
|
166
|
+
# keyword argument, so overrides predating `cause` keep working. Read it
|
|
167
|
+
# here to surface the originating tool call on the handle.
|
|
168
|
+
cause = self._pending_cause
|
|
169
|
+
child_lc = self._lc_by_ns.get(ns)
|
|
170
|
+
# Surface any nested run carrying an lc_agent_name (set by create_agent).
|
|
171
|
+
# A same-named nested agent — e.g. a subagent that invokes itself —
|
|
172
|
+
# re-asserts its own name and is surfaced. Unnamed runs (None) are
|
|
173
|
+
# excluded. Trade-off: a non-agent subgraph that inherited the parent's
|
|
174
|
+
# name also surfaces; null lc_agent_name when invoking such a graph to
|
|
175
|
+
# exclude it.
|
|
176
|
+
if child_lc is None:
|
|
177
|
+
return
|
|
178
|
+
if self._mux is None or ns in self._handles:
|
|
179
|
+
return
|
|
180
|
+
try:
|
|
181
|
+
child_mux = self._mux._make_child(ns) # noqa: SLF001
|
|
182
|
+
except RuntimeError:
|
|
183
|
+
logger.debug("SubagentTransformer: could not create child mux for %s", ns)
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
handle_cls = AsyncSubagentRunStream if child_mux.is_async else SubagentRunStream
|
|
187
|
+
handle = handle_cls(
|
|
188
|
+
mux=child_mux,
|
|
189
|
+
path=ns,
|
|
190
|
+
graph_name=graph_name,
|
|
191
|
+
trigger_call_id=trigger_call_id,
|
|
192
|
+
cause=cause,
|
|
193
|
+
)
|
|
194
|
+
self._handles[ns] = handle
|
|
195
|
+
self._log.push(handle)
|
|
196
|
+
|
|
197
|
+
def _on_terminal(
|
|
198
|
+
self,
|
|
199
|
+
ns: tuple[str, ...],
|
|
200
|
+
status: SubgraphStatus,
|
|
201
|
+
error: str | None,
|
|
202
|
+
) -> None:
|
|
203
|
+
handle = self._handles.get(ns)
|
|
204
|
+
if handle is None or not self._mark_terminal(handle, status, error):
|
|
205
|
+
return
|
|
206
|
+
self._close_or_fail_handle(handle, status, error)
|
|
207
|
+
|
|
208
|
+
async def _aon_terminal(
|
|
209
|
+
self,
|
|
210
|
+
ns: tuple[str, ...],
|
|
211
|
+
status: SubgraphStatus,
|
|
212
|
+
error: str | None,
|
|
213
|
+
) -> None:
|
|
214
|
+
handle = self._handles.get(ns)
|
|
215
|
+
if handle is None or not self._mark_terminal(handle, status, error):
|
|
216
|
+
return
|
|
217
|
+
await self._aclose_or_fail_handle(handle, status, error)
|
|
218
|
+
|
|
219
|
+
def _mark_terminal(
|
|
220
|
+
self,
|
|
221
|
+
handle: SubagentRunStream | AsyncSubagentRunStream,
|
|
222
|
+
status: SubgraphStatus,
|
|
223
|
+
error: str | None,
|
|
224
|
+
) -> bool:
|
|
225
|
+
"""Mark a handle terminal once. Returns True on the first transition."""
|
|
226
|
+
if handle._seen_terminal: # noqa: SLF001
|
|
227
|
+
return False
|
|
228
|
+
handle.status = status
|
|
229
|
+
if error is not None and handle.error is None:
|
|
230
|
+
handle.error = error
|
|
231
|
+
handle._seen_terminal = True # noqa: SLF001
|
|
232
|
+
return True
|
|
233
|
+
|
|
234
|
+
def _close_or_fail_handle(
|
|
235
|
+
self,
|
|
236
|
+
handle: SubagentRunStream | AsyncSubagentRunStream,
|
|
237
|
+
status: SubgraphStatus,
|
|
238
|
+
error: str | None,
|
|
239
|
+
) -> None:
|
|
240
|
+
if handle._mux is None or handle._mux._events._closed: # noqa: SLF001
|
|
241
|
+
return
|
|
242
|
+
if status == "failed":
|
|
243
|
+
handle._mux.fail(RuntimeError(error or "Subagent failed")) # noqa: SLF001
|
|
244
|
+
else:
|
|
245
|
+
handle._mux.close() # noqa: SLF001
|
|
246
|
+
|
|
247
|
+
async def _aclose_or_fail_handle(
|
|
248
|
+
self,
|
|
249
|
+
handle: SubagentRunStream | AsyncSubagentRunStream,
|
|
250
|
+
status: SubgraphStatus,
|
|
251
|
+
error: str | None,
|
|
252
|
+
) -> None:
|
|
253
|
+
if handle._mux is None or handle._mux._events._closed: # noqa: SLF001
|
|
254
|
+
return
|
|
255
|
+
if status == "failed":
|
|
256
|
+
await handle._mux.afail(RuntimeError(error or "Subagent failed")) # noqa: SLF001
|
|
257
|
+
else:
|
|
258
|
+
await handle._mux.aclose() # noqa: SLF001
|
|
259
|
+
|
|
260
|
+
def _handle_for_event(
|
|
261
|
+
self, event: ProtocolEvent
|
|
262
|
+
) -> SubagentRunStream | AsyncSubagentRunStream | None:
|
|
263
|
+
ns = tuple(event["params"]["namespace"])
|
|
264
|
+
depth = len(self.scope)
|
|
265
|
+
if len(ns) < depth + 1:
|
|
266
|
+
return None
|
|
267
|
+
handle = self._handles.get(ns[: depth + 1])
|
|
268
|
+
if handle is None or handle._mux is None or handle._mux._events._closed: # noqa: SLF001
|
|
269
|
+
return None
|
|
270
|
+
return handle
|
|
271
|
+
|
|
272
|
+
def process(self, event: ProtocolEvent) -> bool:
|
|
273
|
+
# Run tasks bookkeeping first so a `started` handle exists by the
|
|
274
|
+
# time we forward the event into the child mini-mux.
|
|
275
|
+
keep = super().process(event)
|
|
276
|
+
handle = self._handle_for_event(event)
|
|
277
|
+
if handle is not None:
|
|
278
|
+
handle._observe_event(event) # noqa: SLF001
|
|
279
|
+
handle._mux.push(event) # noqa: SLF001
|
|
280
|
+
return keep
|
|
281
|
+
|
|
282
|
+
async def aprocess(self, event: ProtocolEvent) -> bool:
|
|
283
|
+
# Async counterpart: repeat the tasks bookkeeping here and forward into
|
|
284
|
+
# the child mini-mux through its async lane so the subagent's own
|
|
285
|
+
# transformers are driven on the correct (async) lane instead of being
|
|
286
|
+
# double-driven via the sync `process`/`push` path.
|
|
287
|
+
if event["method"] == "tasks":
|
|
288
|
+
ns = tuple(event["params"]["namespace"])
|
|
289
|
+
data = event["params"]["data"]
|
|
290
|
+
if "result" in data:
|
|
291
|
+
for child_ns, status, error in self._pop_terminal_transitions(ns, data):
|
|
292
|
+
await self._aon_terminal(child_ns, status, error)
|
|
293
|
+
else:
|
|
294
|
+
# Mirror the sync bookkeeping so the async lane observes parent
|
|
295
|
+
# identity / pending tool calls before discriminating a
|
|
296
|
+
# subagent boundary in `_handle_task_start` -> `_on_started`.
|
|
297
|
+
self._record_identity(ns, data)
|
|
298
|
+
self._record_pending_tool_calls(data)
|
|
299
|
+
self._handle_task_start(ns, data)
|
|
300
|
+
keep = False
|
|
301
|
+
else:
|
|
302
|
+
keep = True
|
|
303
|
+
handle = self._handle_for_event(event)
|
|
304
|
+
if handle is not None:
|
|
305
|
+
handle._observe_event(event) # noqa: SLF001
|
|
306
|
+
await handle._mux.apush(event) # noqa: SLF001
|
|
307
|
+
return keep
|