feat: report input and output token estimates on tool-call events - #54
Merged
Merged
Conversation
… review Drop json.dumps's default=str so an unserializable argument is omitted rather than counted (TypeScript and Go already omit it), and encode with surrogatepass so a lone surrogate off the wire counts its 3 bytes instead of dropping the whole field. Add coverage for both, add a truncation leg to the tools/call ordering test, and note the two Go-only edge cases (empty-object counting, isError responses) plus serialization corner cases in the cross-SDK brief.
naji247
marked this pull request as ready for review
September 22, 2026 19:30
kashishhora
approved these changes
Sep 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Tool-call events now carry
input_tokensandoutput_tokens: estimates of how many tokens the calling model spent on the tool arguments and how many it reads back from the tool result. AgentCat uses them for the oversized-tool-response rule and for per-tool token trends, and until now the server estimated both from the stored, redacted event payload. The SDK is the one place where the raw strings exist, so the estimate now happens there.This is a patch release:
agentcat2.1.1 on PyPI.What changes for you
mcp:tools/callevent includesinput_tokensandoutput_tokens. No configuration is needed and no option changes.context,session_id, andagent_id), and the output side counts the text in the result'scontentblocks, or the structured content when a result has no content blocks. The tool name, transport headers,isError, and the session mint-back text never count, andstructuredContentdoes not count when text blocks are present.redact_sensitive_informationandredact_eventhooks run and before sanitization and truncation, so they describe the original size even when the stored strings are redacted or truncated. If that size must not leave your server, drop the two fields inredact_event.How the estimate works
ceil(utf8_bytes / 3.5), clamped to the server's integer column. The divisor comes from a measurement of 1,240 production tool responses: the text a model reads runs 3.71 bytes per token on OpenAI-family tokenizers and roughly 3.15 on Anthropic-family tokenizers, so 3.5 splits the difference. The same rules and the same test vectors are pinned across the TypeScript, Python, and Go SDKs, so all three report identical numbers for identical payloads.Dependency
Requires
agentcat-api1.0.2, which adds the two fields to the publish request. This branch pins that version. The suites pass on both the modern and the legacymcpgeneration against it.Testing
tests/test_token_estimate.py: the shared vector table (ASCII, accented and CJK text, HTML characters, image-only content, structured-only results, resource blocks, the fallback for results without acontentlist, the int32 clamp, and the never-throw guards).tests/test_token_estimates_integration.py: real tool calls through every supported server flavor and MCP generation, asserting the counts on captured events, the error-result path, that redaction and truncation leave the counts unchanged, and that a broken estimator never breaks the tool call.mcpgeneration.