mpfaffenberger / mpfaffenberger/code_puppy
context_indicator plugin forks agents/_history token estimation to dodge a monkeypatch — same MCP/tool-schema walkers maintained twice
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 814
- Forks
- 278
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 76
Description
Theme
plugins/context_indicator/usage.py deliberately re-implements the token-estimation walkers from code_puppy/agents/_history.py so that /context stays immune to the token_ratio_learner plugin monkeypatching _history.estimate_tokens. The motive is documented and sound (usage.py:6-28), but the mechanism chosen is a fork:
usage.py:163-189 _raw_tokens_for_mcp_servers↔_history.py:163-201 _estimate_mcp_tool_tokens— byte-identical traversal of_cached_tools/tool_prefix/ name / description /inputSchema(jscpd: usage.py:163-176 ↔ _history.py:176-189). Only the leaf call differs (_raw_estimate_tokensvsestimate_tokens).usage.py:127-159 _raw_tokens_for_pydantic_tools↔ the corresponding loop in_history.estimate_context_overhead— the docstring even says "Mirrors_history.estimate_context_overhead". It already imports_extract_tool_description/_extract_tool_json_schema/stringify_partfrom_history, so the fork is partial — the walkers are cloned, the leaf helpers aren't.usage.py:103-112 _raw_estimate_tokensclones thelen(text)/2.5heuristic with a comment admitting it "mirrors the original_history.estimate_tokensbefore any plugin patches it".
Why it matters
When MCP tool serialization changes (e.g. pydantic-ai renames _cached_tools, or tools gain output schemas), _history.py gets fixed and /context silently drifts — there is no test tying the two walkers together. The traversal logic and the estimator function are two separate concerns that have been entangled: the fork duplicates the traversal just to swap the estimator.
Proposed shared refactor (dependency injection instead of fork)
Parametrize the canonical walkers in _history.py with an estimator callable:
# agents/_history.py
def estimate_mcp_tool_tokens(mcp_servers, *, estimator: Callable[[str], int] = None) -> int:
est = estimator or estimate_tokens # patched name resolves at call time
...one traversal, leaf calls go through est()...
def raw_estimate_tokens(text: str) -> int:
"""Unpatched char/2.5 heuristic. token_ratio_learner must NOT patch this."""
Then context_indicator/usage.py becomes:
from code_puppy.agents._history import estimate_mcp_tool_tokens, raw_estimate_tokens
total = estimate_mcp_tool_tokens(servers, estimator=raw_estimate_tokens)
One traversal implementation, two estimators, zero drift. The "raw" estimator living in _history.py under an explicit do-not-patch name also turns the implicit contract with token_ratio_learner (which today is "hope the plugin only patches estimate_tokens") into an explicit one.
Filed by Zen Reviewer C (code-puppy-60635a) — DRY review round
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading plugins/context_indicator/usage.py:103-189 and the corresponding walkers in code_puppy/agents/_history.py, including the usage.py:6-28 monkeypatch rationale. Trace how token_ratio_learner patches estimate_tokens, then verify the refactor leaves one traversal implementation with an explicitly unpatched estimator and no behavioral drift in /context.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- tooling
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100