Mirror the TokensUsageRecord wire contract on RunResults#8
Conversation
Both paths populate them: the hosted durable path reads the pair
GET /v1/runs/{id}/results now returns (unpacked from the runner's
tokens_usages.json artifact — pipelex-platform PR #82), and the blocking
fallback unpacks the same pair from the execute response's
extension-open pipe_output, so result.tokens_usages reads the same
regardless of which path ran. None against older platforms or
pre-artifact runs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`RunResults.tokens_usages` was a `list[dict[str, Any]]` bag. It now validates into `TokensUsageRecord`, a client-side mirror of the wire contract specified in docs/specs/pipelex-mthds-protocol.md — every field optional and `extra="allow"`, so pre-contract durable artifacts (relayed verbatim, never migrated) still parse with `cost`/`pipe_code` None and their legacy `job_metadata`/`unit_costs` kept in `model_extra`. Enum-ish fields stay plain `str` so runtime enum churn is non-breaking. Both paths feed the same typed records: the durable path off the results body, the blocking path lifted out of the execute response's extension-open `pipe_output`. Breaking: `RunResults.pipe_output` is now `DictPipeOutputAbstract | None` rather than `dict[str, Any] | None`. The blocking path already parsed the protocol model and then discarded the types via `.model_dump()`; it now carries the parsed model through, so consumers read the working memory as attributes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR mirrors the run-usage wire contract in the Python SDK. The main changes are:
Confidence Score: 5/5This looks safe to merge. No blocking issues found in the changed code. Both result paths validate usage records into the same model. Legacy fields remain available through allowed extensions. Tests cover the documented compatibility and null semantics.
What T-Rex did
Important Files Changed
Reviews (1): Last reviewed commit: "feat(runs)!: mirror the TokensUsageRecor..." | Re-trigger Greptile |
There was a problem hiding this comment.
No issues found across 8 files
You’re at about 97% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.
Types the run-usage payload against the wire contract specified in
docs/specs/pipelex-mthds-protocol.md→ TokensUsage records on run artifacts. Server side shipped inpipelex0.40.0 /pipelex-api0.10.0 and has been live on hosted dev since 2026-07-20; this is the client mirror.What changed
TokensUsageRecord(pipelex_sdk/runs.py) — one record per inference call.RunResults.tokens_usageswas alist[dict[str, Any]]bag; it now validates into these. Token counts by category, the server-computedcostin USD, model name/id, the pipe that made the call, job-kind fields and timing.extra="allow", because durable artifacts written before the contract shipped are relayed verbatim and never migrated: such a record comes back withcost/pipe_codeasNoneand its legacyjob_metadata/unit_costsinmodel_extra.str.model_type,job_category,unit_job_idare open sets on the wire per the spec — deliberately notStrEnum, so runtime enum churn is non-breaking for consumers.pipe_output.docs/run-usage.md(field reference, cost/null semantics, old-artifact rules), cross-linked fromdocs/architecture.md.Note the rate table (
unit_costs) no longer crosses the wire — a record carries the computedcostinstead,Nonewhen the model has no rate table at all and0when one priced the call at zero.Breaking
RunResults.pipe_outputis nowDictPipeOutputAbstract | None, wasdict[str, Any] | None. The blocking path already parsed the protocol model and then discarded the types via.model_dump(); it now carries the parsed model through. Read the working memory as attributes —result.pipe_output.working_memory.root["out"].content— rather than nested dict keys. The durable path still leaves itNone.Per the repo convention the version is untouched: the
releaseskill owns the bump and the[Unreleased]→[vX.Y.Z]conversion. The breaking entry sits under### Changed, which steers that release to a minor.Test plan
make agent-check— pyright + mypy clean.make agent-test— green. New coverage intests/unit/test_runs.py(every contract field, pre-contract tolerance, unrated-costnull vs priced-at-zero, the three null branches), plus the durable and blocking client tests updated to assert typed records.Downstream
⚠ The
conformanceSDK-mirror PR must merge after this one. Its CI installs this package atref: dev, so merging it first would install a build withoutTokensUsageRecordand turn that job red.🤖 Generated with Claude Code
Summary by cubic
Types
RunResults.tokens_usagesagainst the wireTokensUsageRecordand exposes usage consistently on both hosted and blocking paths. Also switchesRunResults.pipe_outputto the typedDictPipeOutputAbstract.New Features
TokensUsageRecordandRunResults.usage_assembly_error;RunResults.tokens_usagesnow validates intolist[TokensUsageRecord] | None.tokens_usages.jsonor blockingpipe_outputextras; server-computedcostis included (None= no rate table,0= priced zero). No run-level aggregate; sum records.tokens_usagesisNonewhen usage is off/broke/pre-artifact and[]when no inference ran. Useusage_assembly_errorto tell failures. Docs:docs/run-usage.md.Migration
RunResults.pipe_outputis nowDictPipeOutputAbstract | None(wasdict[str, Any] | None). Read working memory as attributes, e.g.result.pipe_output.working_memory.root["out"].content.pipelex0.40.0 /pipelex-api0.10.0. Adjust any code that depended on a dict-shapedpipe_output.Written for commit a0418ef. Summary will update on new commits.