mirror of
https://github.com/openai/codex.git
synced 2026-11-13 22:47:20 +00:00
## 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
85 lines
2.6 KiB
Rust
85 lines
2.6 KiB
Rust
use super::LocalThreadStore;
|
|
use crate::CreateThreadSectionParams;
|
|
use crate::DeleteThreadSectionParams;
|
|
use crate::ListThreadSectionsParams;
|
|
use crate::RenameThreadSectionParams;
|
|
use crate::StoredThreadSection;
|
|
use crate::ThreadStore;
|
|
use crate::ThreadStoreError;
|
|
use crate::local::test_support::test_config;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
#[tokio::test]
|
|
async fn local_thread_sections_require_sqlite_and_preserve_section_identity() {
|
|
let home = tempfile::TempDir::new().expect("temp dir");
|
|
let config = test_config(home.path());
|
|
let unsupported_store = LocalThreadStore::new(config.clone(), /*state_db*/ None);
|
|
assert!(!unsupported_store.supports_thread_sections());
|
|
assert!(matches!(
|
|
unsupported_store
|
|
.create_thread_section(CreateThreadSectionParams {
|
|
name: "Research".to_string(),
|
|
appearance: None,
|
|
})
|
|
.await,
|
|
Err(ThreadStoreError::Unsupported {
|
|
operation: "threadSection/create"
|
|
})
|
|
));
|
|
|
|
let runtime = codex_state::StateRuntime::init(
|
|
config.sqlite.clone(),
|
|
config.default_model_provider_id.clone(),
|
|
)
|
|
.await
|
|
.expect("state db should initialize");
|
|
let store = LocalThreadStore::new(config, Some(runtime));
|
|
assert!(store.supports_thread_sections());
|
|
|
|
let appearance = codex_state::ThreadSectionAppearance {
|
|
icon: Some("folder".to_string()),
|
|
color: Some("purple".to_string()),
|
|
};
|
|
let section = store
|
|
.create_thread_section(CreateThreadSectionParams {
|
|
name: "Research".to_string(),
|
|
appearance: Some(appearance.clone()),
|
|
})
|
|
.await
|
|
.expect("create section");
|
|
let sections = store
|
|
.list_thread_sections(ListThreadSectionsParams {
|
|
cursor: None,
|
|
limit: 10,
|
|
})
|
|
.await
|
|
.expect("list sections");
|
|
assert!(sections.sections.contains(§ion));
|
|
|
|
let renamed = store
|
|
.rename_thread_section(RenameThreadSectionParams {
|
|
section_id: section.id.clone(),
|
|
name: "Planning".to_string(),
|
|
appearance: None,
|
|
})
|
|
.await
|
|
.expect("rename section");
|
|
assert_eq!(
|
|
renamed,
|
|
Some(StoredThreadSection {
|
|
id: section.id.clone(),
|
|
name: "Planning".to_string(),
|
|
appearance: Some(appearance),
|
|
})
|
|
);
|
|
|
|
assert!(
|
|
store
|
|
.delete_thread_section(DeleteThreadSectionParams {
|
|
section_id: section.id,
|
|
})
|
|
.await
|
|
.expect("delete section")
|
|
);
|
|
}
|