Carry targeted moderation decisions on final-answer messages (#59842)

## What changed

Read `final_answer_moderation` metadata and attach `moderation: { moderated: boolean }` to the completed final-answer message only when its item ID matches. Apply this in both normal and plan modes, and reset the pending decision when a new response starts.

Preserve the answer's native item ID and moderation flag through legacy events and history reconstruction. Combine text parts into one legacy event when a moderation decision is present.

Expose nullable moderation data on app-server agent messages and update the JSON schemas, TypeScript types, and Python models.

## Testing

Add an integration test for moderation on completed answers and their legacy events. Extend protocol conversion tests to cover populated moderation data and serialization as `null` when absent.

GitOrigin-RevId: f286b67731a84b3daf5d10b7644f2e31b62f0e53
This commit is contained in:
psachdeva-oai
2026-11-06 07:43:44 +00:00
committed by copyberry
parent 55897425e8
commit ddd1812a51
98 changed files with 802 additions and 39 deletions
@@ -154,6 +154,13 @@ class AgentMessageInputContent(
root: InputTextAgentMessageInputContent | EncryptedContentAgentMessageInputContent
class AgentMessageModeration(BaseModel):
model_config = ConfigDict(
populate_by_name=True,
)
moderated: bool
class AgentPath(RootModel[str]):
root: str
@@ -10715,6 +10722,7 @@ class AgentMessageThreadItem(BaseModel):
delivery: AgentMessageDelivery | None = None
id: str
memory_citation: Annotated[MemoryCitation | None, Field(alias="memoryCitation")] = None
moderation: AgentMessageModeration | None = None
phase: MessagePhase | None = None
questions: list[AsyncUserInputQuestion] | None = None
started_at_ms: Annotated[