diff --git a/src/kimi_cli/acp/convert.py b/src/kimi_cli/acp/convert.py index 48297be3f..84f8c62ad 100644 --- a/src/kimi_cli/acp/convert.py +++ b/src/kimi_cli/acp/convert.py @@ -28,6 +28,23 @@ def acp_blocks_to_content_parts(prompt: list[ACPContentBlock]) -> list[ContentPa ) ) ) + case acp.schema.EmbeddedResourceContentBlock(): + resource = block.resource + if isinstance(resource, acp.schema.TextResourceContents): + uri = resource.uri + text = resource.text + content.append(TextPart(text=f"\n{text}\n")) + else: + logger.warning( + "Unsupported embedded resource type: {type}", + type=type(resource).__name__, + ) + case acp.schema.ResourceContentBlock(): + # ResourceContentBlock is a link reference without inline content; + # include the URI so the model is at least aware of the reference. + content.append( + TextPart(text=f"") + ) case _: logger.warning("Unsupported prompt content block: {block}", block=block) return content diff --git a/src/kimi_cli/acp/server.py b/src/kimi_cli/acp/server.py index 5e77a0eed..a2cb2e711 100644 --- a/src/kimi_cli/acp/server.py +++ b/src/kimi_cli/acp/server.py @@ -63,7 +63,7 @@ async def initialize( agent_capabilities=acp.schema.AgentCapabilities( load_session=True, prompt_capabilities=acp.schema.PromptCapabilities( - embedded_context=False, image=True, audio=False + embedded_context=True, image=True, audio=False ), mcp_capabilities=acp.schema.McpCapabilities(http=True, sse=False), session_capabilities=acp.schema.SessionCapabilities( diff --git a/tests/ui_and_conv/test_acp_convert.py b/tests/ui_and_conv/test_acp_convert.py index 57f8accc9..f933482d8 100644 --- a/tests/ui_and_conv/test_acp_convert.py +++ b/tests/ui_and_conv/test_acp_convert.py @@ -1,7 +1,7 @@ import acp -from kimi_cli.acp.convert import tool_result_to_acp_content -from kimi_cli.wire.types import DiffDisplayBlock, ToolReturnValue +from kimi_cli.acp.convert import acp_blocks_to_content_parts, tool_result_to_acp_content +from kimi_cli.wire.types import DiffDisplayBlock, TextPart, ToolReturnValue def test_tool_result_to_acp_content_handles_diff_display(): @@ -21,3 +21,62 @@ def test_tool_result_to_acp_content_handles_diff_display(): assert content.path == "foo.txt" assert content.old_text == "before" assert content.new_text == "after" + + +def test_acp_blocks_to_content_parts_handles_embedded_text_resource(): + block = acp.schema.EmbeddedResourceContentBlock( + type="resource", + resource=acp.schema.TextResourceContents( + uri="file:///path/to/foo.py", + text="print('hello')", + ), + ) + parts = acp_blocks_to_content_parts([block]) + assert len(parts) == 1 + assert isinstance(parts[0], TextPart) + assert "file:///path/to/foo.py" in parts[0].text + assert "print('hello')" in parts[0].text + + +def test_acp_blocks_to_content_parts_handles_resource_link(): + block = acp.schema.ResourceContentBlock( + type="resource_link", + uri="file:///path/to/bar.py", + name="bar.py", + ) + parts = acp_blocks_to_content_parts([block]) + assert len(parts) == 1 + assert isinstance(parts[0], TextPart) + assert "file:///path/to/bar.py" in parts[0].text + assert "bar.py" in parts[0].text + + +def test_acp_blocks_to_content_parts_skips_blob_resource(): + block = acp.schema.EmbeddedResourceContentBlock( + type="resource", + resource=acp.schema.BlobResourceContents( + uri="file:///path/to/image.png", + blob="iVBORw0KGgo=", + ), + ) + parts = acp_blocks_to_content_parts([block]) + assert len(parts) == 0 + + +def test_acp_blocks_to_content_parts_mixed_blocks(): + blocks = [ + acp.schema.TextContentBlock(type="text", text="Check this file:"), + acp.schema.EmbeddedResourceContentBlock( + type="resource", + resource=acp.schema.TextResourceContents( + uri="file:///src/main.py", + text="def main(): pass", + ), + ), + ] + parts = acp_blocks_to_content_parts(blocks) + assert len(parts) == 2 + assert isinstance(parts[0], TextPart) + assert parts[0].text == "Check this file:" + assert isinstance(parts[1], TextPart) + assert "def main(): pass" in parts[1].text