Download Code-agent-main/ycode/agent/planner.py from Yazhiniyan/YCode: direct link, hf CLI and curl.
- Browser
- Download file 3.45 kB
-
https://huggingface.co/Yazhiniyan/YCode/resolve/main/Code-agent-main/ycode/agent/planner.py
- Command line
-
hf download hf://Yazhiniyan/YCode/Code-agent-main/ycode/agent/planner.py
-
curl -L -o planner.py https://huggingface.co/Yazhiniyan/YCode/resolve/main/Code-agent-main/ycode/agent/planner.py
3.45 kB
| """Planning: a structured plan the model maintains through the update_plan tool. | |
| The model decides how much planning a task needs (the system prompt tells it | |
| to skip plans for trivial requests). When it does plan, the plan is shown to | |
| the user and kept up to date as steps complete. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from typing import Any, Callable | |
| from ycode.errors import ToolError | |
| from ycode.tools.base import Tool, ToolContext, ToolResult | |
| STATUSES = ("pending", "in_progress", "done", "skipped") | |
| class PlanStep: | |
| text: str | |
| status: str = "pending" | |
| class Plan: | |
| steps: list[PlanStep] = field(default_factory=list) | |
| def is_empty(self) -> bool: | |
| return not self.steps | |
| def render_text(self) -> str: | |
| marks = {"pending": " ", "in_progress": "~", "done": "x", "skipped": "-"} | |
| return "\n".join(f"{i}. [{marks[s.status]}] {s.text}" for i, s in enumerate(self.steps, 1)) | |
| def parse_steps(raw: Any) -> list[PlanStep]: | |
| if not isinstance(raw, list) or not raw: | |
| raise ToolError("steps must be a non-empty list") | |
| steps: list[PlanStep] = [] | |
| for item in raw: | |
| if isinstance(item, str): | |
| steps.append(PlanStep(item.strip())) | |
| continue | |
| if not isinstance(item, dict) or not isinstance(item.get("step"), str): | |
| raise ToolError("each step must be a string or an object with a 'step' string") | |
| status = item.get("status", "pending") | |
| if status not in STATUSES: | |
| raise ToolError(f"invalid status {status!r}; use one of {', '.join(STATUSES)}") | |
| steps.append(PlanStep(item["step"].strip(), status)) | |
| if len(steps) > 20: | |
| raise ToolError("keep plans to 20 steps or fewer") | |
| return steps | |
| class UpdatePlanTool(Tool): | |
| name = "update_plan" | |
| description = ( | |
| "Record or update your implementation plan for the current task. Call it before making " | |
| "significant changes (skip it for trivial requests), and again to mark steps in_progress " | |
| "or done. Pass the full list of steps every time." | |
| ) | |
| parameters = { | |
| "type": "object", | |
| "properties": { | |
| "steps": { | |
| "type": "array", | |
| "description": "Ordered plan steps.", | |
| "items": { | |
| "type": "object", | |
| "properties": { | |
| "step": {"type": "string", "description": "Short imperative description."}, | |
| "status": {"type": "string", "enum": list(STATUSES)}, | |
| }, | |
| "required": ["step"], | |
| }, | |
| }, | |
| }, | |
| "required": ["steps"], | |
| } | |
| def __init__(self, on_update: Callable[[Plan], None] | None = None) -> None: | |
| self.plan = Plan() | |
| self.on_update = on_update | |
| def describe(self, args: dict[str, Any]) -> str: | |
| return "Updating plan" | |
| def run(self, args: dict[str, Any], ctx: ToolContext) -> ToolResult: | |
| self.plan = Plan(parse_steps(args["steps"])) | |
| if self.on_update: | |
| self.on_update(self.plan) | |
| done = sum(s.status == "done" for s in self.plan.steps) | |
| return ToolResult( | |
| content="Plan recorded:\n" + self.plan.render_text(), | |
| summary=f"Plan: {done}/{len(self.plan.steps)} steps done", | |
| display={"plan": self.plan}, | |
| ) | |