diff --git a/CHANGELOG.md b/CHANGELOG.md index a65be2967..c43f63138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Only write entries that are worth mentioning to users. ## Unreleased +- Tool: Add `ReadMediaFile` tool for reading image/video files; `ReadFile` now focuses on text files only + ## 0.82 (2026-01-21) - Tool: Allow `WriteFile` and `StrReplaceFile` tools to edit/write files outside the working directory when using absolute paths diff --git a/docs/en/customization/agents.md b/docs/en/customization/agents.md index 268aea771..73875a6ae 100644 --- a/docs/en/customization/agents.md +++ b/docs/en/customization/agents.md @@ -14,7 +14,7 @@ kimi --agent okabe The default agent, suitable for general use. Enabled tools: -`Task`, `SetTodoList`, `Shell`, `ReadFile`, `Glob`, `Grep`, `WriteFile`, `StrReplaceFile`, `SearchWeb`, `FetchURL` +`Task`, `SetTodoList`, `Shell`, `ReadFile`, `ReadMediaFile`, `Glob`, `Grep`, `WriteFile`, `StrReplaceFile`, `SearchWeb`, `FetchURL` ### `okabe` @@ -191,13 +191,22 @@ The following are all built-in tools in Kimi CLI. ### `ReadFile` - **Path**: `kimi_cli.tools.file:ReadFile` -- **Description**: Read file content. Supports text, image, and video files. For text files, max 1000 lines per read, max 2000 characters per line; for image/video files, max 80MB. Files outside working directory require absolute paths. +- **Description**: Read text file content. Max 1000 lines per read, max 2000 characters per line. Files outside working directory require absolute paths. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `path` | string | File path | +| `line_offset` | int | Starting line number, default 1 | +| `n_lines` | int | Number of lines to read, default/max 1000 | + +### `ReadMediaFile` + +- **Path**: `kimi_cli.tools.file:ReadMediaFile` +- **Description**: Read image or video files. Max file size 100MB. Only available when the model supports image/video input. Files outside working directory require absolute paths. | Parameter | Type | Description | |-----------|------|-------------| | `path` | string | File path | -| `line_offset` | int | Starting line number, default 1 (text files only) | -| `n_lines` | int | Number of lines to read, default/max 1000 (text files only) | ### `Glob` diff --git a/docs/en/release-notes/changelog.md b/docs/en/release-notes/changelog.md index 8741ab5f5..f16c73a8e 100644 --- a/docs/en/release-notes/changelog.md +++ b/docs/en/release-notes/changelog.md @@ -4,6 +4,8 @@ This page documents the changes in each Kimi CLI release. ## Unreleased +- Tool: Add `ReadMediaFile` tool for reading image/video files; `ReadFile` now focuses on text files only + ## 0.82 (2026-01-21) - Tool: Allow `WriteFile` and `StrReplaceFile` tools to edit/write files outside the working directory when using absolute paths diff --git a/docs/zh/customization/agents.md b/docs/zh/customization/agents.md index 9ce1a1af5..7d3497531 100644 --- a/docs/zh/customization/agents.md +++ b/docs/zh/customization/agents.md @@ -14,7 +14,7 @@ kimi --agent okabe 默认 Agent,适合通常情况使用。启用的工具: -`Task`、`SetTodoList`、`Shell`、`ReadFile`、`Glob`、`Grep`、`WriteFile`、`StrReplaceFile`、`SearchWeb`、`FetchURL` +`Task`、`SetTodoList`、`Shell`、`ReadFile`、`ReadMediaFile`、`Glob`、`Grep`、`WriteFile`、`StrReplaceFile`、`SearchWeb`、`FetchURL` ### `okabe` @@ -191,13 +191,22 @@ agent: ### `ReadFile` - **路径**:`kimi_cli.tools.file:ReadFile` -- **描述**:读取文件内容。支持文本、图片和视频文件。文本文件单次最多读取 1000 行,每行最多 2000 字符;图片/视频文件最大 80MB。工作目录外的文件需使用绝对路径。 +- **描述**:读取文本文件内容。单次最多读取 1000 行,每行最多 2000 字符。工作目录外的文件需使用绝对路径。 + +| 参数 | 类型 | 说明 | +|------|------|------| +| `path` | string | 文件路径 | +| `line_offset` | int | 起始行号,默认 1 | +| `n_lines` | int | 读取行数,默认/最大 1000 | + +### `ReadMediaFile` + +- **路径**:`kimi_cli.tools.file:ReadMediaFile` +- **描述**:读取图片或视频文件。文件最大 100MB。仅当模型支持图片/视频输入时可用。工作目录外的文件需使用绝对路径。 | 参数 | 类型 | 说明 | |------|------|------| | `path` | string | 文件路径 | -| `line_offset` | int | 起始行号,默认 1(仅适用于文本文件) | -| `n_lines` | int | 读取行数,默认/最大 1000(仅适用于文本文件) | ### `Glob` diff --git a/docs/zh/release-notes/changelog.md b/docs/zh/release-notes/changelog.md index ed2287e91..43b7169b2 100644 --- a/docs/zh/release-notes/changelog.md +++ b/docs/zh/release-notes/changelog.md @@ -4,6 +4,8 @@ ## 未发布 +- Tool:添加 `ReadMediaFile` 工具用于读取图片/视频文件;`ReadFile` 现在仅用于读取文本文件 + ## 0.82 (2026-01-21) - Tool:`WriteFile` 和 `StrReplaceFile` 工具支持使用绝对路径编辑/写入工作目录外的文件 diff --git a/klips/klip-12-wire-initialize-external-tools.md b/klips/klip-12-wire-initialize-external-tools.md index 7d70169ed..a2ed678ac 100644 --- a/klips/klip-12-wire-initialize-external-tools.md +++ b/klips/klip-12-wire-initialize-external-tools.md @@ -1,7 +1,7 @@ --- Author: "@stdrc" Updated: 2026-01-14 -Status: Draft +Status: Implemented --- # KLIP-12: Wire 初始化协商与外部工具调用 diff --git a/src/kimi_cli/acp/AGENTS.md b/src/kimi_cli/acp/AGENTS.md index e856d9e80..24efeacaa 100644 --- a/src/kimi_cli/acp/AGENTS.md +++ b/src/kimi_cli/acp/AGENTS.md @@ -82,6 +82,7 @@ - When the client advertises `fs.readTextFile` / `fs.writeTextFile`, `ACPKaos` routes reads and writes through ACP `fs/*` methods. - `ReadFile` uses `KaosPath.read_lines`, which `ACPKaos` implements via ACP reads. +- `ReadMediaFile` uses `KaosPath.read_bytes` to load image/video payloads through ACP reads. - `WriteFile` uses `KaosPath.read_text/write_text/append_text` and still generates diffs and approvals in the tool layer. diff --git a/src/kimi_cli/agents/default/agent.yaml b/src/kimi_cli/agents/default/agent.yaml index b527388e4..3ca98d744 100644 --- a/src/kimi_cli/agents/default/agent.yaml +++ b/src/kimi_cli/agents/default/agent.yaml @@ -12,6 +12,7 @@ agent: - "kimi_cli.tools.todo:SetTodoList" - "kimi_cli.tools.shell:Shell" - "kimi_cli.tools.file:ReadFile" + - "kimi_cli.tools.file:ReadMediaFile" - "kimi_cli.tools.file:Glob" - "kimi_cli.tools.file:Grep" - "kimi_cli.tools.file:WriteFile" diff --git a/src/kimi_cli/agents/okabe/agent.yaml b/src/kimi_cli/agents/okabe/agent.yaml index 99b390ef1..55b3f9903 100644 --- a/src/kimi_cli/agents/okabe/agent.yaml +++ b/src/kimi_cli/agents/okabe/agent.yaml @@ -9,6 +9,7 @@ agent: - "kimi_cli.tools.todo:SetTodoList" - "kimi_cli.tools.shell:Shell" - "kimi_cli.tools.file:ReadFile" + - "kimi_cli.tools.file:ReadMediaFile" - "kimi_cli.tools.file:Glob" - "kimi_cli.tools.file:Grep" - "kimi_cli.tools.file:WriteFile" diff --git a/src/kimi_cli/tools/__init__.py b/src/kimi_cli/tools/__init__.py index 72582a187..17dc6c521 100644 --- a/src/kimi_cli/tools/__init__.py +++ b/src/kimi_cli/tools/__init__.py @@ -51,6 +51,10 @@ def extract_key_argument(json_content: str | streamingjson.Lexer, tool_name: str if not isinstance(curr_args, dict) or not curr_args.get("path"): return None key_argument = _normalize_path(str(curr_args["path"])) + case "ReadMediaFile": + if not isinstance(curr_args, dict) or not curr_args.get("path"): + return None + key_argument = _normalize_path(str(curr_args["path"])) case "Glob": if not isinstance(curr_args, dict) or not curr_args.get("pattern"): return None diff --git a/src/kimi_cli/tools/file/__init__.py b/src/kimi_cli/tools/file/__init__.py index dde05ef8c..8cfccddcf 100644 --- a/src/kimi_cli/tools/file/__init__.py +++ b/src/kimi_cli/tools/file/__init__.py @@ -16,11 +16,13 @@ class FileActions(str, Enum): from .glob import Glob # noqa: E402 from .grep_local import Grep # noqa: E402 from .read import ReadFile # noqa: E402 +from .read_media import ReadMediaFile # noqa: E402 from .replace import StrReplaceFile # noqa: E402 from .write import WriteFile # noqa: E402 __all__ = ( "ReadFile", + "ReadMediaFile", "Glob", "Grep", "WriteFile", diff --git a/src/kimi_cli/tools/file/read.md b/src/kimi_cli/tools/file/read.md index 667df47b7..57e08a24c 100644 --- a/src/kimi_cli/tools/file/read.md +++ b/src/kimi_cli/tools/file/read.md @@ -1,32 +1,14 @@ -Read content from a file. +Read text content from a file. **Tips:** - Make sure you follow the description of each tool parameter. - A `` tag will be given before the read file content. - The system will notify you when there is anything wrong when reading the file. - This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible. -- This tool can only read text, image and video files. To list directories, you must use the Glob tool or `ls` command via the Shell tool. To read other file types, use appropriate commands via the Shell tool. +- This tool can only read text files. To read images or videos, use other appropriate tools. To list directories, use the Glob tool or `ls` command via the Shell tool. To read other file types, use appropriate commands via the Shell tool. - If the file doesn't exist or path is invalid, an error will be returned. - If you want to search for a certain content/pattern, prefer Grep tool over ReadFile. -- For text files: - - Content will be returned with a line number before each line like `cat -n` format. - - Use `line_offset` and `n_lines` parameters when you only need to read a part of the file. - - The maximum number of lines that can be read at once is ${MAX_LINES}. - - Any lines longer than ${MAX_LINE_LENGTH} characters will be truncated, ending with "...". -{% if "image_in" in capabilities and "video_in" in capabilities %} -- For image and video files: - - Content will be returned in a form that you can view and understand. Feel confident to read image/video files with this tool. - - The maximum size that can be read is ${MAX_MEDIA_BYTES} bytes. An error will be returned if the file is larger than this limit. -{% elif "image_in" in capabilities %} -- For image files: - - Content will be returned in a form that you can view and understand. Feel confident to read image files with this tool. - - The maximum size that can be read is ${MAX_MEDIA_BYTES} bytes. An error will be returned if the file is larger than this limit. -- Other media files (e.g., video, PDFs) are not supported by this tool. Use other proper tools to process them. -{% elif "video_in" in capabilities %} -- For video files: - - Content will be returned in a form that you can view and understand. Feel confident to read video files with this tool. - - The maximum size that can be read is ${MAX_MEDIA_BYTES} bytes. An error will be returned if the file is larger than this limit. -- Other media files (e.g., image, PDFs) are not supported by this tool. Use other proper tools to process them. -{% else %} -- Media files (e.g., image, video, PDFs) are not supported by this tool. Use other proper tools to process them. -{% endif %} +- Content will be returned with a line number before each line like `cat -n` format. +- Use `line_offset` and `n_lines` parameters when you only need to read a part of the file. +- The maximum number of lines that can be read at once is ${MAX_LINES}. +- Any lines longer than ${MAX_LINE_LENGTH} characters will be truncated, ending with "...". diff --git a/src/kimi_cli/tools/file/read.py b/src/kimi_cli/tools/file/read.py index 9128b4952..ed58e97ef 100644 --- a/src/kimi_cli/tools/file/read.py +++ b/src/kimi_cli/tools/file/read.py @@ -1,27 +1,18 @@ -import base64 from pathlib import Path from typing import override from kaos.path import KaosPath -from kosong.chat_provider.kimi import Kimi from kosong.tooling import CallableTool2, ToolError, ToolOk, ToolReturnValue from pydantic import BaseModel, Field from kimi_cli.soul.agent import Runtime -from kimi_cli.tools.file.utils import MEDIA_SNIFF_BYTES, FileType, detect_file_type +from kimi_cli.tools.file.utils import MEDIA_SNIFF_BYTES, detect_file_type from kimi_cli.tools.utils import load_desc_jinja, truncate_line from kimi_cli.utils.path import is_within_directory -from kimi_cli.wire.types import ImageURLPart, VideoURLPart MAX_LINES = 1000 MAX_LINE_LENGTH = 2000 MAX_BYTES = 100 << 10 # 100KB -MAX_MEDIA_BYTES = 80 << 20 # 80MB - - -def _to_data_url(mime_type: str, data: bytes) -> str: - encoded = base64.b64encode(data).decode("ascii") - return f"data:{mime_type};base64,{encoded}" class Params(BaseModel): @@ -56,15 +47,12 @@ class ReadFile(CallableTool2[Params]): params: type[Params] = Params def __init__(self, runtime: Runtime) -> None: - capabilities = runtime.llm.capabilities if runtime.llm else set[str]() description = load_desc_jinja( Path(__file__).parent / "read.md", { "MAX_LINES": MAX_LINES, "MAX_LINE_LENGTH": MAX_LINE_LENGTH, "MAX_BYTES": MAX_BYTES, - "MAX_MEDIA_BYTES": MAX_MEDIA_BYTES, - "capabilities": capabilities, }, ) super().__init__(description=description) @@ -87,47 +75,6 @@ async def _validate_path(self, path: KaosPath) -> ToolError | None: ) return None - async def _read_media(self, path: KaosPath, file_type: FileType) -> ToolReturnValue: - assert file_type.kind in ("image", "video") - - stat = await path.stat() - size = stat.st_size - if size == 0: - return ToolError( - message=f"`{path}` is empty.", - brief="Empty file", - ) - if size > MAX_MEDIA_BYTES: - return ToolError( - message=( - f"`{path}` is {size} bytes, which exceeds the max " - f"{MAX_MEDIA_BYTES} bytes for media files." - ), - brief="File too large", - ) - - match file_type.kind: - case "image": - data = await path.read_bytes() - data_url = _to_data_url(file_type.mime_type, data) - part = ImageURLPart(image_url=ImageURLPart.ImageURL(url=data_url)) - case "video": - data = await path.read_bytes() - if (llm := self._runtime.llm) and isinstance(llm.chat_provider, Kimi): - part = await llm.chat_provider.files.upload_video( - data=data, - mime_type=file_type.mime_type, - ) - else: - data_url = _to_data_url(file_type.mime_type, data) - part = VideoURLPart(video_url=VideoURLPart.VideoURL(url=data_url)) - return ToolOk( - output=part, - message=( - f"Loaded {file_type.kind} file `{path}` ({file_type.mime_type}, {size} bytes)." - ), - ) - @override async def __call__(self, params: Params) -> ToolReturnValue: # TODO: checks: @@ -159,7 +106,13 @@ async def __call__(self, params: Params) -> ToolReturnValue: header = await p.read_bytes(MEDIA_SNIFF_BYTES) file_type = detect_file_type(str(p), header=header) if file_type.kind in ("image", "video"): - return await self._read_media(p, file_type) + return ToolError( + message=( + f"`{params.path}` is a {file_type.kind} file. " + "Use other appropriate tools to read image or video files." + ), + brief="Unsupported file type", + ) if file_type.kind == "unknown": return ToolError( diff --git a/src/kimi_cli/tools/file/read_media.md b/src/kimi_cli/tools/file/read_media.md new file mode 100644 index 000000000..7fdc51f17 --- /dev/null +++ b/src/kimi_cli/tools/file/read_media.md @@ -0,0 +1,24 @@ +Read media content from a file. + +**Tips:** +- Make sure you follow the description of each tool parameter. +- A `` tag will be given before the read file content. +- The system will notify you when there is anything wrong when reading the file. +- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible. +- This tool can only read image or video files. To read other types of files, use the ReadFile tool. To list directories, use the Glob tool or `ls` command via the Shell tool. +- If the file doesn't exist or path is invalid, an error will be returned. +- The maximum size that can be read is ${MAX_MEDIA_MEGABYTES}MB. An error will be returned if the file is larger than this limit. +- The media content will be returned in a form that you can directly view and understand. + +**Capabilities** +{% if "image_in" in capabilities and "video_in" in capabilities %} +- This tool supports image and video files for the current model. +{% elif "image_in" in capabilities %} +- This tool supports image files for the current model. +- Video files are not supported by the current model. +{% elif "video_in" in capabilities %} +- This tool supports video files for the current model. +- Image files are not supported by the current model. +{% else %} +- The current model does not support image or video input. +{% endif %} diff --git a/src/kimi_cli/tools/file/read_media.py b/src/kimi_cli/tools/file/read_media.py new file mode 100644 index 000000000..9d00d9994 --- /dev/null +++ b/src/kimi_cli/tools/file/read_media.py @@ -0,0 +1,206 @@ +import base64 +from io import BytesIO +from pathlib import Path +from typing import override + +from kaos.path import KaosPath +from kosong.chat_provider.kimi import Kimi +from kosong.tooling import CallableTool2, ToolError, ToolOk, ToolReturnValue +from pydantic import BaseModel, Field + +from kimi_cli.soul.agent import Runtime +from kimi_cli.tools import SkipThisTool +from kimi_cli.tools.file.utils import MEDIA_SNIFF_BYTES, FileType, detect_file_type +from kimi_cli.tools.utils import load_desc_jinja +from kimi_cli.utils.path import is_within_directory +from kimi_cli.wire.types import ImageURLPart, VideoURLPart + +MAX_MEDIA_MEGABYTES = 100 + + +def _to_data_url(mime_type: str, data: bytes) -> str: + encoded = base64.b64encode(data).decode("ascii") + return f"data:{mime_type};base64,{encoded}" + + +def _extract_image_size(data: bytes) -> tuple[int, int] | None: + try: + from PIL import Image + except Exception: + return None + try: + with Image.open(BytesIO(data)) as image: + image.load() + return image.size + except Exception: + return None + + +class Params(BaseModel): + path: str = Field( + description=( + "The path to the file to read. Absolute paths are required when reading files " + "outside the working directory." + ) + ) + + +class ReadMediaFile(CallableTool2[Params]): + name: str = "ReadMediaFile" + params: type[Params] = Params + + def __init__(self, runtime: Runtime) -> None: + capabilities = runtime.llm.capabilities if runtime.llm else set[str]() + if "image_in" not in capabilities and "video_in" not in capabilities: + raise SkipThisTool() + + description = load_desc_jinja( + Path(__file__).parent / "read_media.md", + { + "MAX_MEDIA_MEGABYTES": MAX_MEDIA_MEGABYTES, + "capabilities": capabilities, + }, + ) + super().__init__(description=description) + + self._runtime = runtime + self._work_dir = runtime.builtin_args.KIMI_WORK_DIR + self._capabilities = capabilities + + async def _validate_path(self, path: KaosPath) -> ToolError | None: + """Validate that the path is safe to read.""" + resolved_path = path.canonical() + + if not is_within_directory(resolved_path, self._work_dir) and not path.is_absolute(): + # Outside files can only be read with absolute paths + return ToolError( + message=( + f"`{path}` is not an absolute path. " + "You must provide an absolute path to read a file " + "outside the working directory." + ), + brief="Invalid path", + ) + return None + + async def _read_media(self, path: KaosPath, file_type: FileType) -> ToolReturnValue: + assert file_type.kind in ("image", "video") + + stat = await path.stat() + size = stat.st_size + if size == 0: + return ToolError( + message=f"`{path}` is empty.", + brief="Empty file", + ) + if size > (MAX_MEDIA_MEGABYTES << 20): + return ToolError( + message=( + f"`{path}` is {size} bytes, which exceeds the max " + f"{MAX_MEDIA_MEGABYTES}MB bytes for media files." + ), + brief="File too large", + ) + + match file_type.kind: + case "image": + data = await path.read_bytes() + data_url = _to_data_url(file_type.mime_type, data) + part = ImageURLPart(image_url=ImageURLPart.ImageURL(url=data_url)) + image_size = _extract_image_size(data) + case "video": + data = await path.read_bytes() + if (llm := self._runtime.llm) and isinstance(llm.chat_provider, Kimi): + part = await llm.chat_provider.files.upload_video( + data=data, + mime_type=file_type.mime_type, + ) + else: + data_url = _to_data_url(file_type.mime_type, data) + part = VideoURLPart(video_url=VideoURLPart.VideoURL(url=data_url)) + image_size = None + + size_hint = "" + if image_size: + size_hint = f", original size {image_size[0]}x{image_size[1]}px" + note = ( + " If you need to output coordinates, output relative coordinates first and " + "compute absolute coordinates using the original image size; if you generate or " + "edit images/videos via commands or scripts, read the result back immediately " + "before continuing." + ) + return ToolOk( + output=part, + message=( + f"Loaded {file_type.kind} file `{path}` " + f"({file_type.mime_type}, {size} bytes{size_hint}).{note}" + ), + ) + + @override + async def __call__(self, params: Params) -> ToolReturnValue: + if not params.path: + return ToolError( + message="File path cannot be empty.", + brief="Empty file path", + ) + + try: + p = KaosPath(params.path).expanduser() + if err := await self._validate_path(p): + return err + p = p.canonical() + + if not await p.exists(): + return ToolError( + message=f"`{params.path}` does not exist.", + brief="File not found", + ) + if not await p.is_file(): + return ToolError( + message=f"`{params.path}` is not a file.", + brief="Invalid path", + ) + + header = await p.read_bytes(MEDIA_SNIFF_BYTES) + file_type = detect_file_type(str(p), header=header) + if file_type.kind == "text": + return ToolError( + message=f"`{params.path}` is a text file. Use ReadFile to read text files.", + brief="Unsupported file type", + ) + if file_type.kind == "unknown": + return ToolError( + message=( + f"`{params.path}` seems not readable as an image or video file. " + "You may need to read it with proper shell commands, Python tools " + "or MCP tools if available. " + "If you read/operate it with Python, you MUST ensure that any " + "third-party packages are installed in a virtual environment (venv)." + ), + brief="File not readable", + ) + + if file_type.kind == "image" and "image_in" not in self._capabilities: + return ToolError( + message=( + "The current model does not support image input. " + "Tell the user to use a model with image input capability." + ), + brief="Unsupported media type", + ) + if file_type.kind == "video" and "video_in" not in self._capabilities: + return ToolError( + message=( + "The current model does not support video input. " + "Tell the user to use a model with video input capability." + ), + brief="Unsupported media type", + ) + + return await self._read_media(p, file_type) + except Exception as e: + return ToolError( + message=f"Failed to read {params.path}. Error: {e}", + brief="Failed to read file", + ) diff --git a/tests/conftest.py b/tests/conftest.py index d8aa8c476..f27b814b6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -29,6 +29,7 @@ from kimi_cli.tools.file.glob import Glob from kimi_cli.tools.file.grep_local import Grep from kimi_cli.tools.file.read import ReadFile +from kimi_cli.tools.file.read_media import ReadMediaFile from kimi_cli.tools.file.replace import StrReplaceFile from kimi_cli.tools.file.write import WriteFile from kimi_cli.tools.multiagent.create import CreateSubagent @@ -247,6 +248,12 @@ def read_file_tool(runtime: Runtime) -> ReadFile: return ReadFile(runtime) +@pytest.fixture +def read_media_file_tool(runtime: Runtime) -> ReadMediaFile: + """Create a ReadMediaFile tool instance.""" + return ReadMediaFile(runtime) + + @pytest.fixture def glob_tool(builtin_args: BuiltinSystemPromptArgs) -> Glob: """Create a Glob tool instance.""" diff --git a/tests/test_agent_spec.py b/tests/test_agent_spec.py index be7dffb0b..93e241e72 100644 --- a/tests/test_agent_spec.py +++ b/tests/test_agent_spec.py @@ -27,6 +27,7 @@ def test_load_default_agent_spec(): "kimi_cli.tools.todo:SetTodoList", "kimi_cli.tools.shell:Shell", "kimi_cli.tools.file:ReadFile", + "kimi_cli.tools.file:ReadMediaFile", "kimi_cli.tools.file:Glob", "kimi_cli.tools.file:Grep", "kimi_cli.tools.file:WriteFile", @@ -65,6 +66,7 @@ def test_load_default_agent_spec(): "kimi_cli.tools.todo:SetTodoList", "kimi_cli.tools.shell:Shell", "kimi_cli.tools.file:ReadFile", + "kimi_cli.tools.file:ReadMediaFile", "kimi_cli.tools.file:Glob", "kimi_cli.tools.file:Grep", "kimi_cli.tools.file:WriteFile", @@ -154,6 +156,7 @@ def test_load_agent_spec_default_extension(): "kimi_cli.tools.todo:SetTodoList", "kimi_cli.tools.shell:Shell", "kimi_cli.tools.file:ReadFile", + "kimi_cli.tools.file:ReadMediaFile", "kimi_cli.tools.file:Glob", "kimi_cli.tools.file:Grep", "kimi_cli.tools.file:WriteFile", diff --git a/tests/test_default_agent.py b/tests/test_default_agent.py index 3995d3fe0..84a58edf1 100644 --- a/tests/test_default_agent.py +++ b/tests/test_default_agent.py @@ -304,24 +304,20 @@ async def test_default_agent(runtime: Runtime): Tool( name="ReadFile", description="""\ -Read content from a file. +Read text content from a file. **Tips:** - Make sure you follow the description of each tool parameter. - A `` tag will be given before the read file content. - The system will notify you when there is anything wrong when reading the file. - This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible. -- This tool can only read text, image and video files. To list directories, you must use the Glob tool or `ls` command via the Shell tool. To read other file types, use appropriate commands via the Shell tool. +- This tool can only read text files. To read images or videos, use other appropriate tools. To list directories, use the Glob tool or `ls` command via the Shell tool. To read other file types, use appropriate commands via the Shell tool. - If the file doesn't exist or path is invalid, an error will be returned. - If you want to search for a certain content/pattern, prefer Grep tool over ReadFile. -- For text files: - - Content will be returned with a line number before each line like `cat -n` format. - - Use `line_offset` and `n_lines` parameters when you only need to read a part of the file. - - The maximum number of lines that can be read at once is 1000. - - Any lines longer than 2000 characters will be truncated, ending with "...". -- For image and video files: - - Content will be returned in a form that you can view and understand. Feel confident to read image/video files with this tool. - - The maximum size that can be read is 83886080 bytes. An error will be returned if the file is larger than this limit. +- Content will be returned with a line number before each line like `cat -n` format. +- Use `line_offset` and `n_lines` parameters when you only need to read a part of the file. +- The maximum number of lines that can be read at once is 1000. +- Any lines longer than 2000 characters will be truncated, ending with "...". """, parameters={ "properties": { @@ -346,6 +342,35 @@ async def test_default_agent(runtime: Runtime): "type": "object", }, ), + Tool( + name="ReadMediaFile", + description="""\ +Read media content from a file. + +**Tips:** +- Make sure you follow the description of each tool parameter. +- A `` tag will be given before the read file content. +- The system will notify you when there is anything wrong when reading the file. +- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible. +- This tool can only read image or video files. To read other types of files, use the ReadFile tool. To list directories, use the Glob tool or `ls` command via the Shell tool. +- If the file doesn't exist or path is invalid, an error will be returned. +- The maximum size that can be read is 100MB. An error will be returned if the file is larger than this limit. +- The media content will be returned in a form that you can directly view and understand. + +**Capabilities** +- This tool supports image and video files for the current model. +""", + parameters={ + "properties": { + "path": { + "description": "The path to the file to read. Absolute paths are required when reading files outside the working directory.", + "type": "string", + } + }, + "required": ["path"], + "type": "object", + }, + ), Tool( name="Glob", description="""\ @@ -762,6 +787,7 @@ async def test_default_agent(runtime: Runtime): [ "Shell", "ReadFile", + "ReadMediaFile", "Glob", "Grep", "WriteFile", diff --git a/tests/test_pyinstaller_utils.py b/tests/test_pyinstaller_utils.py index 02c1c9e0b..047e05a97 100644 --- a/tests/test_pyinstaller_utils.py +++ b/tests/test_pyinstaller_utils.py @@ -96,6 +96,10 @@ def test_pyinstaller_datas(): "src/kimi_cli/tools/file/read.md", "kimi_cli/tools/file", ), + ( + "src/kimi_cli/tools/file/read_media.md", + "kimi_cli/tools/file", + ), ( "src/kimi_cli/tools/file/replace.md", "kimi_cli/tools/file", @@ -140,6 +144,7 @@ def test_pyinstaller_hiddenimports(): "kimi_cli.tools.file.glob", "kimi_cli.tools.file.grep_local", "kimi_cli.tools.file.read", + "kimi_cli.tools.file.read_media", "kimi_cli.tools.file.replace", "kimi_cli.tools.file.utils", "kimi_cli.tools.file.write", diff --git a/tests/test_read_file.py b/tests/test_read_file.py index 35939c335..6e7115168 100644 --- a/tests/test_read_file.py +++ b/tests/test_read_file.py @@ -15,7 +15,6 @@ Params, ReadFile, ) -from kimi_cli.wire.types import ImageURLPart, VideoURLPart @pytest.fixture @@ -159,12 +158,11 @@ async def test_read_image_file(read_file_tool: ReadFile, temp_work_dir: KaosPath result = await read_file_tool(Params(path=str(image_file))) - assert not result.is_error - assert isinstance(result.output, list) - assert len(result.output) == 1 - part = result.output[0] - assert isinstance(part, ImageURLPart) - assert part.image_url.url.startswith("data:image/png;base64,") + assert result.is_error + assert result.message == snapshot( + f"`{image_file}` is a image file. Use other appropriate tools to read image or video files." + ) + assert result.brief == snapshot("Unsupported file type") async def test_read_extensionless_image_file(read_file_tool: ReadFile, temp_work_dir: KaosPath): @@ -175,12 +173,11 @@ async def test_read_extensionless_image_file(read_file_tool: ReadFile, temp_work result = await read_file_tool(Params(path=str(image_file))) - assert not result.is_error - assert isinstance(result.output, list) - assert len(result.output) == 1 - part = result.output[0] - assert isinstance(part, ImageURLPart) - assert part.image_url.url.startswith("data:image/png;base64,") + assert result.is_error + assert result.message == snapshot( + f"`{image_file}` is a image file. Use other appropriate tools to read image or video files." + ) + assert result.brief == snapshot("Unsupported file type") async def test_read_video_file(read_file_tool: ReadFile, temp_work_dir: KaosPath): @@ -191,12 +188,11 @@ async def test_read_video_file(read_file_tool: ReadFile, temp_work_dir: KaosPath result = await read_file_tool(Params(path=str(video_file))) - assert not result.is_error - assert isinstance(result.output, list) - assert len(result.output) == 1 - part = result.output[0] - assert isinstance(part, VideoURLPart) - assert part.video_url.url.startswith("data:video/mp4;base64,") + assert result.is_error + assert result.message == snapshot( + f"`{video_file}` is a video file. Use other appropriate tools to read image or video files." + ) + assert result.brief == snapshot("Unsupported file type") async def test_read_line_offset_beyond_file_length(read_file_tool: ReadFile, sample_file: KaosPath): diff --git a/tests/test_read_file_desc.py b/tests/test_read_file_desc.py deleted file mode 100644 index 38f7a8bf5..000000000 --- a/tests/test_read_file_desc.py +++ /dev/null @@ -1,124 +0,0 @@ -from __future__ import annotations -from typing import cast - -# ruff: noqa - -import pytest -from inline_snapshot import snapshot - -from kimi_cli.llm import ModelCapability -from kimi_cli.soul.agent import Runtime -from kimi_cli.tools.file.read import ReadFile - - -@pytest.mark.parametrize( - ("capabilities", "expected"), - [ - ( - {"image_in", "video_in"}, - snapshot( - """\ -Read content from a file. - -**Tips:** -- Make sure you follow the description of each tool parameter. -- A `` tag will be given before the read file content. -- The system will notify you when there is anything wrong when reading the file. -- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible. -- This tool can only read text, image and video files. To list directories, you must use the Glob tool or `ls` command via the Shell tool. To read other file types, use appropriate commands via the Shell tool. -- If the file doesn't exist or path is invalid, an error will be returned. -- If you want to search for a certain content/pattern, prefer Grep tool over ReadFile. -- For text files: - - Content will be returned with a line number before each line like `cat -n` format. - - Use `line_offset` and `n_lines` parameters when you only need to read a part of the file. - - The maximum number of lines that can be read at once is 1000. - - Any lines longer than 2000 characters will be truncated, ending with "...". -- For image and video files: - - Content will be returned in a form that you can view and understand. Feel confident to read image/video files with this tool. - - The maximum size that can be read is 83886080 bytes. An error will be returned if the file is larger than this limit. -""" - ), - ), - ( - {"image_in"}, - snapshot( - """\ -Read content from a file. - -**Tips:** -- Make sure you follow the description of each tool parameter. -- A `` tag will be given before the read file content. -- The system will notify you when there is anything wrong when reading the file. -- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible. -- This tool can only read text, image and video files. To list directories, you must use the Glob tool or `ls` command via the Shell tool. To read other file types, use appropriate commands via the Shell tool. -- If the file doesn't exist or path is invalid, an error will be returned. -- If you want to search for a certain content/pattern, prefer Grep tool over ReadFile. -- For text files: - - Content will be returned with a line number before each line like `cat -n` format. - - Use `line_offset` and `n_lines` parameters when you only need to read a part of the file. - - The maximum number of lines that can be read at once is 1000. - - Any lines longer than 2000 characters will be truncated, ending with "...". -- For image files: - - Content will be returned in a form that you can view and understand. Feel confident to read image files with this tool. - - The maximum size that can be read is 83886080 bytes. An error will be returned if the file is larger than this limit. -- Other media files (e.g., video, PDFs) are not supported by this tool. Use other proper tools to process them. -""" - ), - ), - ( - {"video_in"}, - snapshot( - """\ -Read content from a file. - -**Tips:** -- Make sure you follow the description of each tool parameter. -- A `` tag will be given before the read file content. -- The system will notify you when there is anything wrong when reading the file. -- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible. -- This tool can only read text, image and video files. To list directories, you must use the Glob tool or `ls` command via the Shell tool. To read other file types, use appropriate commands via the Shell tool. -- If the file doesn't exist or path is invalid, an error will be returned. -- If you want to search for a certain content/pattern, prefer Grep tool over ReadFile. -- For text files: - - Content will be returned with a line number before each line like `cat -n` format. - - Use `line_offset` and `n_lines` parameters when you only need to read a part of the file. - - The maximum number of lines that can be read at once is 1000. - - Any lines longer than 2000 characters will be truncated, ending with "...". -- For video files: - - Content will be returned in a form that you can view and understand. Feel confident to read video files with this tool. - - The maximum size that can be read is 83886080 bytes. An error will be returned if the file is larger than this limit. -- Other media files (e.g., image, PDFs) are not supported by this tool. Use other proper tools to process them. -""" - ), - ), - ( - set(), - snapshot( - """\ -Read content from a file. - -**Tips:** -- Make sure you follow the description of each tool parameter. -- A `` tag will be given before the read file content. -- The system will notify you when there is anything wrong when reading the file. -- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible. -- This tool can only read text, image and video files. To list directories, you must use the Glob tool or `ls` command via the Shell tool. To read other file types, use appropriate commands via the Shell tool. -- If the file doesn't exist or path is invalid, an error will be returned. -- If you want to search for a certain content/pattern, prefer Grep tool over ReadFile. -- For text files: - - Content will be returned with a line number before each line like `cat -n` format. - - Use `line_offset` and `n_lines` parameters when you only need to read a part of the file. - - The maximum number of lines that can be read at once is 1000. - - Any lines longer than 2000 characters will be truncated, ending with "...". -- Media files (e.g., image, video, PDFs) are not supported by this tool. Use other proper tools to process them. -""" - ), - ), - ], -) -def test_read_file_description_by_capabilities( - runtime: Runtime, capabilities: set[str], expected: str -) -> None: - assert runtime.llm is not None - runtime.llm.capabilities = cast(set[ModelCapability], capabilities) - assert ReadFile(runtime).base.description == expected diff --git a/tests/test_read_media_file.py b/tests/test_read_media_file.py new file mode 100644 index 000000000..11db0ba3a --- /dev/null +++ b/tests/test_read_media_file.py @@ -0,0 +1,144 @@ +"""Tests for the ReadMediaFile tool.""" + +from __future__ import annotations + +from io import BytesIO +from typing import cast + +import pytest +from inline_snapshot import snapshot +from kaos.path import KaosPath + +from kimi_cli.llm import ModelCapability +from kimi_cli.soul.agent import Runtime +from kimi_cli.tools.file.read_media import Params, ReadMediaFile +from kimi_cli.wire.types import ImageURLPart, VideoURLPart + + +async def test_read_image_file(read_media_file_tool: ReadMediaFile, temp_work_dir: KaosPath): + """Test reading an image file.""" + image_file = temp_work_dir / "sample.png" + data = b"\x89PNG\r\n\x1a\n" + b"pngdata" + await image_file.write_bytes(data) + + result = await read_media_file_tool(Params(path=str(image_file))) + + assert not result.is_error + assert isinstance(result.output, list) + assert len(result.output) == 1 + part = result.output[0] + assert isinstance(part, ImageURLPart) + assert part.image_url.url.startswith("data:image/png;base64,") + assert result.message == snapshot( + f"Loaded image file `{image_file}` (image/png, {len(data)} bytes). " + "If you need to output coordinates, output relative coordinates first and " + "compute absolute coordinates using the original image size; if you generate or " + "edit images/videos via commands or scripts, read the result back immediately " + "before continuing." + ) + + +async def test_read_extensionless_image_file( + read_media_file_tool: ReadMediaFile, temp_work_dir: KaosPath +): + """Test reading an extensionless image file.""" + image_file = temp_work_dir / "sample" + data = b"\x89PNG\r\n\x1a\n" + b"pngdata" + await image_file.write_bytes(data) + + result = await read_media_file_tool(Params(path=str(image_file))) + + assert not result.is_error + assert isinstance(result.output, list) + assert len(result.output) == 1 + part = result.output[0] + assert isinstance(part, ImageURLPart) + assert part.image_url.url.startswith("data:image/png;base64,") + assert result.message == snapshot( + f"Loaded image file `{image_file}` (image/png, {len(data)} bytes). " + "If you need to output coordinates, output relative coordinates first and " + "compute absolute coordinates using the original image size; if you generate or " + "edit images/videos via commands or scripts, read the result back immediately " + "before continuing." + ) + + +async def test_read_image_file_with_size( + read_media_file_tool: ReadMediaFile, temp_work_dir: KaosPath +): + """Test reading an image file with detectable dimensions.""" + Image = pytest.importorskip("PIL.Image") + image_file = temp_work_dir / "valid.png" + image = Image.new("RGB", (3, 4), color=(0, 0, 0)) + buffer = BytesIO() + image.save(buffer, format="PNG") + data = buffer.getvalue() + await image_file.write_bytes(data) + + result = await read_media_file_tool(Params(path=str(image_file))) + + assert not result.is_error + assert result.message == snapshot( + f"Loaded image file `{image_file}` (image/png, {len(data)} bytes, original size 3x4px). " + "If you need to output coordinates, output relative coordinates first and " + "compute absolute coordinates using the original image size; if you generate or " + "edit images/videos via commands or scripts, read the result back immediately " + "before continuing." + ) + + +async def test_read_video_file(read_media_file_tool: ReadMediaFile, temp_work_dir: KaosPath): + """Test reading a video file.""" + video_file = temp_work_dir / "sample.mp4" + data = b"\x00\x00\x00\x18ftypmp42\x00\x00\x00\x00mp42isom" + await video_file.write_bytes(data) + + result = await read_media_file_tool(Params(path=str(video_file))) + + assert not result.is_error + assert isinstance(result.output, list) + assert len(result.output) == 1 + part = result.output[0] + assert isinstance(part, VideoURLPart) + assert part.video_url.url.startswith("data:video/mp4;base64,") + assert result.message == snapshot( + f"Loaded video file `{video_file}` (video/mp4, {len(data)} bytes). " + "If you need to output coordinates, output relative coordinates first and " + "compute absolute coordinates using the original image size; if you generate or " + "edit images/videos via commands or scripts, read the result back immediately " + "before continuing." + ) + + +async def test_read_text_file(read_media_file_tool: ReadMediaFile, temp_work_dir: KaosPath): + """Test reading a text file with ReadMediaFile.""" + text_file = temp_work_dir / "sample.txt" + await text_file.write_text("hello") + + result = await read_media_file_tool(Params(path=str(text_file))) + + assert result.is_error + assert result.message == snapshot( + f"`{text_file}` is a text file. Use ReadFile to read text files." + ) + assert result.brief == snapshot("Unsupported file type") + + +async def test_read_video_file_without_capability(runtime: Runtime, temp_work_dir: KaosPath): + """Test reading a video file without video capability.""" + assert runtime.llm is not None + runtime.llm.capabilities = cast(set[ModelCapability], {"image_in"}) + read_media_file_tool = ReadMediaFile(runtime) + + video_file = temp_work_dir / "sample.mp4" + data = b"\x00\x00\x00\x18ftypmp42\x00\x00\x00\x00mp42isom" + await video_file.write_bytes(data) + + result = await read_media_file_tool(Params(path=str(video_file))) + + assert result.is_error + assert result.message == snapshot( + "The current model does not support video input. " + "Tell the user to use a model with video input capability." + ) + assert result.brief == snapshot("Unsupported media type") diff --git a/tests/test_read_media_file_desc.py b/tests/test_read_media_file_desc.py new file mode 100644 index 000000000..d813ef5a1 --- /dev/null +++ b/tests/test_read_media_file_desc.py @@ -0,0 +1,98 @@ +from __future__ import annotations + +from typing import cast + +# ruff: noqa + +import pytest +from inline_snapshot import snapshot + +from kimi_cli.llm import ModelCapability +from kimi_cli.soul.agent import Runtime +from kimi_cli.tools import SkipThisTool +from kimi_cli.tools.file.read_media import ReadMediaFile + + +@pytest.mark.parametrize( + ("capabilities", "expected"), + [ + ( + {"image_in", "video_in"}, + snapshot( + """\ +Read media content from a file. + +**Tips:** +- Make sure you follow the description of each tool parameter. +- A `` tag will be given before the read file content. +- The system will notify you when there is anything wrong when reading the file. +- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible. +- This tool can only read image or video files. To read other types of files, use the ReadFile tool. To list directories, use the Glob tool or `ls` command via the Shell tool. +- If the file doesn't exist or path is invalid, an error will be returned. +- The maximum size that can be read is 100MB. An error will be returned if the file is larger than this limit. +- The media content will be returned in a form that you can directly view and understand. + +**Capabilities** +- This tool supports image and video files for the current model. +""" + ), + ), + ( + {"image_in"}, + snapshot( + """\ +Read media content from a file. + +**Tips:** +- Make sure you follow the description of each tool parameter. +- A `` tag will be given before the read file content. +- The system will notify you when there is anything wrong when reading the file. +- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible. +- This tool can only read image or video files. To read other types of files, use the ReadFile tool. To list directories, use the Glob tool or `ls` command via the Shell tool. +- If the file doesn't exist or path is invalid, an error will be returned. +- The maximum size that can be read is 100MB. An error will be returned if the file is larger than this limit. +- The media content will be returned in a form that you can directly view and understand. + +**Capabilities** +- This tool supports image files for the current model. +- Video files are not supported by the current model. +""" + ), + ), + ( + {"video_in"}, + snapshot( + """\ +Read media content from a file. + +**Tips:** +- Make sure you follow the description of each tool parameter. +- A `` tag will be given before the read file content. +- The system will notify you when there is anything wrong when reading the file. +- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible. +- This tool can only read image or video files. To read other types of files, use the ReadFile tool. To list directories, use the Glob tool or `ls` command via the Shell tool. +- If the file doesn't exist or path is invalid, an error will be returned. +- The maximum size that can be read is 100MB. An error will be returned if the file is larger than this limit. +- The media content will be returned in a form that you can directly view and understand. + +**Capabilities** +- This tool supports video files for the current model. +- Image files are not supported by the current model. +""" + ), + ), + ], +) +def test_read_media_file_description_by_capabilities( + runtime: Runtime, capabilities: set[str], expected: str +) -> None: + assert runtime.llm is not None + runtime.llm.capabilities = cast(set[ModelCapability], capabilities) + assert ReadMediaFile(runtime).base.description == expected + + +def test_read_media_file_description_without_capabilities(runtime: Runtime) -> None: + assert runtime.llm is not None + runtime.llm.capabilities = cast(set[ModelCapability], set()) + with pytest.raises(SkipThisTool): + ReadMediaFile(runtime) diff --git a/tests/test_tool_descriptions.py b/tests/test_tool_descriptions.py index 2c59aabc3..fadc116f7 100644 --- a/tests/test_tool_descriptions.py +++ b/tests/test_tool_descriptions.py @@ -12,6 +12,7 @@ from kimi_cli.tools.file.glob import Glob from kimi_cli.tools.file.grep_local import Grep from kimi_cli.tools.file.read import ReadFile +from kimi_cli.tools.file.read_media import ReadMediaFile from kimi_cli.tools.file.replace import StrReplaceFile from kimi_cli.tools.file.write import WriteFile from kimi_cli.tools.multiagent.task import Task @@ -172,24 +173,42 @@ def test_read_file_description(read_file_tool: ReadFile): """Test the description of ReadFile tool.""" assert read_file_tool.base.description == snapshot( """\ -Read content from a file. +Read text content from a file. **Tips:** - Make sure you follow the description of each tool parameter. - A `` tag will be given before the read file content. - The system will notify you when there is anything wrong when reading the file. - This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible. -- This tool can only read text, image and video files. To list directories, you must use the Glob tool or `ls` command via the Shell tool. To read other file types, use appropriate commands via the Shell tool. +- This tool can only read text files. To read images or videos, use other appropriate tools. To list directories, use the Glob tool or `ls` command via the Shell tool. To read other file types, use appropriate commands via the Shell tool. - If the file doesn't exist or path is invalid, an error will be returned. - If you want to search for a certain content/pattern, prefer Grep tool over ReadFile. -- For text files: - - Content will be returned with a line number before each line like `cat -n` format. - - Use `line_offset` and `n_lines` parameters when you only need to read a part of the file. - - The maximum number of lines that can be read at once is 1000. - - Any lines longer than 2000 characters will be truncated, ending with "...". -- For image and video files: - - Content will be returned in a form that you can view and understand. Feel confident to read image/video files with this tool. - - The maximum size that can be read is 83886080 bytes. An error will be returned if the file is larger than this limit. +- Content will be returned with a line number before each line like `cat -n` format. +- Use `line_offset` and `n_lines` parameters when you only need to read a part of the file. +- The maximum number of lines that can be read at once is 1000. +- Any lines longer than 2000 characters will be truncated, ending with "...". +""" + ) + + +def test_read_media_file_description(read_media_file_tool: ReadMediaFile): + """Test the description of ReadMediaFile tool.""" + assert read_media_file_tool.base.description == snapshot( + """\ +Read media content from a file. + +**Tips:** +- Make sure you follow the description of each tool parameter. +- A `` tag will be given before the read file content. +- The system will notify you when there is anything wrong when reading the file. +- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible. +- This tool can only read image or video files. To read other types of files, use the ReadFile tool. To list directories, use the Glob tool or `ls` command via the Shell tool. +- If the file doesn't exist or path is invalid, an error will be returned. +- The maximum size that can be read is 100MB. An error will be returned if the file is larger than this limit. +- The media content will be returned in a form that you can directly view and understand. + +**Capabilities** +- This tool supports image and video files for the current model. """ ) diff --git a/tests/test_tool_schemas.py b/tests/test_tool_schemas.py index 2d1caadfc..45eea7375 100644 --- a/tests/test_tool_schemas.py +++ b/tests/test_tool_schemas.py @@ -10,6 +10,7 @@ from kimi_cli.tools.file.glob import Glob from kimi_cli.tools.file.grep_local import Grep from kimi_cli.tools.file.read import ReadFile +from kimi_cli.tools.file.read_media import ReadMediaFile from kimi_cli.tools.file.replace import StrReplaceFile from kimi_cli.tools.file.write import WriteFile from kimi_cli.tools.multiagent.task import Task @@ -180,6 +181,22 @@ def test_read_file_params_schema(read_file_tool: ReadFile): ) +def test_read_media_file_params_schema(read_media_file_tool: ReadMediaFile): + """Test the schema of ReadMediaFile tool parameters.""" + assert read_media_file_tool.base.parameters == snapshot( + { + "properties": { + "path": { + "description": "The path to the file to read. Absolute paths are required when reading files outside the working directory.", + "type": "string", + } + }, + "required": ["path"], + "type": "object", + } + ) + + def test_glob_params_schema(glob_tool: Glob): """Test the schema of Glob tool parameters.""" assert glob_tool.base.parameters == snapshot(