Skip to content

Commit 854c09d

Browse files
authored
Align Python SDK path types with Kimi CLI signatures (#13)
* fix: unify work_dir and skills_dir types to KaosPath in session and prompt APIs * test: add tests for Session and prompt requiring KaosPath for work_dir and skills_dir * fix: update kimi-cli dependency version to 0.79
1 parent e23ac77 commit 854c09d

6 files changed

Lines changed: 92 additions & 30 deletions

File tree

python/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ asyncio.run(main())
3434

3535
```python
3636
import asyncio
37+
from kaos.path import KaosPath
3738
from kimi_agent_sdk import ApprovalRequest, Session, TextPart
3839

3940

4041
async def main() -> None:
41-
async with await Session.create(work_dir=".") as session:
42+
async with await Session.create(work_dir=KaosPath.cwd()) as session:
4243
async for wire_msg in session.prompt("List files in current directory"):
4344
match wire_msg:
4445
case TextPart(text=text):
@@ -54,4 +55,5 @@ asyncio.run(main())
5455

5556
- `prompt()` creates a temporary session per call.
5657
- `Session.prompt()` yields raw Wire messages and requires handling approvals.
58+
- `work_dir` and `skills_dir` expect `KaosPath` values.
5759
- See the API docstrings for more configuration options.

python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ classifiers = [
1919
"License :: OSI Approved :: Apache Software License",
2020
]
2121
dependencies = [
22-
"kimi-cli>=0.77,<0.78",
22+
"kimi-cli>=0.79,<0.80",
2323
"kosong>=0.38.0,<0.39.0",
2424
]
2525

python/src/kimi_agent_sdk/_prompt.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from pathlib import Path
66
from typing import TYPE_CHECKING, Any
77

8+
from kaos.path import KaosPath
89
from kimi_cli.config import Config
910
from kimi_cli.wire.types import ApprovalRequest
1011
from kosong.message import ContentPart, Message
@@ -22,7 +23,7 @@ async def prompt(
2223
user_input: str | list[ContentPart],
2324
*,
2425
# Basic configuration
25-
work_dir: Path | str | None = None,
26+
work_dir: KaosPath | None = None,
2627
config: Config | Path | None = None,
2728
model: str | None = None,
2829
thinking: bool = False,
@@ -32,7 +33,7 @@ async def prompt(
3233
# Extensions
3334
agent_file: Path | None = None,
3435
mcp_configs: list[MCPConfig] | list[dict[str, Any]] | None = None,
35-
skills_dir: Path | None = None,
36+
skills_dir: KaosPath | None = None,
3637
# Loop control
3738
max_steps_per_turn: int | None = None,
3839
max_retries_per_step: int | None = None,
@@ -48,15 +49,15 @@ async def prompt(
4849
4950
Args:
5051
user_input: User input, can be plain text or a list of content parts.
51-
work_dir: Working directory. Defaults to current directory.
52+
work_dir: Working directory (KaosPath). Defaults to current directory.
5253
config: Configuration object or path to a config file.
5354
model: Model name, e.g. "kimi".
5455
thinking: Whether to enable thinking mode (requires model support).
5556
yolo: Automatically approve all approval requests.
5657
approval_handler_fn: Custom approval handler callback (sync or async).
5758
agent_file: Agent specification file path.
5859
mcp_configs: MCP server configurations.
59-
skills_dir: Skills directory.
60+
skills_dir: Skills directory (KaosPath).
6061
max_steps_per_turn: Maximum number of steps in one turn.
6162
max_retries_per_step: Maximum number of retries per step.
6263
max_ralph_iterations: Extra iterations in Ralph mode (-1 for unlimited).

python/src/kimi_agent_sdk/_session.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,9 @@
1919
from kimi_agent_sdk import MCPConfig
2020

2121

22-
def _coerce_work_dir(work_dir: Path | str | None) -> KaosPath:
23-
if work_dir is None:
24-
return KaosPath.cwd()
25-
if isinstance(work_dir, KaosPath):
26-
return work_dir.expanduser()
27-
if isinstance(work_dir, Path):
28-
return KaosPath.unsafe_from_local_path(work_dir).expanduser()
29-
return KaosPath(str(work_dir)).expanduser()
22+
def _ensure_type(name: str, value: object, expected: type) -> None:
23+
if not isinstance(value, expected):
24+
raise TypeError(f"{name} must be {expected.__name__}, got {type(value).__name__}")
3025

3126

3227
class Session:
@@ -44,7 +39,7 @@ def __init__(self, cli: KimiCLI) -> None:
4439

4540
@staticmethod
4641
async def create(
47-
work_dir: Path | str | None = None,
42+
work_dir: KaosPath | None = None,
4843
*,
4944
# Basic configuration
5045
session_id: str | None = None,
@@ -56,7 +51,7 @@ async def create(
5651
# Extensions
5752
agent_file: Path | None = None,
5853
mcp_configs: list[MCPConfig] | list[dict[str, Any]] | None = None,
59-
skills_dir: Path | None = None,
54+
skills_dir: KaosPath | None = None,
6055
# Loop control
6156
max_steps_per_turn: int | None = None,
6257
max_retries_per_step: int | None = None,
@@ -66,15 +61,15 @@ async def create(
6661
Create a new Session instance.
6762
6863
Args:
69-
work_dir: Working directory. Defaults to current directory.
64+
work_dir: Working directory (KaosPath). Defaults to current directory.
7065
session_id: Custom session ID (optional).
7166
config: Configuration object or path to a config file.
7267
model: Model name, e.g. "kimi".
7368
thinking: Whether to enable thinking mode (requires model support).
7469
yolo: Automatically approve all approval requests.
7570
agent_file: Agent specification file path.
7671
mcp_configs: MCP server configurations.
77-
skills_dir: Skills directory.
72+
skills_dir: Skills directory (KaosPath).
7873
max_steps_per_turn: Maximum number of steps in one turn.
7974
max_retries_per_step: Maximum number of retries per step.
8075
max_ralph_iterations: Extra iterations in Ralph mode (-1 for unlimited).
@@ -91,7 +86,14 @@ async def create(
9186
MCPRuntimeError(KimiCLIException, RuntimeError): When any MCP server cannot be
9287
connected.
9388
"""
94-
work_dir_path = _coerce_work_dir(work_dir)
89+
if work_dir is None:
90+
work_dir_path = KaosPath.cwd()
91+
else:
92+
_ensure_type("work_dir", work_dir, KaosPath)
93+
work_dir_path = work_dir
94+
95+
if skills_dir is not None:
96+
_ensure_type("skills_dir", skills_dir, KaosPath)
9597
cli_session = await CliSession.create(work_dir_path, session_id)
9698
cli = await KimiCLI.create(
9799
cli_session,
@@ -110,7 +112,7 @@ async def create(
110112

111113
@staticmethod
112114
async def resume(
113-
work_dir: Path | str,
115+
work_dir: KaosPath,
114116
session_id: str | None = None,
115117
*,
116118
# Basic configuration
@@ -122,7 +124,7 @@ async def resume(
122124
# Extensions
123125
agent_file: Path | None = None,
124126
mcp_configs: list[MCPConfig] | list[dict[str, Any]] | None = None,
125-
skills_dir: Path | None = None,
127+
skills_dir: KaosPath | None = None,
126128
# Loop control
127129
max_steps_per_turn: int | None = None,
128130
max_retries_per_step: int | None = None,
@@ -132,15 +134,15 @@ async def resume(
132134
Resume an existing session.
133135
134136
Args:
135-
work_dir: Working directory to resume from.
137+
work_dir: Working directory to resume from (KaosPath).
136138
session_id: Session ID to resume. If None, resumes the most recent session.
137139
config: Configuration object or path to a config file.
138140
model: Model name, e.g. "kimi".
139141
thinking: Whether to enable thinking mode (requires model support).
140142
yolo: Automatically approve all approval requests.
141143
agent_file: Agent specification file path.
142144
mcp_configs: MCP server configurations.
143-
skills_dir: Skills directory.
145+
skills_dir: Skills directory (KaosPath).
144146
max_steps_per_turn: Maximum number of steps in one turn.
145147
max_retries_per_step: Maximum number of retries per step.
146148
max_ralph_iterations: Extra iterations in Ralph mode (-1 for unlimited).
@@ -157,11 +159,13 @@ async def resume(
157159
MCPRuntimeError(KimiCLIException, RuntimeError): When any MCP server cannot be
158160
connected.
159161
"""
160-
work_dir_path = _coerce_work_dir(work_dir)
162+
_ensure_type("work_dir", work_dir, KaosPath)
163+
if skills_dir is not None:
164+
_ensure_type("skills_dir", skills_dir, KaosPath)
161165
if session_id is None:
162-
cli_session = await CliSession.continue_(work_dir_path)
166+
cli_session = await CliSession.continue_(work_dir)
163167
else:
164-
cli_session = await CliSession.find(work_dir_path, session_id)
168+
cli_session = await CliSession.find(work_dir, session_id)
165169
if cli_session is None:
166170
return None
167171
cli = await KimiCLI.create(

python/tests/test_paths.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
from typing import Any, cast
5+
6+
import pytest
7+
from kaos.path import KaosPath
8+
9+
from kimi_agent_sdk import Session, prompt
10+
from kimi_agent_sdk import _session as session_module
11+
12+
13+
@pytest.mark.asyncio
14+
async def test_session_create_requires_kaos_work_dir() -> None:
15+
with pytest.raises(TypeError, match="work_dir must be KaosPath"):
16+
await Session.create(work_dir=cast(Any, Path(".")))
17+
18+
19+
@pytest.mark.asyncio
20+
async def test_session_resume_requires_kaos_work_dir() -> None:
21+
with pytest.raises(TypeError, match="work_dir must be KaosPath"):
22+
await Session.resume(cast(Any, Path(".")))
23+
24+
25+
@pytest.mark.asyncio
26+
async def test_session_create_requires_kaos_skills_dir() -> None:
27+
with pytest.raises(TypeError, match="skills_dir must be KaosPath"):
28+
await Session.create(work_dir=KaosPath.cwd(), skills_dir=cast(Any, Path(".")))
29+
30+
31+
@pytest.mark.asyncio
32+
async def test_prompt_requires_kaos_work_dir() -> None:
33+
with pytest.raises(TypeError, match="work_dir must be KaosPath"):
34+
await anext(prompt("hi", yolo=True, work_dir=cast(Any, Path("."))))
35+
36+
37+
@pytest.mark.asyncio
38+
async def test_prompt_requires_kaos_skills_dir() -> None:
39+
with pytest.raises(TypeError, match="skills_dir must be KaosPath"):
40+
await anext(prompt("hi", yolo=True, skills_dir=cast(Any, Path("."))))
41+
42+
43+
@pytest.mark.asyncio
44+
async def test_session_create_accepts_kaos_paths(monkeypatch: pytest.MonkeyPatch) -> None:
45+
async def _dummy_session_create(*_args: Any, **_kwargs: Any) -> Any:
46+
return object()
47+
48+
async def _dummy_cli_create(*_args: Any, **_kwargs: Any) -> Any:
49+
return cast(Any, object())
50+
51+
monkeypatch.setattr(session_module.CliSession, "create", _dummy_session_create)
52+
monkeypatch.setattr(session_module.KimiCLI, "create", _dummy_cli_create)
53+
54+
session = await Session.create(work_dir=KaosPath.cwd(), skills_dir=KaosPath.cwd())
55+
assert isinstance(session, Session)

python/uv.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)