Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Only write entries that are worth mentioning to users.

## Unreleased

- Tool: Make `ReadFile` tool description reflect model capabilities for image/video support

## 0.75 (2026-01-09)

- Tool: Improve `ReadFile` tool description
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ ai-test: ## Run the test suite with Kimi CLI.
.PHONY: gen-changelog gen-docs
gen-changelog: ## Generate changelog with Kimi CLI.
@echo "==> Generating changelog"
@uv run kimi -c "$$(cat ./docs/prompts/gen-changelog.txt)" --yolo
@uv run kimi -c "$$(cat .kimi/prompts/gen-changelog.md)" --yolo
gen-docs: ## Generate user docs with Kimi CLI.
@echo "==> Generating user docs"
@uv run kimi -c "$$(cat ./docs/prompts/gen-docs.txt)" --yolo
@uv run kimi -c "$$(cat .kimi/prompts/gen-docs.md)" --yolo
Comment on lines +113 to +116

Copilot AI Jan 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These Makefile changes (updating prompt file paths from ./docs/prompts/ to .kimi/prompts/ and changing file extensions from .txt to .md) appear to be unrelated to the PR's stated purpose of adding conditional ReadFile descriptions. These changes should either be explained in the PR description or moved to a separate commit/PR for better change tracking.

Copilot uses AI. Check for mistakes.

include src/kimi_cli/deps/Makefile
2 changes: 2 additions & 0 deletions docs/en/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ This page documents the changes in each Kimi CLI release.

## Unreleased

- Tool: Make `ReadFile` tool description reflect model capabilities for image/video support

## 0.75 (2026-01-09)

- Tool: Improve `ReadFile` tool description
Expand Down
2 changes: 2 additions & 0 deletions docs/zh/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

## 未发布

- Tool:让 `ReadFile` 工具描述根据模型能力动态反映图片/视频支持情况

## 0.75 (2026-01-09)

- Tool:改进 `ReadFile` 工具描述
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies = [
"pykaos==0.6.0",
"batrachian-toad==0.5.23; python_version >= \"3.14\"",
"tomlkit==0.13.3",
"jinja2==3.1.4",
]

[dependency-groups]
Expand Down
16 changes: 15 additions & 1 deletion src/kimi_cli/tools/file/read.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ Read content from a file.
- 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. If you do not support image/video input, try other tools to process media 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 %}
30 changes: 16 additions & 14 deletions src/kimi_cli/tools/file/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
from kosong.tooling import CallableTool2, ToolError, ToolOk, ToolReturnValue
from pydantic import BaseModel, Field

from kimi_cli.soul.agent import BuiltinSystemPromptArgs
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.utils import load_desc, truncate_line
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

Expand Down Expand Up @@ -52,20 +52,22 @@ class Params(BaseModel):

class ReadFile(CallableTool2[Params]):
name: str = "ReadFile"
description: str = load_desc(
Path(__file__).parent / "read.md",
{
"MAX_LINES": str(MAX_LINES),
"MAX_LINE_LENGTH": str(MAX_LINE_LENGTH),
"MAX_BYTES": str(MAX_BYTES),
"MAX_MEDIA_BYTES": str(MAX_MEDIA_BYTES),
},
)
params: type[Params] = Params

def __init__(self, builtin_args: BuiltinSystemPromptArgs) -> None:
super().__init__()
self._work_dir = builtin_args.KIMI_WORK_DIR
def __init__(self, runtime: Runtime) -> None:
capabilities = runtime.llm.capabilities if runtime.llm else set[str]()

Copilot AI Jan 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expression set[str]() is incorrect syntax. This should be set() to create an empty set. The current code set[str]() attempts to call a generic type as a function, which will raise a TypeError at runtime when runtime.llm is None. To fix this, change it to just set() since the type will be inferred from the conditional expression, or use set[ModelCapability]() if you want to be explicit (though you'll need to import ModelCapability from kimi_cli.llm).

Suggested change
capabilities = runtime.llm.capabilities if runtime.llm else set[str]()
capabilities = runtime.llm.capabilities if runtime.llm else set()

Copilot uses AI. Check for mistakes.
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)
self._work_dir = runtime.builtin_args.KIMI_WORK_DIR

async def _validate_path(self, path: KaosPath) -> ToolError | None:
"""Validate that the path is safe to read."""
Expand Down
16 changes: 16 additions & 0 deletions src/kimi_cli/tools/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import string
from pathlib import Path

from jinja2 import Environment
from kosong.tooling import BriefDisplayBlock, DisplayBlock, ToolError, ToolReturnValue
from kosong.utils.typing import JsonType

Expand All @@ -14,6 +15,21 @@ def load_desc(path: Path, substitutions: dict[str, str] | None = None) -> str:
return description


def load_desc_jinja(path: Path, context: dict[str, object] | None = None) -> str:
"""Load a tool description from a file, rendered via Jinja2."""
description = path.read_text(encoding="utf-8")
env = Environment(
autoescape=False,
keep_trailing_newline=True,
lstrip_blocks=True,
trim_blocks=True,
variable_start_string="${",
variable_end_string="}",
)
template = env.from_string(description)
return template.render(context or {})


def truncate_line(line: str, max_length: int, marker: str = "...") -> str:
"""
Truncate a line if it exceeds `max_length`, preserving the beginning and the line break.
Expand Down
8 changes: 4 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from pydantic import SecretStr

from kimi_cli.config import Config, MoonshotSearchConfig, get_default_config
from kimi_cli.llm import LLM
from kimi_cli.llm import ALL_MODEL_CAPABILITIES, LLM
from kimi_cli.metadata import WorkDirMeta
from kimi_cli.session import Session
from kimi_cli.soul.agent import Agent, BuiltinSystemPromptArgs, LaborMarket, Runtime
Expand Down Expand Up @@ -58,7 +58,7 @@ def llm() -> LLM:
return LLM(
chat_provider=MockChatProvider([]),
max_context_size=100_000,
capabilities=set(),
capabilities=ALL_MODEL_CAPABILITIES,
)


Expand Down Expand Up @@ -242,9 +242,9 @@ def shell_tool(approval: Approval, environment: Environment) -> Generator[Shell]


@pytest.fixture
def read_file_tool(builtin_args: BuiltinSystemPromptArgs) -> ReadFile:
def read_file_tool(runtime: Runtime) -> ReadFile:
"""Create a ReadFile tool instance."""
return ReadFile(builtin_args)
return ReadFile(runtime)


@pytest.fixture
Expand Down
2 changes: 1 addition & 1 deletion tests/test_default_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ async def test_default_agent(runtime: Runtime):
- 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. If you do not support image/video input, try other tools to process media 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.
""",
parameters={
Expand Down
124 changes: 124 additions & 0 deletions tests/test_read_file_desc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
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 `<system>` 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 `<system>` 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 `<system>` 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 `<system>` 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
2 changes: 1 addition & 1 deletion tests/test_tool_descriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ def test_read_file_description(read_file_tool: ReadFile):
- 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. If you do not support image/video input, try other tools to process media 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.
"""
)
Expand Down
8 changes: 5 additions & 3 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading