Repository navigation
Normalize unified agent session metadata and AIC accounting - #65344
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
@copilot update go parser |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Canonical-field precedence, checkpoint preservation, and unified usage rendering remain incorrect in several paths.
Review effort: Balanced
Findings: 4
Open (4)
What changed in this PR
Normalizes unified-session metadata and usage fields while adding phase-specific AIC resolution and reader compatibility.
Changes:
- Standardizes known payloads on camelCase and expands workflow metadata.
- Estimates detection/eval AIC only when explicit accounting is absent.
- Updates usage rendering, reconciliation, tests, documentation, and embedded assets.
| File | Description |
|---|---|
docs/src/content/docs/specs/unified-agent-session-specification.md |
Documents normalized metadata and accounting. |
actions/setup/session_parsers.go |
Embeds model-pricing resources. |
actions/setup/js/unified_session.test.cjs |
Tests metadata and phase AIC behavior. |
actions/setup/js/unified_session.cjs |
Passes source phase into normalization. |
actions/setup/js/unified_session_render.cjs |
Renders normalized field names. |
actions/setup/js/unified_session_payload.test.cjs |
Tests payload normalization and overflow handling. |
actions/setup/js/unified_session_payload.cjs |
Implements normalization and AIC resolution. |
actions/setup/js/log_parser_shared.cjs |
Normalizes usage before detailed rendering. |
actions/setup/js/agent_session.cjs |
Adds camelCase reader compatibility. |
.github/workflows/smoke-codex-auto.lock.yml |
Regenerates setup context propagation. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| const totalTokens = usage.totalTokens ?? usage.total_tokens; | ||
| const inputTokens = usage.inputTokens ?? usage.input_tokens; | ||
| const outputTokens = usage.outputTokens ?? usage.output_tokens; | ||
| if (isTokenCount(totalTokens)) return totalTokens; | ||
| if (!isTokenCount(inputTokens) && !isTokenCount(outputTokens)) return undefined; | ||
| const cache = | ||
| (usage.inputTokensIncludeCache ?? usage.input_tokens_include_cache) === false ? (usage.cacheCreationInputTokens ?? usage.cache_creation_input_tokens ?? 0) + (usage.cacheReadInputTokens ?? usage.cache_read_input_tokens ?? 0) : 0; | ||
| const total = (inputTokens ?? 0) + (outputTokens ?? 0) + cache; | ||
| return isTokenCount(total) ? total : undefined; |
There was a problem hiding this comment.
Fixed in f5d8d90: usage normalization and total calculation now prefer snake_case fields by own-property presence, including zero and invalid canonical values.
|
|
||
| if (lastEntry.usage) { | ||
| const usage = lastEntry.usage; | ||
| const usage = normalizeSessionUsage(lastEntry.usage) ?? lastEntry.usage; |
There was a problem hiding this comment.
Fixed in f5d8d90: statistics rendering now uses the shared normalizeSessionUsage projection, so camelCase totals, token components, and cache semantics render consistently.
| aic: ["aic", "ai_credits_this_response"], | ||
| totalAic: ["totalAic", "ai_credits_total", "ai_credits"], | ||
| aic: ["aic", "ai_credits_this_response", "aiCreditsThisResponse"], | ||
| totalAic: ["totalAic", "ai_credits_total", "ai_credits", "aiCredits"], |
There was a problem hiding this comment.
Fixed in f5d8d90: aiCreditsTotal is now accepted as an authoritative totalAic alias and suppresses per-request estimation.
|
✅ Test Quality Sentinel completed test quality analysis. Test Quality Sentinel skipped because pre-fetch PR data was unavailable: unable to fetch test file diff
|
|
✅ Ponytail Reviewer completed successfully! Lean already. Ship.
|
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. See the comment below for the result and any generated ADR draft. No ADR enforcement needed: PR does not have the "implementation" label and has ≤100 new lines of code in business logic directories.
|
|
✅ PR Code Quality Reviewer completed the code quality review.
|
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅
|
Comment MemoryNote This comment is managed by comment memory.It stores persistent context for this thread in the code block at the top of this comment.
|
There was a problem hiding this comment.
Request changes
This normalization pass still regresses schema-compatibility rules in a few places, so I don't think it's safe to merge yet.
Blocking themes
- Mixed camelCase/snake_case records still have precedence bugs: alias values can override explicitly present canonical fields.
- The reader-compatibility work is incomplete, so some summaries still render camelCase accounting as partial or otherwise incorrect totals.
usage.reportstill misses at least one established AIC alias (aiCreditsTotal), which drops authoritative checkpoint totals and can trigger the wrong fallback behavior.
🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt54 · 73.9 AIC · ⌖ 9.17 AIC · ⊞ 20.2K
Comment /review to run again
Comments that could not be inline-anchored
actions/setup/js/log_parser_shared.cjs:320
This formatter still reports some camelCase usage objects as partial accounting, so the compatibility fix is incomplete and can silently misstate rendered token totals.
<details><summary>💡 Why this is still wrong</summary>
normalizeSessionUsage() only backfills input_tokens, output_tokens, and the cache token counts. It leaves totalTokens, inputTokensIncludeCache, and reasoningOutputTokens in camelCase only. The code below then decides whether the total is authoritative from `usa…
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Updated the Go JSONL parser to support camelCase usage fields and overflow markers, preserving snake_case precedence. Added regression tests; committed as |
There was a problem hiding this comment.
Verified the four existing automated review comments against the current diff/code; all are accurate and blocking:
agent_session.cjs:39(normalizeSessionUsage) — canonical-field precedence is reversed foroverflowed_tokens/overflowedTokens.agent_session.cjs:348(sessionTokenTotal) — camelCase aliases now win over explicit canonical snake_case values (e.g.{total_tokens: 7, totalTokens: 99}→ 99).log_parser_shared.cjs:316— only the "detailed information" path normalizes usage;log_parser_format.cjs'sappendStatistics(used for step-summary/plain-text rendering) still reads rawusage.input_tokens/cache_read_input_tokensand will silently show nothing for camelCase-only usage records.unified_session_payload.cjs:126(totalAicaliases) — missesaiCreditsTotal, whichai_credits_context.cjs:15(AI_CREDITS_TOTAL_FIELDS) treats as an equally canonical checkpoint field elsewhere in the codebase.
No additional high-signal issues found beyond these. Requesting changes to resolve the alias-precedence and incomplete-normalization bugs above before merge.
🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · copilot · sonnet50 · 67.2 AIC · ⌖ 13.1 AIC · ⊞ 8.1K
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /codebase-design and /tdd to the camelCase normalization and new AIC-resolution logic.
📋 Key Themes & Highlights
Key Themes
- Existing bot review comments are still valid and unresolved: the precedence bug in
normalizeSessionUsage/sessionTokenTotal(agent_session.cjs:39,348) where??makes the newly-accepted camelCase alias win over an explicitly present canonical snake_case value, contradicting the spec's "canonical value wins by presence" rule; theaiCreditsTotalalias gap inunified_session_payload.cjs:126; and the partial normalization inlog_parser_shared.cjsthat doesn't coverappendStatistics/CLI-style summary paths. These should be addressed before merge — they go directly against the behavior guaranteed in the spec's own wording. - AIC phase-gating is implicit:
resolveUsageAicis restricted tophase === "detection" || "evals"via a literal check buried innormalizeUnifiedSessionEvent, rather than a named, co-located constant — makes the coupling between phase and pricing resolution easy to miss when adding a new phase. - New test coverage is good but has gaps: the detection/evals AIC test (
unified_session.test.cjs) covers the "happy path" combinations well but doesn't exercise the guard-clause edge cases inresolveUsageAic(negative/non-finite tokens, cache-only usage with zero input/output tokens).
Positive Highlights
- ✅ Clear, well-documented spec updates (precedence rules, trigger type provenance, version metadata) make the intended behavior easy to verify against.
- ✅ The
overflowed_tokens/overflowedTokensalias reconciliation correctly round-trips through both canonical and camelCase forms without losing entries. - ✅ Good test discipline overall — most new behavior (trigger type, requested model, version fields,
workflow.infoconsolidation) is backed by targeted assertions.
@copilot please address the review comments above.
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet50 · 96 AIC · ⌖ 14.6 AIC · ⊞ 10K
Comment /matt to run again
| if (event.type === "usage.report" && (phase === "detection" || phase === "evals")) resolveUsageAic(source, data, usage); | ||
| } | ||
| if (event.type === "tool.execution_complete" && (source.is_error === true || source.result?.isError === true || source.result?.is_error === true)) data.isError = true; | ||
| if (event.type === "experiment.assignment") { |
There was a problem hiding this comment.
[/codebase-design] resolveUsageAic is gated by a hardcoded phase allowlist (phase === "detection" || phase === "evals") buried inside normalizeUnifiedSessionEvent, far from the EVENT_FIELDS/USAGE_FIELDS tables that define the rest of the accounting shape.
💡 Suggestion
If a future phase (e.g. a new eval/detection source, or conclusion) needs pricing-derived AIC, this silently skips it. Consider hoisting the phase list to a named constant (e.g. AIC_RESOLVABLE_PHASES) next to USAGE_FIELDS so the coupling between phase and pricing resolution is visible and easy to extend/test.
@copilot please address this.
There was a problem hiding this comment.
Fixed in f5d8d90: the detection/evals pricing phases are now named in AIC_RESOLVABLE_PHASES beside USAGE_FIELDS.
| }); | ||
| expect(events.find(event => event.type === "workflow.info").data).not.toHaveProperty("secret"); | ||
| }); | ||
|
|
There was a problem hiding this comment.
[/tdd] New test covers the happy paths for resolveUsageAic (zero tokens, unknown model, explicit ai_credits/ai_credits_this_response) but not the guard-clause edge cases inside the function itself: negative/non-finite token values, and a usage record where only cache tokens are present (cacheReadInputTokens/cacheCreationInputTokens > 0 but inputTokens/outputTokens are 0 or absent).
💡 Suggested additional cases
// cache-only usage should still compute AIC
write("evals/evals_token_usage.jsonl", [
{ provider: "openai", model: "gpt-4o-mini", cache_read_input_tokens: 500 },
]);
// negative/non-finite tokens should be rejected, not silently coerced
write("threat-detection/detection_usage.jsonl", [
{ provider: "openai", model: "gpt-4o-mini", input_tokens: -5 },
]);Without these, a regression in the tokens.some(...)/tokens.every(...) guard in resolveUsageAic (unified_session_payload.cjs:183-184) could silently start producing negative or NaN AIC values without any test failing.
@copilot please address this.
There was a problem hiding this comment.
Added in f5d8d90: tests cover cache-only pricing, negative and non-finite token rejection, and leaving other phases unestimated.
There was a problem hiding this comment.
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
clients2.google.com
To allow these domains, add them to the network.allowed list in your workflow frontmatter:
network:
allowed:
- defaults
- "clients2.google.com"See Network Configuration for more information.
💥 [THE END] — Illustrated by Smoke Claude · claude · sonnet46 · 50.8 AIC · ⌖ 3.91 AIC · ⊞ 853
Comment /smoke-claude to run again
| @@ -2,17 +2,18 @@ | |||
|
|
|||
| /** @typedef {import("./types/agent_session").SessionEvent} SessionEvent */ | |||
| /** @typedef {Record<string, string[]>} Fields */ | |||
There was a problem hiding this comment.
🤖 Smoke test review comment (Run 37138576096): Good use of named imports from model_costs.cjs — this keeps the dependency explicit and tree-shakeable.
There was a problem hiding this comment.
No change was needed in f5d8d90; the import from model_costs.cjs remains a named import.
| @@ -30,13 +30,15 @@ function isMetric(value) { | |||
| function normalizeSessionUsage(usage) { | |||
| if (!usage || typeof usage !== "object" || Array.isArray(usage)) return undefined; | |||
There was a problem hiding this comment.
🤖 Smoke test review comment (Run 37138576096): The normalizeSessionUsage function correctly handles the object spread — consider adding a comment explaining why arrays are explicitly excluded.
There was a problem hiding this comment.
Added a concise explanation in f5d8d90: usage is a named-field object, so arrays cannot represent its token fields.
|
@copilot run pr-finisher skill |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Ran the PR-finisher pass and pushed the review fixes in |
|
🎉 This pull request is included in a new release. Release: |

Unified session records mixed field naming, repeated engine metadata, and lacked a clear way to resolve detection and eval AIC. They also omitted the trigger type and some available version metadata.
✨ PR Review Safe Output Test - Run 37138576096
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
clients2.google.comTo allow these domains, add them to the
network.allowedlist in your workflow frontmatter:See Network Configuration for more information.