feat: expose operation usage metadata - #2530
Merged
Merged
Conversation
|
shrey150
marked this pull request as ready for review
July 30, 2026 22:58
Contributor
There was a problem hiding this comment.
cubic analysis
No issues found across 16 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Linked issue analysis
Linked issue: STG-2670: figure out what we are doing with .metrics & token tracking
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Add optional per-operation usage metadata to act/observe/extract results with fields inputTokens, outputTokens, reasoningTokens, cachedInputTokens, inferenceTimeMs | Schema, docs, and runtime all add the usage object and the specified fields; tests assert presence/shape in SDK and server results. |
| ✅ | Populate usage from real model inference and aggregate multi-call operations (e.g., two-step actions) | Server code converts inference responses into usage, aggregates multi-call usage, and tests verify aggregation and numeric totals. |
| ✅ | Omit usage metadata when an operation did not run inference (deterministic actions / cache hits) | Runtime preserves omission for operations without inference and tests assert usage is undefined for cache hits or purely deterministic actions. |
| ✅ | Ensure JSON-RPC wire uses snake_case and SDK surfaces camelCase fields (round-trip casing + verify through browser runtime) | Wire-casing tests and browser-runtime smoke test capture raw JSON-RPC messages and assert snake_case on the wire and camelCase in SDK results; test instruments transport to check raw messages. |
| ✅ | Regenerate canonical JSON Schema and update generated Python and Go models to include the usage shape | The canonical JSON schema and generated model files were updated to include StagehandResultUsage and the checked-in generated models reflect the new fields. |
Architecture diagram
sequenceDiagram
participant Client as Client SDK
participant Server as RPC Server
participant ActSvc as Act Service
participant LLM as LLM Service
participant Cache as Cache Store
Note over Client,Cache: 🆕 Per-operation LLM usage metadata
Client->>Server: act({ instruction, page })
Server->>ActSvc: act(params)
alt instruction is Action (deterministic)
ActSvc->>ActSvc: performAction(deterministic)
Note over ActSvc: No LLM inference
ActSvc-->>Server: { data: { success, ... }, metadata: {} }
else instruction is a string
ActSvc->>Cache: getCachedActions(instruction)
alt Cache HIT
Cache-->>ActSvc: cached actions
Note over ActSvc: No LLM inference
ActSvc-->>Server: { data: { ... }, metadata: {} }
else Cache MISS
ActSvc->>LLM: generate(instruction, model)
LLM-->>ActSvc: { action, twoStep, usage: { prompt_tokens, ... } }
ActSvc->>ActSvc: aggregateUsage(initial)
alt twoStep is true
ActSvc->>LLM: generate(second_action)
LLM-->>ActSvc: { action, usage: { ... } }
ActSvc->>ActSvc: aggregateUsage(additional)
end
ActSvc->>ActSvc: performAction()
ActSvc->>Cache: setCachedActions(actions)
ActSvc-->>Server: { data: { ... }, metadata: { usage: { inputTokens, ... } } }
end
end
Note over Server,Client: JSON-RPC transport converts to snake_case (input_tokens, etc.)
Server-->>Client: act result (with optional usage)
Client->>Client: decode to camelCase
shrey150
force-pushed
the
shrey/stg-2670-json-rpc-usage
branch
from
July 31, 2026 06:41
a5d1d8f to
c1c2f8b
Compare
shrey150
force-pushed
the
shrey/stg-2670-json-rpc-usage
branch
from
July 31, 2026 20:18
c1c2f8b to
8a4f4f3
Compare
monadoid
reviewed
Aug 2, 2026
monadoid
approved these changes
Aug 3, 2026
shrey150
added a commit
that referenced
this pull request
Aug 3, 2026
## Summary - add a runtime-scoped metrics accumulator for `act`, `observe`, and `extract` - record each returned operation's already-aggregated `metadata.usage` exactly once - implement the existing `stagehand.metrics()` JSON-RPC method as a detached session snapshot - preserve V3-style per-method and overall prompt, completion, reasoning, cached-input, and inference-time totals ## Semantics Each Stagehand instance starts with zeroed metrics. Every successful operation returns a complete `metadata.usage` object. Deterministic actions and cache hits return zero-valued usage, so recording them leaves the counters unchanged. Calls that throw before returning a result are not recorded. Reading `stagehand.metrics()` does not mutate or reset the accumulated values. This PR is stacked on [#2530](#2530), which introduces and populates the per-operation `metadata.usage` values consumed here. ## E2E Test Matrix | Command / flow | Observed output | Confidence / sufficiency | | --- | --- | --- | | `corepack pnpm exec turbo run build --force` | 4 build tasks completed successfully with cache bypassed; the protocol, server extension, TypeScript SDK, and eval package outputs were rebuilt | Proves the exact combined source builds without relying on a stale cached extension artifact. | | `pnpm exec vitest run packages/sdk-ts/tests/browser-runtime/stagehand-launch-connect-smoke.test.ts` | 1 browser-runtime file and 14 tests passed against headless Chrome | Proves the public SDK reaches the merged server through the real extension/service-worker transport, deterministic actions return complete zero usage, inferred operations update session metrics exactly once, and repeated snapshots are read-only. The model adapter is deterministic rather than a live provider. | | `pnpm exec vitest run packages/server/tests/metrics.test.ts packages/server/tests/rpc-router.test.ts` | 2 files and 8 tests passed | Proves zero initialization, per-method and total arithmetic, zero-valued usage leaves counters unchanged, snapshots are detached, and the metrics RPC routes successfully. | | `pnpm --filter ./packages/server test` | Server build passed; 24 files passed with 183 tests passing and 11 existing TODOs | Covers the full server unit suite around the changed controller, runtime, and accumulator behavior. | | `pnpm check` | 8 tasks completed successfully | Covers formatting, lint, type checking, package builds, and documentation validation. | | `go -C packages/sdk-go run ./internal/extensionpack --check` | Exited successfully with no output | Proves the committed Go extension archive exactly matches the rebuilt server extension. | ## Scope This preserves the existing public `StagehandMetrics` result shape and SDK methods; it only implements the previously stubbed server behavior.
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
act,observe, andextractresults0, so deterministic actions and cache hits return a complete zero-valued aggregate instead of omitting usageResult shape
The raw JSON-RPC response uses
input_tokens,output_tokens,reasoning_tokens,cached_input_tokens, andinference_time_ms. All five fields are always present. Operations that do not run inference return0for all five fields.Usage is scoped to one operation and aggregates every model call made by that operation. Session-wide aggregation through
stagehand.metrics()is intentionally outside this PR.E2E Test Matrix
extract→observe→actagainsthttps://example.commetadata.usage; extract returned811input /237output tokens, observe returned680/376, and act returned1149/313and navigated to the expected public destinationnode_modules/.bin/vitest run --root . packages/sdk-ts/tests/browser-runtime/stagehand-launch-connect-smoke.test.tsactreturned five zero-valued camelCase SDK fields and five zero-valued snake_case wire fields<Corepack PATH> node_modules/.bin/turbo run test:unitnode_modules/.bin/turbo run build && node_modules/.bin/turbo run fmt:check lint typecheckuv --directory packages/sdk-python run --locked pytestplus Ruff, Ty, and generator checksgo list ./...package tests plus generator tests/checkFollow-up
A stacked PR will implement the existing
stagehand.metrics()RPC using V3-style session totals built from these per-operation usage values.