diff --git a/src/amplifier_agent_http/_wire.py b/src/amplifier_agent_http/_wire.py index b801e90c..4e032c96 100644 --- a/src/amplifier_agent_http/_wire.py +++ b/src/amplifier_agent_http/_wire.py @@ -180,6 +180,7 @@ def stop_chunk( completion_tokens: int = 0, cached_tokens: int = 0, cost_usd: str | None = None, + total_nano_aiu: int | None = None, include_usage: bool = True, ) -> dict[str, Any]: """Final chunk -- empty delta, finish_reason: stop, optional usage block. @@ -206,6 +207,8 @@ def stop_chunk( cached_tokens=cached_tokens, cost_usd=cost_usd, ) + if total_nano_aiu is not None: + chunk["copilot_usage"] = {"total_nano_aiu": total_nano_aiu} return chunk @@ -262,6 +265,7 @@ def tool_calls_stop_chunk( completion_tokens: int = 0, cached_tokens: int = 0, cost_usd: str | None = None, + total_nano_aiu: int | None = None, include_usage: bool = True, ) -> dict[str, Any]: """Terminal chunk for a turn that ends with host-delegated tool calls. @@ -287,6 +291,8 @@ def tool_calls_stop_chunk( cached_tokens=cached_tokens, cost_usd=cost_usd, ) + if total_nano_aiu is not None: + chunk["copilot_usage"] = {"total_nano_aiu": total_nano_aiu} return chunk diff --git a/src/amplifier_agent_http/routes/chat_completions.py b/src/amplifier_agent_http/routes/chat_completions.py index 8ef1d2c3..a5c18b0e 100644 --- a/src/amplifier_agent_http/routes/chat_completions.py +++ b/src/amplifier_agent_http/routes/chat_completions.py @@ -26,7 +26,7 @@ import logging import time from collections.abc import AsyncGenerator -from decimal import Decimal, InvalidOperation +from decimal import ROUND_HALF_UP, Decimal, InvalidOperation from typing import Any from fastapi import APIRouter, Depends, HTTPException, Request, status @@ -427,6 +427,14 @@ async def _stream_chat_completion( # runs the tool host-side, then re-POSTs. # - "stop" for the normal end-of-turn path (with or without text). cost_str_final: str | None = str(usage_cost) if usage_cost is not None else None + # opencode reads a top-level copilot_usage.total_nano_aiu and overrides its + # own token x rate cost estimate with total_nano_aiu / 1e11, so emitting + # round(cost_usd * 1e11) makes its displayed cost exactly cost_usd. + total_nano_aiu: int | None = ( + int((usage_cost * Decimal(10**11)).to_integral_value(rounding=ROUND_HALF_UP)) + if usage_cost is not None + else None + ) if finish_reason_tool_calls: yield sse_data( tool_calls_stop_chunk( @@ -436,6 +444,7 @@ async def _stream_chat_completion( completion_tokens=usage_completion, cached_tokens=usage_cached, cost_usd=cost_str_final, + total_nano_aiu=total_nano_aiu, include_usage=True, ) ) @@ -448,6 +457,7 @@ async def _stream_chat_completion( completion_tokens=usage_completion, cached_tokens=usage_cached, cost_usd=cost_str_final, + total_nano_aiu=total_nano_aiu, include_usage=True, ) )