Preserve display instructions in answer moderation (#60172)

## Why

Recovered answer history needs presentation instructions alongside the moderation flag so clients can apply the intended display behavior.

## What changed

- Add optional `display` metadata with an `action` and optional `blockType`, `cyberTrustedAccess`, and `checkTrustedContacts` fields.
- Carry display metadata through final answers, legacy events, thread history reconstruction, and legacy rollout migration.
- Expose the metadata in the app-server protocol and update generated JSON schemas, TypeScript types, and Python SDK models. Missing display fields remain compatible with existing stored moderation data; the app-server emits absent optional values as `null`.

## Testing

Extend the final-answer moderation test to cover display metadata in answer items and legacy events. Add serialization coverage for missing display data, nullable fields, and preservation of an explicit `false` value.

GitOrigin-RevId: b798a2ecc53c2a94c7659ef3d9663b3143b70a1a
This commit is contained in:
psachdeva-oai
2026-11-07 21:31:08 +00:00
committed by copyberry
parent 33b3f80a09
commit 202b590e84
35 changed files with 948 additions and 9 deletions
@@ -177,8 +177,49 @@
],
"type": "object"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -7408,6 +7408,37 @@
"title": "AgentMessageDeltaNotification",
"type": "object"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageInputContent": {
"oneOf": [
{
@@ -7454,6 +7485,16 @@
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/v2/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -523,6 +523,37 @@
"title": "AgentMessageDeltaNotification",
"type": "object"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageInputContent": {
"oneOf": [
{
@@ -569,6 +600,16 @@
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -11,8 +11,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -11,8 +11,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -11,8 +11,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -31,8 +31,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -11,8 +11,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -11,8 +11,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -11,8 +11,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -11,8 +11,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -31,8 +31,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -11,8 +11,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -31,8 +31,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -11,8 +11,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -11,8 +11,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -11,8 +11,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -11,8 +11,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -11,8 +11,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -11,8 +11,49 @@
],
"type": "string"
},
"AgentMessageDisplay": {
"description": "A presentation instruction for Chat to apply to this answer when recovering history.",
"properties": {
"action": {
"description": "Unknown actions must be left for the producing client to handle.",
"type": "string"
},
"blockType": {
"type": [
"string",
"null"
]
},
"checkTrustedContacts": {
"type": [
"boolean",
"null"
]
},
"cyberTrustedAccess": {
"type": [
"boolean",
"null"
]
}
},
"required": [
"action"
],
"type": "object"
},
"AgentMessageModeration": {
"properties": {
"display": {
"anyOf": [
{
"$ref": "#/definitions/AgentMessageDisplay"
},
{
"type": "null"
}
]
},
"moderated": {
"type": "boolean"
}
@@ -0,0 +1,12 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* A presentation instruction for Chat to apply to this answer when recovering history.
*/
export type AgentMessageDisplay = {
/**
* Unknown actions must be left for the producing client to handle.
*/
action: string, blockType: string | null, cyberTrustedAccess: boolean | null, checkTrustedContacts: boolean | null, };
@@ -1,5 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AgentMessageDisplay } from "./AgentMessageDisplay";
export type AgentMessageModeration = { moderated: boolean, };
export type AgentMessageModeration = { moderated: boolean, display: AgentMessageDisplay | null, };
@@ -22,6 +22,7 @@ export type { AdditionalNetworkPermissions } from "./AdditionalNetworkPermission
export type { AdditionalPermissionProfile } from "./AdditionalPermissionProfile";
export type { AgentMessageDelivery } from "./AgentMessageDelivery";
export type { AgentMessageDeltaNotification } from "./AgentMessageDeltaNotification";
export type { AgentMessageDisplay } from "./AgentMessageDisplay";
export type { AgentMessageModeration } from "./AgentMessageModeration";
export type { AllowDenyRequirement } from "./AllowDenyRequirement";
export type { AnalyticsConfig } from "./AnalyticsConfig";
@@ -638,6 +638,7 @@ impl ThreadHistoryBuilder {
.as_ref()
.map(|decision| AgentMessageModeration {
moderated: decision.moderated,
display: decision.display.clone().map(Into::into),
}),
id,
text: payload.message.clone(),
@@ -28,6 +28,7 @@ use codex_protocol::approvals::GuardianAssessmentDecisionSource as CoreGuardianA
use codex_protocol::approvals::GuardianCommandSource as CoreGuardianCommandSource;
use codex_protocol::items::AgentMessageContent as CoreAgentMessageContent;
pub use codex_protocol::items::AgentMessageDelivery;
use codex_protocol::items::AgentMessageDisplay as CoreAgentMessageDisplay;
use codex_protocol::items::AgentMessageModeration as CoreAgentMessageModeration;
pub use codex_protocol::items::AsyncUserInputQuestion;
use codex_protocol::items::CollabAgentTool as CoreCollabAgentTool;
@@ -150,17 +151,42 @@ pub enum CommandAction {
},
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AgentMessageModeration {
pub moderated: bool,
pub display: Option<AgentMessageDisplay>,
}
impl From<CoreAgentMessageModeration> for AgentMessageModeration {
fn from(value: CoreAgentMessageModeration) -> Self {
Self {
moderated: value.moderated,
display: value.display.map(Into::into),
}
}
}
/// A presentation instruction for Chat to apply to this answer when recovering history.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AgentMessageDisplay {
/// Unknown actions must be left for the producing client to handle.
pub action: String,
pub block_type: Option<String>,
pub cyber_trusted_access: Option<bool>,
pub check_trusted_contacts: Option<bool>,
}
impl From<CoreAgentMessageDisplay> for AgentMessageDisplay {
fn from(value: CoreAgentMessageDisplay) -> Self {
Self {
action: value.action,
block_type: value.block_type,
cyber_trusted_access: value.cyber_trusted_access,
check_trusted_contacts: value.check_trusted_contacts,
}
}
}
@@ -3170,6 +3170,35 @@ fn audio_references_round_trip_through_core_and_json() {
);
}
#[test]
fn answer_moderation_exposes_nullable_display_fields() {
for (stored, expected) in [
(
json!({"moderated": false}),
json!({"moderated": false, "display": null}),
),
(
json!({"moderated": true, "display": {
"action": "answerHelpline",
"checkTrustedContacts": false,
}}),
json!({"moderated": true, "display": {
"action": "answerHelpline",
"blockType": null,
"cyberTrustedAccess": null,
"checkTrustedContacts": false,
}}),
),
] {
let core: codex_protocol::items::AgentMessageModeration =
serde_json::from_value(stored).unwrap();
assert_eq!(
serde_json::to_value(AgentMessageModeration::from(core)).unwrap(),
expected
);
}
}
#[test]
fn core_turn_item_into_thread_item_converts_supported_variants() {
let user_item = TurnItem::UserMessage(UserMessageItem {
@@ -3297,7 +3326,10 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
);
let agent_item_with_phase = TurnItem::AgentMessage(AgentMessageItem {
moderation: Some(codex_protocol::items::AgentMessageModeration { moderated: true }),
moderation: Some(codex_protocol::items::AgentMessageModeration {
moderated: true,
display: None,
}),
id: "agent-2".to_string(),
content: vec![AgentMessageContent::Text {
text: "final".to_string(),
@@ -3319,7 +3351,10 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
assert_eq!(
ThreadItemPayload::from(agent_item_with_phase),
ThreadItemPayload::AgentMessage {
moderation: Some(AgentMessageModeration { moderated: true }),
moderation: Some(AgentMessageModeration {
moderated: true,
display: None
}),
id: "agent-2".to_string(),
text: "final".to_string(),
phase: Some(MessagePhase::FinalAnswer),
+1
View File
@@ -342,6 +342,7 @@ pub(crate) fn apply_answer_moderation(
{
answer.moderation = Some(codex_protocol::items::AgentMessageModeration {
moderated: decision.moderated,
display: decision.display.clone(),
});
}
}
+6 -3
View File
@@ -470,7 +470,7 @@ async fn final_answer_carries_its_moderation_decision() -> Result<()> {
responses::sse(vec![
responses::ev_response_created("resp-1"),
json!({"type":"response.metadata","metadata":{"openai_chatgpt_moderation_metadata":{
"final_answer_moderation":{"item_id":"msg-42","moderated":true},
"final_answer_moderation":{"item_id":"msg-42","moderated":true,"display":{"action":"answerHelpline"}},
"generation":{"private":"classifier details"}
}}}),
answer,
@@ -494,13 +494,16 @@ async fn final_answer_carries_its_moderation_decision() -> Result<()> {
panic!("expected assistant answer");
};
let public = serde_json::to_value(&message)?;
assert_eq!(public["moderation"], json!({"moderated": true}));
assert_eq!(
public["moderation"],
json!({"moderated": true,"display":{"action":"answerHelpline"}})
);
let [EventMsg::AgentMessage(legacy)] = &message.as_legacy_events()[..] else {
panic!("expected one legacy answer");
};
assert_eq!(
serde_json::to_value(&legacy.moderation)?,
json!({"item_id":"msg-42","moderated":true})
json!({"item_id":"msg-42","moderated":true,"display":{"action":"answerHelpline"}})
);
Ok(())
}
+22 -1
View File
@@ -151,9 +151,30 @@ pub struct AsyncUserInputQuestion {
pub options: Option<Vec<String>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, TS, JsonSchema)]
/// A presentation instruction for Chat to apply to this answer when recovering history.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, TS, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AgentMessageDisplay {
/// Unknown actions must be left for the producing client to handle.
pub action: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub block_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub cyber_trusted_access: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub check_trusted_contacts: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, TS, JsonSchema)]
pub struct AgentMessageModeration {
pub moderated: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub display: Option<AgentMessageDisplay>,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)]
+2 -1
View File
@@ -139,13 +139,14 @@ impl AgentMessageItem {
questions: self.questions.clone(),
})
};
if let Some(decision) = self.moderation {
if let Some(decision) = &self.moderation {
// The canonical answer has one ID even when the model supplied multiple text parts.
return vec![event(
texts.collect(),
Some(crate::protocol::TargetedAnswerModeration {
item_id: self.id.clone(),
moderated: decision.moderated,
display: decision.display.clone(),
}),
)];
}
+3
View File
@@ -2400,6 +2400,9 @@ pub struct ModelVerificationEvent {
pub struct TargetedAnswerModeration {
pub item_id: String,
pub moderated: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub display: Option<crate::items::AgentMessageDisplay>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
@@ -161,6 +161,7 @@ pub(super) fn completed_item(
moderation: event.moderation.as_ref().map(|decision| {
codex_protocol::items::AgentMessageModeration {
moderated: decision.moderated,
display: decision.display.clone(),
}
}),
id: event
@@ -149,6 +149,18 @@ class AgentMessageDeltaNotification(BaseModel):
turn_id: Annotated[str, Field(alias="turnId")]
class AgentMessageDisplay(BaseModel):
model_config = ConfigDict(
populate_by_name=True,
)
action: Annotated[
str, Field(description="Unknown actions must be left for the producing client to handle.")
]
block_type: Annotated[str | None, Field(alias="blockType")] = None
check_trusted_contacts: Annotated[bool | None, Field(alias="checkTrustedContacts")] = None
cyber_trusted_access: Annotated[bool | None, Field(alias="cyberTrustedAccess")] = None
class InputTextAgentMessageInputContent(BaseModel):
model_config = ConfigDict(
populate_by_name=True,
@@ -177,6 +189,7 @@ class AgentMessageModeration(BaseModel):
model_config = ConfigDict(
populate_by_name=True,
)
display: AgentMessageDisplay | None = None
moderated: bool