9201ef759e
CI / lint (push) Waiting to run
CI / mypy (push) Waiting to run
CI / docs (push) Waiting to run
CI / test on 3.10 (standard) (push) Waiting to run
CI / test on 3.11 (standard) (push) Waiting to run
CI / test on 3.12 (standard) (push) Waiting to run
CI / test on 3.10 (all-extras) (push) Waiting to run
CI / test on 3.11 (all-extras) (push) Waiting to run
CI / test on 3.12 (all-extras) (push) Waiting to run
CI / test on 3.13 (all-extras) (push) Waiting to run
CI / test on 3.14 (pydantic-evals) (push) Waiting to run
CI / test on 3.13 (standard) (push) Waiting to run
CI / test on 3.14 (standard) (push) Waiting to run
CI / test on 3.14 (all-extras) (push) Waiting to run
CI / test on 3.10 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.11 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.12 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.13 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.14 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.10 (pydantic-evals) (push) Waiting to run
CI / test on 3.11 (pydantic-evals) (push) Waiting to run
CI / test on 3.12 (pydantic-evals) (push) Waiting to run
CI / test on 3.13 (pydantic-evals) (push) Waiting to run
CI / test on 3.10 (lowest-versions) (push) Waiting to run
CI / test on 3.11 (lowest-versions) (push) Waiting to run
CI / test on 3.12 (lowest-versions) (push) Waiting to run
CI / test on 3.13 (lowest-versions) (push) Waiting to run
CI / test on 3.14 (lowest-versions) (push) Waiting to run
CI / test examples on 3.11 (push) Waiting to run
CI / test examples on 3.12 (push) Waiting to run
CI / test examples on 3.13 (push) Waiting to run
CI / test examples on 3.14 (push) Waiting to run
CI / coverage (push) Blocked by required conditions
CI / check (push) Blocked by required conditions
CI / deploy-docs (push) Blocked by required conditions
CI / deploy-docs-preview (push) Blocked by required conditions
CI / build release artifacts (push) Blocked by required conditions
CI / publish to PyPI (push) Blocked by required conditions
CI / Send tweet (push) Blocked by required conditions
Harness Compat / harness compat (push) Failing after 0s
53 lines
2.2 KiB
Python
53 lines
2.2 KiB
Python
"""Claude's `TodoWrite` tool -- record the agent's task checklist.
|
|
|
|
Backed by pydantic-ai-harness's `experimental.planning` capability: the adapter
|
|
maps Claude's todo schema onto the harness `PlanItem` list and calls the same
|
|
`write_plan` the capability exposes, so the checklist is rendered (with the
|
|
harness's advisory note when more than one step is `in_progress`) instead of a
|
|
hand-rolled ack.
|
|
|
|
`planning` is experimental -- its API may change without a deprecation period --
|
|
which is acceptable here; the warning is silenced at import so it doesn't leak
|
|
into the agent's stdout. The Claude `TodoWrite` signature (`content` / `status` /
|
|
`activeForm` items) is preserved; `activeForm` is the present-tense label Claude
|
|
shows while a step runs and has no harness equivalent, so it's dropped (the
|
|
headless shim renders nothing live anyway).
|
|
"""
|
|
|
|
import warnings
|
|
|
|
from pydantic_ai_harness.experimental import HarnessExperimentalWarning
|
|
from typing_extensions import TypedDict
|
|
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter('ignore', HarnessExperimentalWarning)
|
|
from pydantic_ai_harness.experimental.planning import PlanItem, Planning, PlanningToolset, TaskStatus
|
|
|
|
|
|
class TodoItem(TypedDict):
|
|
"""One entry for `TodoWrite` (Claude's todo schema)."""
|
|
|
|
content: str
|
|
status: str
|
|
activeForm: str
|
|
|
|
|
|
def _to_status(value: str) -> TaskStatus:
|
|
"""Map a Claude todo status onto a harness `TaskStatus`, defaulting to `pending`."""
|
|
try:
|
|
return TaskStatus(value)
|
|
except ValueError:
|
|
return TaskStatus.pending
|
|
|
|
|
|
async def todo_write(todos: list[TodoItem]) -> str:
|
|
"""Record the agent's task checklist."""
|
|
items = [PlanItem(content=t.get('content', ''), status=_to_status(t.get('status', ''))) for t in todos]
|
|
# A fresh capability per call gives a fresh plan state; Claude resends the
|
|
# full list every time, so no cross-call state needs to be retained.
|
|
# `get_toolset()` is typed `AgentToolset | None` but always returns the
|
|
# planning toolset, so narrow to reach `write_plan` without a private import.
|
|
toolset = Planning[None]().get_toolset()
|
|
assert isinstance(toolset, PlanningToolset)
|
|
return await toolset.write_plan(items)
|