[BUG](foundation-api): Parse search usage records - #7524
Conversation
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
Reviewer ChecklistPlease leverage this checklist to ensure your code review is thorough before approving Testing, Bugs, Errors, Logs, Documentation
System Compatibility
Quality
|
f47cbcf to
d2ab86e
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f47cbcf880
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| #[serde(default)] | ||
| usage_records: Vec<UsageData>, | ||
| #[serde(default)] | ||
| model: Option<String>, |
There was a problem hiding this comment.
Reject payloads that match neither usage shape
When upstream sends a malformed object such as {"usage_record":[...]} or {}, every field here receives a default, so deserialization succeeds, into_records returns an empty vector, and the warning branch in AgentEvent::parse never runs. This silently drops metering precisely when schema drift should be reported; require either a usage_records field or the legacy model shape instead of defaulting both alternatives.
Useful? React with 👍 / 👎.
| Some("usage") => match from_data::<UsageEventData>(data()) { | ||
| Some(usage) => AgentEvent::Usage(usage.into_records()), | ||
| None => { | ||
| tracing::warn!( | ||
| payload = %data(), | ||
| "dropping malformed search-agent usage event" | ||
| ); | ||
| AgentEvent::Unknown | ||
| } | ||
| }, |
There was a problem hiding this comment.
Breaking backward compatibility: The PR description claims to "preserve legacy single-model usage payload support", but this implementation only parses the new usage_records envelope format. Old payloads with direct fields {"model":"...", "input_tokens":..., "output_tokens":...} will fail to deserialize as UsageEventData (which requires usage_records field), trigger the warning, and be dropped as Unknown.
This contradicts the stated goal and will cause production usage data loss for any search agents still emitting the old format.
To fix, implement fallback parsing:
Some("usage") => {
// Try new format first
if let Some(usage) = from_data::<UsageEventData>(data()) {
AgentEvent::Usage(usage.into_records())
}
// Fallback to legacy single-model format
else if let Some(single_usage) = from_data::<UsageData>(data()) {
AgentEvent::Usage(vec![single_usage])
}
else {
tracing::warn!(
payload = %data(),
"dropping malformed search-agent usage event"
);
AgentEvent::Unknown
}
}| Some("usage") => match from_data::<UsageEventData>(data()) { | |
| Some(usage) => AgentEvent::Usage(usage.into_records()), | |
| None => { | |
| tracing::warn!( | |
| payload = %data(), | |
| "dropping malformed search-agent usage event" | |
| ); | |
| AgentEvent::Unknown | |
| } | |
| }, | |
| Some("usage") => { | |
| if let Some(usage) = from_data::<UsageEventData>(data()) { | |
| AgentEvent::Usage(usage.into_records()) | |
| } else if let Some(single_usage) = from_data::<UsageData>(data()) { | |
| AgentEvent::Usage(vec![single_usage]) | |
| } else { | |
| tracing::warn!( | |
| payload = %data(), | |
| "dropping malformed search-agent usage event" | |
| ); | |
| AgentEvent::Unknown | |
| } | |
| } | |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
There was a problem hiding this comment.
backwards compat probably not necessary though?
Summary
usage_recordsenvelope emitted by search-agent-researchTesting
cargo fmtcargo test -p foundation-api routes::subagent_search::tests:: -- --nocapture(started; local Cargo artifact-lock contention prevented capturing completion before PR creation)