diff --git a/symbiont/dispatcher.py b/symbiont/dispatcher.py index b39c322..40c50b2 100644 --- a/symbiont/dispatcher.py +++ b/symbiont/dispatcher.py @@ -113,39 +113,43 @@ def dispatch( "--print", # non-interactive, just print the response "--model", model_flag, "--output-format", "json", - "--dangerously-skip-permissions", # full access, no prompts ] if system_prompt: cmd.extend(["--system-prompt", system_prompt]) - # Prompt is a positional argument in Claude Code CLI - cmd.append(prompt) - start = time.monotonic() try: + # Pipe the prompt via stdin to avoid shell escaping issues result = subprocess.run( cmd, + input=prompt, capture_output=True, text=True, timeout=timeout_seconds, ) elapsed = time.monotonic() - start - # Try to parse JSON output for token counts + # Parse the rich JSON output from Claude Code CLI output_text = result.stdout.strip() input_tokens = 0 output_tokens = 0 + actual_cost_usd = 0.0 try: parsed = json.loads(output_text) - # Claude Code JSON output includes result and usage if isinstance(parsed, dict): output_text = parsed.get("result", output_text) + # Claude Code gives us exact usage data usage = parsed.get("usage", {}) input_tokens = usage.get("input_tokens", 0) output_tokens = usage.get("output_tokens", 0) + # Real cost from the CLI (even on Pro, this tracks the equivalent) + actual_cost_usd = parsed.get("total_cost_usd", 0.0) + # Also capture cache stats for efficiency tracking + cache_read = usage.get("cache_read_input_tokens", 0) + cache_create = usage.get("cache_creation_input_tokens", 0) except json.JSONDecodeError: pass # Plain text output, that's fine @@ -171,12 +175,15 @@ def dispatch( error=f"Exit code {result.returncode}: {stderr}", ) - # Estimate cost for the ledger - costs = MODEL_COSTS[tier] - estimated_cost = ( - (input_tokens / 1_000_000) * costs["input"] - + (output_tokens / 1_000_000) * costs["output"] - ) + # Use real cost from CLI if available, otherwise estimate + if actual_cost_usd > 0: + estimated_cost = actual_cost_usd + else: + costs = MODEL_COSTS[tier] + estimated_cost = ( + (input_tokens / 1_000_000) * costs["input"] + + (output_tokens / 1_000_000) * costs["output"] + ) dispatch_result = DispatchResult( success=True,