# 2795 Tun 1658 Phase6 Messaging Normalization A07396d7

> Summary

- Skill: `tools-only/2795-tun-1658-phase6-messaging-normalization-a07396d7` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add tools-only/2795-tun-1658-phase6-messaging-normalization-a07396d7`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tools-only/2795-tun-1658-phase6-messaging-normalization-a07396d7/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tools-only (https://skillmd.com/u/tools-only)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/tools-only/2795-tun-1658-phase6-messaging-normalization-a07396d7

---


## Summary
Normalized message utilities to operate on tinyagent dict messages (role + content[type]) only. Token estimation and resume sanitization no longer attempt to interpret legacy pydantic-ai message objects, aligning with the “full rip-out” direction.

## Context
During the tun-1658 tinyagent migration, history is persisted as tinyagent dicts:
- assistant: `{role: "assistant", content: [{type: "text"|"thinking"|"tool_call"|"image", ...}]}`
- tool result: `{role: "tool_result", tool_call_id: "...", content: [...]}`

Older utilities still had defensive support for pydantic-ai style `parts`/`part_kind` shapes.

## Root Cause
Utility modules (notably token counting and some sanitize logic) were written to accept polymorphic message inputs and silently “do something” for objects with `.parts`/`.content`. With tinyagent dict messages, this both (a) masked stale session formats and (b) caused unnecessary canonicalization overhead.

## Changes
- `src/tunacode/utils/messaging/token_counter.py`
  - Now supports **only** tinyagent dict messages and `CanonicalMessage`.
  - Token estimation runs via `to_canonical()` + canonical parts, and counts tool-call metadata (`tool_name`, `tool_call_id`, `args`) heuristically.
- `src/tunacode/core/agents/resume/sanitize.py`
  - `_is_request_message()` is now role-based on dict messages (no canonical conversion).
  - Removed `to_canonical` dependency from sanitize (canonicalization remains in `find_dangling_tool_calls()` only).
- `src/tunacode/utils/messaging/adapter.py`
  - Refactored `to_canonical()` / `from_canonical()` into smaller helpers to satisfy ruff complexity constraints (no behavior change intended).
- Tests updated to use tinyagent message shapes:
  - `tests/unit/utils/test_token_counter.py`
  - `tests/unit/test_sanitize_canonicalization.py`

## Behavioral Impact
- Token estimation now fails loudly if non-tinyagent message objects appear in history.
- Resume sanitization no longer performs redundant canonical conversions when scanning for consecutive request messages.
- No user-facing UI rendering changes yet (handled in Phase 7).

## Related Cards
- [[tun-1658-phase5-cleanup]]

