Files
wehub-resource-sync 4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

146 lines
4.3 KiB
Python

"""Tests for interactive-shell session token accounting."""
from __future__ import annotations
import io
import time
from collections.abc import Iterator
from typing import Any
from rich.console import Console
from core.agent_harness.accounting.token_accounting import (
build_llm_run_info,
estimate_tokens,
format_token_total,
record_llm_turn,
)
from surfaces.interactive_shell.runtime.answer_turn import answer_shell_question
from surfaces.interactive_shell.session import Session
from surfaces.interactive_shell.ui.streaming import _CHARS_PER_TOKEN
def test_estimate_tokens_uses_chars_per_token_ratio() -> None:
text = "x" * (8 * _CHARS_PER_TOKEN)
assert estimate_tokens(text) == 8
def test_record_token_usage_accumulates_on_session() -> None:
session = Session()
record_llm_turn(session, prompt="a" * 40, response="b" * 20)
assert session.tokens.totals == {
"input": 10,
"output": 5,
"input_estimated": 10,
"output_estimated": 5,
}
assert session.tokens.call_count == 1
assert session.tokens.has_estimates is True
record_llm_turn(session, prompt="c" * 8, response="d" * 4)
assert session.tokens.totals == {
"input": 12,
"output": 6,
"input_estimated": 12,
"output_estimated": 6,
}
assert session.tokens.call_count == 2
def test_record_llm_turn_uses_provider_counts_when_present() -> None:
session = Session()
inp, out, estimated = record_llm_turn(
session,
prompt="ignored when counts supplied",
response="also ignored",
input_tokens=1200,
output_tokens=80,
)
assert (inp, out, estimated) == (1200, 80, False)
assert session.tokens.totals == {
"input": 1200,
"output": 80,
"input_measured": 1200,
"output_measured": 80,
}
assert session.tokens.has_estimates is False
def test_format_token_total_shows_mixed_breakdown() -> None:
session = Session()
record_llm_turn(
session,
prompt="x",
response="y",
input_tokens=300,
output_tokens=40,
)
record_llm_turn(session, prompt="a" * 400, response="b" * 80)
assert format_token_total(session, direction="input") == (
"input tokens",
"400 (300 provider + 100 est.)",
)
assert format_token_total(session, direction="output") == (
"output tokens",
"60 (40 provider + 20 est.)",
)
def test_build_llm_run_info_records_tokens_and_metadata() -> None:
session = Session()
class _Client:
_model = "claude-test"
_provider_label = "Anthropic"
run = build_llm_run_info(
session=session,
prompt="a" * 40,
response_text="b" * 20,
client=_Client(),
started=time.monotonic() - 0.05,
)
assert run.model == "claude-test"
assert run.provider == "anthropic"
assert run.input_tokens == 10
assert run.output_tokens == 5
assert run.response_text == "b" * 20
assert run.latency_ms is not None and run.latency_ms >= 0
def test_coerce_usage_tokens_accepts_float_counts() -> None:
from core.llm.shared.usage import coerce_usage_tokens
assert coerce_usage_tokens(
{"input_tokens": 512.0, "output_tokens": 64.0},
input_key="input_tokens",
output_key="output_tokens",
) == (512, 64)
def test_record_token_usage_skips_zero_counts() -> None:
session = Session()
session.tokens.record()
assert session.tokens.totals == {}
assert session.tokens.call_count == 0
class _FakeLLMClient:
def __init__(self, content: str) -> None:
self._content = content
self.last_prompt: str | None = None
def invoke_stream(self, prompt: str) -> Iterator[str]:
self.last_prompt = prompt
yield self._content
def test_answer_shell_question_records_session_token_usage(monkeypatch: Any) -> None:
client = _FakeLLMClient("assistant reply")
monkeypatch.setattr("core.llm.factory.get_llm", lambda _role: client)
session = Session()
console = Console(file=io.StringIO(), force_terminal=False)
answer_shell_question("hello", session, console)
assert session.tokens.totals["input"] > 0
assert session.tokens.totals["output"] == estimate_tokens("assistant reply")
assert session.tokens.has_estimates is True