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,534 @@
|
|
|
1
|
+
"""Derivations of standard content blocks from Anthropic content."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import Iterator
|
|
5
|
+
from typing import Any, cast
|
|
6
|
+
|
|
7
|
+
from coco_core.messages import AIMessage, AIMessageChunk
|
|
8
|
+
from coco_core.messages import content as types
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _populate_extras(
|
|
12
|
+
standard_block: types.ContentBlock, block: dict[str, Any], known_fields: set[str]
|
|
13
|
+
) -> types.ContentBlock:
|
|
14
|
+
"""Mutate a block, populating extras."""
|
|
15
|
+
if standard_block.get("type") == "non_standard":
|
|
16
|
+
return standard_block
|
|
17
|
+
|
|
18
|
+
for key, value in block.items():
|
|
19
|
+
if key not in known_fields:
|
|
20
|
+
if "extras" not in standard_block:
|
|
21
|
+
# Below type-ignores are because mypy thinks a non-standard block can
|
|
22
|
+
# get here, although we exclude them above.
|
|
23
|
+
standard_block["extras"] = {} # type: ignore[typeddict-unknown-key]
|
|
24
|
+
standard_block["extras"][key] = value # type: ignore[typeddict-item]
|
|
25
|
+
|
|
26
|
+
return standard_block
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _non_standard_block(block: dict[str, Any]) -> types.NonStandardContentBlock:
|
|
30
|
+
return {"type": "non_standard", "value": block}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _convert_to_v1_from_anthropic_input(
|
|
34
|
+
content: list[types.ContentBlock],
|
|
35
|
+
) -> list[types.ContentBlock]:
|
|
36
|
+
"""Convert Anthropic format blocks to v1 format.
|
|
37
|
+
|
|
38
|
+
During the `content_blocks` parsing process, we wrap blocks not recognized as a v1
|
|
39
|
+
block as a `'non_standard'` block with the original block stored in the `value`
|
|
40
|
+
field. This function attempts to unpack those blocks and convert any blocks that
|
|
41
|
+
might be Anthropic format to v1 ContentBlocks.
|
|
42
|
+
|
|
43
|
+
If conversion fails, the block is left as a `'non_standard'` block.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
content: List of content blocks to process.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Updated list with Anthropic blocks converted to v1 format.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def _iter_blocks() -> Iterator[types.ContentBlock]:
|
|
53
|
+
blocks: list[dict[str, Any]] = [
|
|
54
|
+
cast("dict[str, Any]", block)
|
|
55
|
+
if block.get("type") != "non_standard"
|
|
56
|
+
else block["value"] # type: ignore[typeddict-item] # this is only non-standard blocks
|
|
57
|
+
for block in content
|
|
58
|
+
]
|
|
59
|
+
for block in blocks:
|
|
60
|
+
block_type = block.get("type")
|
|
61
|
+
source = block.get("source")
|
|
62
|
+
|
|
63
|
+
if (
|
|
64
|
+
block_type == "document"
|
|
65
|
+
and isinstance(source, dict)
|
|
66
|
+
and "type" in source
|
|
67
|
+
):
|
|
68
|
+
if source["type"] == "base64":
|
|
69
|
+
if "data" not in source or "media_type" not in source:
|
|
70
|
+
yield _non_standard_block(block)
|
|
71
|
+
continue
|
|
72
|
+
file_block: types.FileContentBlock = {
|
|
73
|
+
"type": "file",
|
|
74
|
+
"base64": source["data"],
|
|
75
|
+
"mime_type": source["media_type"],
|
|
76
|
+
}
|
|
77
|
+
_populate_extras(file_block, block, {"type", "source"})
|
|
78
|
+
yield file_block
|
|
79
|
+
|
|
80
|
+
elif source["type"] == "url":
|
|
81
|
+
if "url" not in source:
|
|
82
|
+
yield _non_standard_block(block)
|
|
83
|
+
continue
|
|
84
|
+
file_block = {
|
|
85
|
+
"type": "file",
|
|
86
|
+
"url": source["url"],
|
|
87
|
+
}
|
|
88
|
+
_populate_extras(file_block, block, {"type", "source"})
|
|
89
|
+
yield file_block
|
|
90
|
+
|
|
91
|
+
elif source["type"] == "file":
|
|
92
|
+
if "file_id" not in source:
|
|
93
|
+
yield _non_standard_block(block)
|
|
94
|
+
continue
|
|
95
|
+
file_block = {
|
|
96
|
+
"type": "file",
|
|
97
|
+
"id": source["file_id"],
|
|
98
|
+
}
|
|
99
|
+
_populate_extras(file_block, block, {"type", "source"})
|
|
100
|
+
yield file_block
|
|
101
|
+
|
|
102
|
+
elif source["type"] == "text":
|
|
103
|
+
if "data" not in source:
|
|
104
|
+
yield _non_standard_block(block)
|
|
105
|
+
continue
|
|
106
|
+
plain_text_block: types.PlainTextContentBlock = {
|
|
107
|
+
"type": "text-plain",
|
|
108
|
+
"text": source["data"],
|
|
109
|
+
"mime_type": block.get("media_type", "text/plain"),
|
|
110
|
+
}
|
|
111
|
+
_populate_extras(plain_text_block, block, {"type", "source"})
|
|
112
|
+
yield plain_text_block
|
|
113
|
+
|
|
114
|
+
else:
|
|
115
|
+
yield _non_standard_block(block)
|
|
116
|
+
|
|
117
|
+
elif (
|
|
118
|
+
block_type == "image" and isinstance(source, dict) and "type" in source
|
|
119
|
+
):
|
|
120
|
+
if source["type"] == "base64":
|
|
121
|
+
if "data" not in source or "media_type" not in source:
|
|
122
|
+
yield _non_standard_block(block)
|
|
123
|
+
continue
|
|
124
|
+
image_block: types.ImageContentBlock = {
|
|
125
|
+
"type": "image",
|
|
126
|
+
"base64": source["data"],
|
|
127
|
+
"mime_type": source["media_type"],
|
|
128
|
+
}
|
|
129
|
+
_populate_extras(image_block, block, {"type", "source"})
|
|
130
|
+
yield image_block
|
|
131
|
+
|
|
132
|
+
elif source["type"] == "url":
|
|
133
|
+
if "url" not in source:
|
|
134
|
+
yield _non_standard_block(block)
|
|
135
|
+
continue
|
|
136
|
+
image_block = {
|
|
137
|
+
"type": "image",
|
|
138
|
+
"url": source["url"],
|
|
139
|
+
}
|
|
140
|
+
_populate_extras(image_block, block, {"type", "source"})
|
|
141
|
+
yield image_block
|
|
142
|
+
|
|
143
|
+
elif source["type"] == "file":
|
|
144
|
+
if "file_id" not in source:
|
|
145
|
+
yield _non_standard_block(block)
|
|
146
|
+
continue
|
|
147
|
+
image_block = {
|
|
148
|
+
"type": "image",
|
|
149
|
+
"id": source["file_id"],
|
|
150
|
+
}
|
|
151
|
+
_populate_extras(image_block, block, {"type", "source"})
|
|
152
|
+
yield image_block
|
|
153
|
+
|
|
154
|
+
else:
|
|
155
|
+
yield _non_standard_block(block)
|
|
156
|
+
|
|
157
|
+
elif block_type in types.KNOWN_BLOCK_TYPES:
|
|
158
|
+
yield cast("types.ContentBlock", block)
|
|
159
|
+
|
|
160
|
+
else:
|
|
161
|
+
yield _non_standard_block(block)
|
|
162
|
+
|
|
163
|
+
return list(_iter_blocks())
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _convert_citation_to_v1(citation: dict[str, Any]) -> types.Annotation:
|
|
167
|
+
citation_type = citation.get("type")
|
|
168
|
+
|
|
169
|
+
if citation_type == "web_search_result_location":
|
|
170
|
+
if "cited_text" not in citation or "url" not in citation:
|
|
171
|
+
return {
|
|
172
|
+
"type": "non_standard_annotation",
|
|
173
|
+
"value": citation,
|
|
174
|
+
}
|
|
175
|
+
url_citation: types.Citation = {
|
|
176
|
+
"type": "citation",
|
|
177
|
+
"cited_text": citation["cited_text"],
|
|
178
|
+
"url": citation["url"],
|
|
179
|
+
}
|
|
180
|
+
if title := citation.get("title"):
|
|
181
|
+
url_citation["title"] = title
|
|
182
|
+
known_fields = {"type", "cited_text", "url", "title", "index", "extras"}
|
|
183
|
+
for key, value in citation.items():
|
|
184
|
+
if key not in known_fields:
|
|
185
|
+
if "extras" not in url_citation:
|
|
186
|
+
url_citation["extras"] = {}
|
|
187
|
+
url_citation["extras"][key] = value
|
|
188
|
+
|
|
189
|
+
return url_citation
|
|
190
|
+
|
|
191
|
+
if citation_type in {
|
|
192
|
+
"char_location",
|
|
193
|
+
"content_block_location",
|
|
194
|
+
"page_location",
|
|
195
|
+
"search_result_location",
|
|
196
|
+
}:
|
|
197
|
+
if "cited_text" not in citation:
|
|
198
|
+
return {
|
|
199
|
+
"type": "non_standard_annotation",
|
|
200
|
+
"value": citation,
|
|
201
|
+
}
|
|
202
|
+
document_citation: types.Citation = {
|
|
203
|
+
"type": "citation",
|
|
204
|
+
"cited_text": citation["cited_text"],
|
|
205
|
+
}
|
|
206
|
+
if "document_title" in citation:
|
|
207
|
+
document_citation["title"] = citation["document_title"]
|
|
208
|
+
elif title := citation.get("title"):
|
|
209
|
+
document_citation["title"] = title
|
|
210
|
+
known_fields = {
|
|
211
|
+
"type",
|
|
212
|
+
"cited_text",
|
|
213
|
+
"document_title",
|
|
214
|
+
"title",
|
|
215
|
+
"index",
|
|
216
|
+
"extras",
|
|
217
|
+
}
|
|
218
|
+
for key, value in citation.items():
|
|
219
|
+
if key not in known_fields:
|
|
220
|
+
if "extras" not in document_citation:
|
|
221
|
+
document_citation["extras"] = {}
|
|
222
|
+
document_citation["extras"][key] = value
|
|
223
|
+
|
|
224
|
+
return document_citation
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
"type": "non_standard_annotation",
|
|
228
|
+
"value": citation,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _convert_to_v1_from_anthropic(message: AIMessage) -> list[types.ContentBlock]:
|
|
233
|
+
"""Convert Anthropic message content to v1 format."""
|
|
234
|
+
content: list[str | dict[str, Any]]
|
|
235
|
+
if isinstance(message.content, str):
|
|
236
|
+
content = [{"type": "text", "text": message.content}]
|
|
237
|
+
else:
|
|
238
|
+
content = message.content
|
|
239
|
+
|
|
240
|
+
def _iter_blocks() -> Iterator[types.ContentBlock]:
|
|
241
|
+
for block in content:
|
|
242
|
+
if not isinstance(block, dict):
|
|
243
|
+
continue
|
|
244
|
+
block_type = block.get("type")
|
|
245
|
+
|
|
246
|
+
if block_type == "text":
|
|
247
|
+
if citations := block.get("citations"):
|
|
248
|
+
text_block: types.TextContentBlock = {
|
|
249
|
+
"type": "text",
|
|
250
|
+
"text": block.get("text", ""),
|
|
251
|
+
"annotations": [_convert_citation_to_v1(a) for a in citations],
|
|
252
|
+
}
|
|
253
|
+
else:
|
|
254
|
+
text_block = {"type": "text", "text": block["text"]}
|
|
255
|
+
if "index" in block:
|
|
256
|
+
text_block["index"] = block["index"]
|
|
257
|
+
yield text_block
|
|
258
|
+
|
|
259
|
+
elif block_type == "thinking":
|
|
260
|
+
reasoning_block: types.ReasoningContentBlock = {
|
|
261
|
+
"type": "reasoning",
|
|
262
|
+
"reasoning": block.get("thinking", ""),
|
|
263
|
+
}
|
|
264
|
+
if "index" in block:
|
|
265
|
+
reasoning_block["index"] = block["index"]
|
|
266
|
+
known_fields = {"type", "thinking", "index", "extras"}
|
|
267
|
+
for key in block:
|
|
268
|
+
if key not in known_fields:
|
|
269
|
+
if "extras" not in reasoning_block:
|
|
270
|
+
reasoning_block["extras"] = {}
|
|
271
|
+
reasoning_block["extras"][key] = block[key]
|
|
272
|
+
yield reasoning_block
|
|
273
|
+
|
|
274
|
+
elif block_type == "tool_use":
|
|
275
|
+
if (
|
|
276
|
+
isinstance(message, AIMessageChunk)
|
|
277
|
+
and len(message.tool_call_chunks) == 1
|
|
278
|
+
and message.chunk_position != "last"
|
|
279
|
+
):
|
|
280
|
+
# Isolated chunk
|
|
281
|
+
chunk = message.tool_call_chunks[0]
|
|
282
|
+
|
|
283
|
+
tool_call_chunk = types.ToolCallChunk(
|
|
284
|
+
name=chunk.get("name"),
|
|
285
|
+
id=chunk.get("id"),
|
|
286
|
+
args=chunk.get("args"),
|
|
287
|
+
type="tool_call_chunk",
|
|
288
|
+
)
|
|
289
|
+
if "caller" in block:
|
|
290
|
+
tool_call_chunk["extras"] = {"caller": block["caller"]}
|
|
291
|
+
|
|
292
|
+
index = chunk.get("index")
|
|
293
|
+
if index is not None:
|
|
294
|
+
tool_call_chunk["index"] = index
|
|
295
|
+
yield tool_call_chunk
|
|
296
|
+
else:
|
|
297
|
+
tool_call_block: types.ToolCall | None = None
|
|
298
|
+
# Non-streaming or gathered chunk
|
|
299
|
+
if len(message.tool_calls) == 1:
|
|
300
|
+
tool_call_block = {
|
|
301
|
+
"type": "tool_call",
|
|
302
|
+
"name": message.tool_calls[0]["name"],
|
|
303
|
+
"args": message.tool_calls[0]["args"],
|
|
304
|
+
"id": message.tool_calls[0].get("id"),
|
|
305
|
+
}
|
|
306
|
+
elif call_id := block.get("id"):
|
|
307
|
+
for tc in message.tool_calls:
|
|
308
|
+
if tc.get("id") == call_id:
|
|
309
|
+
tool_call_block = {
|
|
310
|
+
"type": "tool_call",
|
|
311
|
+
"name": tc["name"],
|
|
312
|
+
"args": tc["args"],
|
|
313
|
+
"id": tc.get("id"),
|
|
314
|
+
}
|
|
315
|
+
break
|
|
316
|
+
if not tool_call_block:
|
|
317
|
+
tool_call_block = {
|
|
318
|
+
"type": "tool_call",
|
|
319
|
+
"name": block.get("name", ""),
|
|
320
|
+
"args": block.get("input", {}),
|
|
321
|
+
"id": block.get("id", ""),
|
|
322
|
+
}
|
|
323
|
+
if "index" in block:
|
|
324
|
+
tool_call_block["index"] = block["index"]
|
|
325
|
+
if "caller" in block:
|
|
326
|
+
if "extras" not in tool_call_block:
|
|
327
|
+
tool_call_block["extras"] = {}
|
|
328
|
+
tool_call_block["extras"]["caller"] = block["caller"]
|
|
329
|
+
|
|
330
|
+
yield tool_call_block
|
|
331
|
+
|
|
332
|
+
elif block_type == "input_json_delta" and isinstance(
|
|
333
|
+
message, AIMessageChunk
|
|
334
|
+
):
|
|
335
|
+
if len(message.tool_call_chunks) == 1:
|
|
336
|
+
chunk = message.tool_call_chunks[0]
|
|
337
|
+
tool_call_chunk = types.ToolCallChunk(
|
|
338
|
+
name=chunk.get("name"),
|
|
339
|
+
id=chunk.get("id"),
|
|
340
|
+
args=chunk.get("args"),
|
|
341
|
+
type="tool_call_chunk",
|
|
342
|
+
)
|
|
343
|
+
index = chunk.get("index")
|
|
344
|
+
if index is not None:
|
|
345
|
+
tool_call_chunk["index"] = index
|
|
346
|
+
yield tool_call_chunk
|
|
347
|
+
|
|
348
|
+
else:
|
|
349
|
+
server_tool_call_chunk: types.ServerToolCallChunk = {
|
|
350
|
+
"type": "server_tool_call_chunk",
|
|
351
|
+
"args": block.get("partial_json", ""),
|
|
352
|
+
}
|
|
353
|
+
if "index" in block:
|
|
354
|
+
server_tool_call_chunk["index"] = block["index"]
|
|
355
|
+
yield server_tool_call_chunk
|
|
356
|
+
|
|
357
|
+
elif block_type == "server_tool_use":
|
|
358
|
+
if block.get("name") == "code_execution":
|
|
359
|
+
server_tool_use_name = "code_interpreter"
|
|
360
|
+
else:
|
|
361
|
+
server_tool_use_name = block.get("name", "")
|
|
362
|
+
if (
|
|
363
|
+
isinstance(message, AIMessageChunk)
|
|
364
|
+
and block.get("input") == {}
|
|
365
|
+
and "partial_json" not in block
|
|
366
|
+
and message.chunk_position != "last"
|
|
367
|
+
):
|
|
368
|
+
# First chunk in a stream
|
|
369
|
+
server_tool_call_chunk = {
|
|
370
|
+
"type": "server_tool_call_chunk",
|
|
371
|
+
"name": server_tool_use_name,
|
|
372
|
+
"args": "",
|
|
373
|
+
"id": block.get("id", ""),
|
|
374
|
+
}
|
|
375
|
+
if "index" in block:
|
|
376
|
+
server_tool_call_chunk["index"] = block["index"]
|
|
377
|
+
known_fields = {"type", "name", "input", "id", "index"}
|
|
378
|
+
_populate_extras(server_tool_call_chunk, block, known_fields)
|
|
379
|
+
yield server_tool_call_chunk
|
|
380
|
+
else:
|
|
381
|
+
server_tool_call: types.ServerToolCall = {
|
|
382
|
+
"type": "server_tool_call",
|
|
383
|
+
"name": server_tool_use_name,
|
|
384
|
+
"args": block.get("input", {}),
|
|
385
|
+
"id": block.get("id", ""),
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if block.get("input") == {} and "partial_json" in block:
|
|
389
|
+
try:
|
|
390
|
+
input_ = json.loads(block["partial_json"])
|
|
391
|
+
if isinstance(input_, dict):
|
|
392
|
+
server_tool_call["args"] = input_
|
|
393
|
+
except json.JSONDecodeError:
|
|
394
|
+
pass
|
|
395
|
+
|
|
396
|
+
if "index" in block:
|
|
397
|
+
server_tool_call["index"] = block["index"]
|
|
398
|
+
known_fields = {
|
|
399
|
+
"type",
|
|
400
|
+
"name",
|
|
401
|
+
"input",
|
|
402
|
+
"partial_json",
|
|
403
|
+
"id",
|
|
404
|
+
"index",
|
|
405
|
+
}
|
|
406
|
+
_populate_extras(server_tool_call, block, known_fields)
|
|
407
|
+
|
|
408
|
+
yield server_tool_call
|
|
409
|
+
|
|
410
|
+
elif block_type == "mcp_tool_use":
|
|
411
|
+
if (
|
|
412
|
+
isinstance(message, AIMessageChunk)
|
|
413
|
+
and block.get("input") == {}
|
|
414
|
+
and "partial_json" not in block
|
|
415
|
+
and message.chunk_position != "last"
|
|
416
|
+
):
|
|
417
|
+
# First chunk in a stream
|
|
418
|
+
server_tool_call_chunk = {
|
|
419
|
+
"type": "server_tool_call_chunk",
|
|
420
|
+
"name": "remote_mcp",
|
|
421
|
+
"args": "",
|
|
422
|
+
"id": block.get("id", ""),
|
|
423
|
+
}
|
|
424
|
+
if "name" in block:
|
|
425
|
+
server_tool_call_chunk["extras"] = {"tool_name": block["name"]}
|
|
426
|
+
known_fields = {"type", "name", "input", "id", "index"}
|
|
427
|
+
_populate_extras(server_tool_call_chunk, block, known_fields)
|
|
428
|
+
if "index" in block:
|
|
429
|
+
server_tool_call_chunk["index"] = block["index"]
|
|
430
|
+
yield server_tool_call_chunk
|
|
431
|
+
else:
|
|
432
|
+
server_tool_call = {
|
|
433
|
+
"type": "server_tool_call",
|
|
434
|
+
"name": "remote_mcp",
|
|
435
|
+
"args": block.get("input", {}),
|
|
436
|
+
"id": block.get("id", ""),
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if block.get("input") == {} and "partial_json" in block:
|
|
440
|
+
try:
|
|
441
|
+
input_ = json.loads(block["partial_json"])
|
|
442
|
+
if isinstance(input_, dict):
|
|
443
|
+
server_tool_call["args"] = input_
|
|
444
|
+
except json.JSONDecodeError:
|
|
445
|
+
pass
|
|
446
|
+
|
|
447
|
+
if "name" in block:
|
|
448
|
+
server_tool_call["extras"] = {"tool_name": block["name"]}
|
|
449
|
+
known_fields = {
|
|
450
|
+
"type",
|
|
451
|
+
"name",
|
|
452
|
+
"input",
|
|
453
|
+
"partial_json",
|
|
454
|
+
"id",
|
|
455
|
+
"index",
|
|
456
|
+
}
|
|
457
|
+
_populate_extras(server_tool_call, block, known_fields)
|
|
458
|
+
if "index" in block:
|
|
459
|
+
server_tool_call["index"] = block["index"]
|
|
460
|
+
|
|
461
|
+
yield server_tool_call
|
|
462
|
+
|
|
463
|
+
elif block_type and block_type.endswith("_tool_result"):
|
|
464
|
+
server_tool_result: types.ServerToolResult = {
|
|
465
|
+
"type": "server_tool_result",
|
|
466
|
+
"tool_call_id": block.get("tool_use_id", ""),
|
|
467
|
+
"status": "success",
|
|
468
|
+
"extras": {"block_type": block_type},
|
|
469
|
+
}
|
|
470
|
+
if output := block.get("content", []):
|
|
471
|
+
server_tool_result["output"] = output
|
|
472
|
+
if isinstance(output, dict) and output.get(
|
|
473
|
+
"error_code" # web_search, code_interpreter
|
|
474
|
+
):
|
|
475
|
+
server_tool_result["status"] = "error"
|
|
476
|
+
if block.get("is_error"): # mcp_tool_result
|
|
477
|
+
server_tool_result["status"] = "error"
|
|
478
|
+
if "index" in block:
|
|
479
|
+
server_tool_result["index"] = block["index"]
|
|
480
|
+
|
|
481
|
+
known_fields = {"type", "tool_use_id", "content", "is_error", "index"}
|
|
482
|
+
_populate_extras(server_tool_result, block, known_fields)
|
|
483
|
+
|
|
484
|
+
yield server_tool_result
|
|
485
|
+
|
|
486
|
+
else:
|
|
487
|
+
new_block: types.NonStandardContentBlock = {
|
|
488
|
+
"type": "non_standard",
|
|
489
|
+
"value": block,
|
|
490
|
+
}
|
|
491
|
+
if "index" in new_block["value"]:
|
|
492
|
+
new_block["index"] = new_block["value"].pop("index")
|
|
493
|
+
yield new_block
|
|
494
|
+
|
|
495
|
+
return list(_iter_blocks())
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def translate_content(message: AIMessage) -> list[types.ContentBlock]:
|
|
499
|
+
"""Derive standard content blocks from a message with Anthropic content.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
message: The message to translate.
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
The derived content blocks.
|
|
506
|
+
"""
|
|
507
|
+
return _convert_to_v1_from_anthropic(message)
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
|
|
511
|
+
"""Derive standard content blocks from a message chunk with Anthropic content.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
message: The message chunk to translate.
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
The derived content blocks.
|
|
518
|
+
"""
|
|
519
|
+
return _convert_to_v1_from_anthropic(message)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _register_anthropic_translator() -> None:
|
|
523
|
+
"""Register the Anthropic translator with the central registry.
|
|
524
|
+
|
|
525
|
+
Run automatically when the module is imported.
|
|
526
|
+
"""
|
|
527
|
+
from coco_core.messages.block_translators import ( # noqa: PLC0415
|
|
528
|
+
register_translator,
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
register_translator("anthropic", translate_content, translate_content_chunk)
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
_register_anthropic_translator()
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Derivations of standard content blocks from Bedrock content."""
|
|
2
|
+
|
|
3
|
+
from coco_core.messages import AIMessage, AIMessageChunk
|
|
4
|
+
from coco_core.messages import content as types
|
|
5
|
+
from coco_core.messages.block_translators.anthropic import (
|
|
6
|
+
_convert_to_v1_from_anthropic,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _convert_to_v1_from_bedrock(message: AIMessage) -> list[types.ContentBlock]:
|
|
11
|
+
"""Convert bedrock message content to v1 format."""
|
|
12
|
+
out = _convert_to_v1_from_anthropic(message)
|
|
13
|
+
|
|
14
|
+
content_tool_call_ids = {
|
|
15
|
+
block.get("id")
|
|
16
|
+
for block in out
|
|
17
|
+
if isinstance(block, dict) and block.get("type") == "tool_call"
|
|
18
|
+
}
|
|
19
|
+
for tool_call in message.tool_calls:
|
|
20
|
+
if (id_ := tool_call.get("id")) and id_ not in content_tool_call_ids:
|
|
21
|
+
tool_call_block: types.ToolCall = {
|
|
22
|
+
"type": "tool_call",
|
|
23
|
+
"id": id_,
|
|
24
|
+
"name": tool_call["name"],
|
|
25
|
+
"args": tool_call["args"],
|
|
26
|
+
}
|
|
27
|
+
if "index" in tool_call:
|
|
28
|
+
tool_call_block["index"] = tool_call["index"] # type: ignore[typeddict-item]
|
|
29
|
+
if "extras" in tool_call:
|
|
30
|
+
tool_call_block["extras"] = tool_call["extras"] # type: ignore[typeddict-item]
|
|
31
|
+
out.append(tool_call_block)
|
|
32
|
+
return out
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _convert_to_v1_from_bedrock_chunk(
|
|
36
|
+
message: AIMessageChunk,
|
|
37
|
+
) -> list[types.ContentBlock]:
|
|
38
|
+
"""Convert bedrock message chunk content to v1 format."""
|
|
39
|
+
if (
|
|
40
|
+
message.content == ""
|
|
41
|
+
and not message.additional_kwargs
|
|
42
|
+
and not message.tool_calls
|
|
43
|
+
):
|
|
44
|
+
# Bedrock outputs multiple chunks containing response metadata
|
|
45
|
+
return []
|
|
46
|
+
|
|
47
|
+
out = _convert_to_v1_from_anthropic(message)
|
|
48
|
+
|
|
49
|
+
if (
|
|
50
|
+
message.tool_call_chunks
|
|
51
|
+
and not message.content
|
|
52
|
+
and message.chunk_position != "last" # keep tool_calls if aggregated
|
|
53
|
+
):
|
|
54
|
+
for tool_call_chunk in message.tool_call_chunks:
|
|
55
|
+
tc: types.ToolCallChunk = {
|
|
56
|
+
"type": "tool_call_chunk",
|
|
57
|
+
"id": tool_call_chunk.get("id"),
|
|
58
|
+
"name": tool_call_chunk.get("name"),
|
|
59
|
+
"args": tool_call_chunk.get("args"),
|
|
60
|
+
}
|
|
61
|
+
if (idx := tool_call_chunk.get("index")) is not None:
|
|
62
|
+
tc["index"] = idx
|
|
63
|
+
out.append(tc)
|
|
64
|
+
return out
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def translate_content(message: AIMessage) -> list[types.ContentBlock]:
|
|
68
|
+
"""Derive standard content blocks from a message with Bedrock content.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
message: The message to translate.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
The derived content blocks.
|
|
75
|
+
"""
|
|
76
|
+
if "claude" not in message.response_metadata.get("model_name", "").lower():
|
|
77
|
+
raise NotImplementedError # fall back to best-effort parsing
|
|
78
|
+
return _convert_to_v1_from_bedrock(message)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
|
|
82
|
+
"""Derive standard content blocks from a message chunk with Bedrock content.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
message: The message chunk to translate.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
The derived content blocks.
|
|
89
|
+
"""
|
|
90
|
+
# TODO: add model_name to all Bedrock chunks and update core merging logic
|
|
91
|
+
# to not append during aggregation. Then raise NotImplementedError here if
|
|
92
|
+
# not an Anthropic model to fall back to best-effort parsing.
|
|
93
|
+
return _convert_to_v1_from_bedrock_chunk(message)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _register_bedrock_translator() -> None:
|
|
97
|
+
"""Register the bedrock translator with the central registry.
|
|
98
|
+
|
|
99
|
+
Run automatically when the module is imported.
|
|
100
|
+
"""
|
|
101
|
+
from coco_core.messages.block_translators import ( # noqa: PLC0415
|
|
102
|
+
register_translator,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
register_translator("bedrock", translate_content, translate_content_chunk)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
_register_bedrock_translator()
|