mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Add appearance metadata to thread sections (#37898)
## What changed - Add optional `icon` and `color` appearance fields to custom thread sections and expose them through the app-server protocol. - Persist appearance metadata in SQLite and include it when listing sections or returning threads. - Let `threadSection/update` preserve an omitted appearance, clear it with `null`, or replace it with a new value. - Reject appearance fields larger than 64 bytes. ## Testing - Cover protocol compatibility, persistence across restart, update and clear behavior, and field-length validation. GitOrigin-RevId: 8f55ce4f42f8bd17aa5416848a85845f681953df
This commit is contained in:
1 parent
92912d6d84
commit
1549756b78
54 files changed
+939
-47
No files matched your search
@@ -255,6 +255,7 @@ fn thread_resume_response_round_trips_initial_turns_page() {
|
||||
section: Some(ThreadSection {
|
||||
id: "01984de2-8f74-7c91-a3b2-5c5e937cf318".to_string(),
|
||||
name: "Pinned".to_string(),
|
||||
appearance: None,
|
||||
}),
|
||||
section_entered_at: Some(1),
|
||||
history_mode: Default::default(),
|
||||
@@ -302,6 +303,7 @@ fn thread_resume_response_round_trips_initial_turns_page() {
|
||||
json!({
|
||||
"id": "01984de2-8f74-7c91-a3b2-5c5e937cf318",
|
||||
"name": "Pinned",
|
||||
"appearance": null,
|
||||
})
|
||||
);
|
||||
assert_eq!(value["thread"]["sectionEnteredAt"], json!(1));
|
||||
@@ -496,6 +498,13 @@ fn thread_list_params_accepts_section_id_filter() {
|
||||
|
||||
#[test]
|
||||
fn thread_section_list_params_and_response_round_trip() {
|
||||
let legacy = serde_json::from_value::<ThreadSection>(json!({
|
||||
"id": "legacy",
|
||||
"name": "Legacy",
|
||||
}))
|
||||
.expect("legacy sections without appearance should deserialize");
|
||||
assert_eq!(legacy.appearance, None);
|
||||
|
||||
let params = serde_json::from_value::<ThreadSectionListParams>(json!({
|
||||
"cursor": "section-cursor",
|
||||
"limit": 25,
|
||||
@@ -513,6 +522,7 @@ fn thread_section_list_params_and_response_round_trip() {
|
||||
data: vec![ThreadSection {
|
||||
id: "01984de2-8f74-7c91-a3b2-5c5e937cf318".to_string(),
|
||||
name: "Pinned".to_string(),
|
||||
appearance: None,
|
||||
}],
|
||||
next_cursor: None,
|
||||
};
|
||||
@@ -523,6 +533,7 @@ fn thread_section_list_params_and_response_round_trip() {
|
||||
"data": [{
|
||||
"id": "01984de2-8f74-7c91-a3b2-5c5e937cf318",
|
||||
"name": "Pinned",
|
||||
"appearance": null,
|
||||
}],
|
||||
"nextCursor": null,
|
||||
})
|
||||
@@ -534,6 +545,21 @@ fn thread_section_list_params_and_response_round_trip() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_section_updates_distinguish_omitted_and_cleared_appearance() {
|
||||
for (value, appearance) in [
|
||||
(json!({ "sectionId": "section", "name": "Work" }), None),
|
||||
(
|
||||
json!({ "sectionId": "section", "name": "Work", "appearance": null }),
|
||||
Some(None),
|
||||
),
|
||||
] {
|
||||
let params = serde_json::from_value::<ThreadSectionUpdateParams>(value)
|
||||
.expect("section update should deserialize");
|
||||
assert_eq!(params.appearance, appearance);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collab_agent_state_maps_interrupted_status() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -7,6 +7,7 @@ use super::Thread;
|
||||
use super::ThreadHistoryMode;
|
||||
use super::ThreadItem;
|
||||
use super::ThreadSection;
|
||||
use super::ThreadSectionAppearance;
|
||||
use super::ThreadSource;
|
||||
use super::Turn;
|
||||
use super::TurnEnvironmentParams;
|
||||
@@ -1146,6 +1147,9 @@ pub struct ThreadSectionListResponse {
|
||||
pub struct ThreadSectionCreateParams {
|
||||
/// The user-visible name of the section.
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
#[ts(optional = nullable)]
|
||||
pub appearance: Option<ThreadSectionAppearance>,
|
||||
}
|
||||
|
||||
/// The independently persisted section created by the server.
|
||||
@@ -1165,6 +1169,16 @@ pub struct ThreadSectionUpdateParams {
|
||||
pub section_id: String,
|
||||
/// The updated user-visible name of the section.
|
||||
pub name: String,
|
||||
/// Omit to preserve appearance, use `null` to clear it, or provide a replacement.
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
serialize_with = "crate::protocol::serde_helpers::serialize_double_option",
|
||||
deserialize_with = "crate::protocol::serde_helpers::deserialize_double_option"
|
||||
)]
|
||||
#[schemars(with = "Option<ThreadSectionAppearance>")]
|
||||
#[ts(optional = nullable, as = "Option<ThreadSectionAppearance>")]
|
||||
pub appearance: Option<Option<ThreadSectionAppearance>>,
|
||||
}
|
||||
|
||||
/// The independently persisted section after its name is updated.
|
||||
|
||||
@@ -176,6 +176,18 @@ pub struct ThreadSection {
|
||||
pub id: String,
|
||||
/// The current user-visible section name.
|
||||
pub name: String,
|
||||
/// Optional appearance synchronized across clients.
|
||||
#[serde(default)]
|
||||
pub appearance: Option<ThreadSectionAppearance>,
|
||||
}
|
||||
|
||||
/// Extensible visual presentation for a custom thread section.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadSectionAppearance {
|
||||
pub icon: Option<String>,
|
||||
pub color: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||
|
||||
Reference in new issue
Block a user