Skip to content

Refactor/sync command closures#8

Merged
PTFOPlayer merged 4 commits into
masterfrom
refactor/sync-command-closures
May 18, 2026
Merged

Refactor/sync command closures#8
PTFOPlayer merged 4 commits into
masterfrom
refactor/sync-command-closures

Conversation

@PTFOPlayer
Copy link
Copy Markdown
Owner

Merge pull request: Refactor command architecture + token tracking fixes

High-level:
Replace the monolithic Command enum with a trait-based CommandRegistry supporting lightweight sync closures. Convert all remaining async commands to async_command! macro. Fix two bugs: JSON numeric tool argument parsing and context-token undercounting.

Changes (refactor/sync-command-closures, 4 commits, -231 net lines):

  1. a089f37 — Replace Command enum with trait-based registry + sync closures

    • Introduces Command trait, SyncCommand<F> wrapper, AliasCommand, and CommandRegistry in commands/registry.rs
    • Extracts CommandContext from the old CommandDispatcher
    • Converts all 18 sync commands from struct + impl Command to closures registered via register_sync() / register_sync_with_usage()
    • 6 async commands kept as impl Command, later converted in the next commit
    • Eliminates ~18 struct definitions and their impl Command boilerplate
    • Net -231 lines (1020 removed, 789 added); 87 tests pass, clippy clean
  2. 69561db — Convert remaining async commands to async_command! macro

    • Converts CompactCommand, TimeoutCommand, RetriesCommand, ModelsCommand, InitCommand, ConfigSettingsCommand
    • Eliminates ~25 lines of boilerplate per async command
    • Net -105 lines (249 removed, 144 added)
  3. e755f76 — Use Ollama prompt_eval_count for accurate context token tracking

    • Adjusts heuristic constants to better match real tokenizer behavior for tool output messages (Role::Tool was being undercounted)
    • Captures ground-truth prompt_eval_count from Ollama's final_data response, caching it alongside the message count at capture time
    • Falls back to heuristic only when messages have changed; invalidates cache after slash commands that mutate messages
  4. 03aef90 — Handle JSON numeric tool arguments (timeout, etc.)

    • execute_tool_call used v.as_str() which returned None for JSON number values, silently producing "" that fell through parse::<u64>() to defaults
    • Fixes by matching on Value::String vs everything else, using to_string() for non-string values (numbers, booleans)

Replace the monolithic Command enum and CommandDispatcher with a
trait-based CommandRegistry that supports both async Command impls
and lightweight SyncCommand closures.

Architecture:
- Add Command trait, SyncCommand<F> wrapper, AliasCommand, and
  CommandRegistry to registry.rs
- Extract CommandContext from the old CommandDispatcher
- 18 sync commands converted from struct+impl Command to closures
  registered via register_sync()/register_sync_with_usage()
- 6 async commands kept as impl Command (need provider.lock().await):
  CompactCommand, InitCommand, ModelsCommand, ModelCommand,
  TimeoutCommand, RetriesCommand

Benefits:
- Eliminates ~18 struct definitions and impl Command blocks
- Command registration is declarative and centralized in build_registry()
- Sync commands no longer need Pin<Box<dyn Future>> boilerplate
- Individual command files are simpler (just logic functions)
- Net -231 lines across the codebase (789 added, 1020 removed)

All 87 tests pass. Clippy clean. Formatted with cargo fmt.
- Convert CompactCommand, TimeoutCommand, RetriesCommand, ModelsCommand,
  InitCommand, and ConfigSettingsCommand to use the async_command! macro
- Eliminates ~25 lines of boilerplate per async command
- Net -105 lines across 6 files (144 additions, 249 deletions)
- All 87 tests passing, clean clippy
- Adjust heuristic constants (chars/token 4.0→3.2, tokens/word 1.3→1.6,
  overhead 2→4) to better match real tokenizer behavior for tool outputs
- Capture ground-truth prompt_eval_count from Ollama's final_data on every
  chat call, falling back to heuristic only when message count has changed
- Invalidate cached count after slash commands that may mutate messages
The execute_tool_call conversion from serde_json::Value to
HashMap<String, String> used v.as_str() which only works for JSON
string values. When the LLM emits numeric params like
"timeout": 60000 (common with many models), as_str() returned
None, producing "" which silently failed parse::<u64>() and fell
back to the 30000 default.

Fixed by matching on Value::String vs everything else, using
to_string() for non-string values (Number, Bool, etc.).
@PTFOPlayer PTFOPlayer merged commit 519ae85 into master May 18, 2026
4 checks passed
PTFOPlayer added a commit that referenced this pull request May 19, 2026
Merge pull request #8 from PTFOPlayer/refactor/sync-command-closures
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant