mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Make thread store process-scoped (#19474)
- Build one app-server process ThreadStore from startup config and share it with ThreadManager and CodexMessageProcessor. - Remove per-thread/fork store reconstruction so effective thread config cannot switch the persistence backend. - Add params to ThreadStore create/resume for specifying thread metadata, since otherwise the metadata from store creation would be used (incorrectly).
This commit is contained in:
@@ -25,6 +25,7 @@ use crate::StoredThread;
|
||||
use crate::StoredThreadHistory;
|
||||
use crate::ThreadEventPersistenceMode;
|
||||
use crate::ThreadMetadataPatch;
|
||||
use crate::ThreadPersistenceMetadata;
|
||||
use crate::ThreadSortKey;
|
||||
use crate::ThreadStoreError;
|
||||
use crate::ThreadStoreResult;
|
||||
@@ -186,6 +187,12 @@ pub(super) fn dynamic_tools_json(
|
||||
serialize_json_vec(dynamic_tools, "dynamic_tool")
|
||||
}
|
||||
|
||||
pub(super) fn thread_persistence_metadata_json(
|
||||
metadata: &ThreadPersistenceMetadata,
|
||||
) -> ThreadStoreResult<String> {
|
||||
serialize_json(metadata, "thread_persistence_metadata")
|
||||
}
|
||||
|
||||
pub(super) fn rollout_items_json(items: &[RolloutItem]) -> ThreadStoreResult<Vec<String>> {
|
||||
serialize_json_vec(items, "rollout_item")
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ impl ThreadStore for RemoteThreadStore {
|
||||
params.event_persistence_mode,
|
||||
)
|
||||
.into(),
|
||||
metadata_json: helpers::thread_persistence_metadata_json(¶ms.metadata)?,
|
||||
};
|
||||
self.client()
|
||||
.await?
|
||||
@@ -96,6 +97,7 @@ impl ThreadStore for RemoteThreadStore {
|
||||
params.event_persistence_mode,
|
||||
)
|
||||
.into(),
|
||||
metadata_json: helpers::thread_persistence_metadata_json(¶ms.metadata)?,
|
||||
};
|
||||
self.client()
|
||||
.await?
|
||||
@@ -260,3 +262,148 @@ impl ThreadStore for RemoteThreadStore {
|
||||
helpers::stored_thread_from_proto(thread)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::ThreadMemoryMode;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tokio::sync::mpsc;
|
||||
use tonic::Request;
|
||||
use tonic::Response;
|
||||
use tonic::Status;
|
||||
use tonic::transport::Server;
|
||||
|
||||
use super::*;
|
||||
use crate::ThreadEventPersistenceMode;
|
||||
use crate::ThreadPersistenceMetadata;
|
||||
use proto::thread_store_server;
|
||||
use proto::thread_store_server::ThreadStoreServer;
|
||||
|
||||
enum RecordedRequest {
|
||||
Create(proto::CreateThreadRequest),
|
||||
Resume(proto::ResumeThreadRequest),
|
||||
}
|
||||
|
||||
struct TestServer {
|
||||
requests_tx: mpsc::UnboundedSender<RecordedRequest>,
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl thread_store_server::ThreadStore for TestServer {
|
||||
async fn create_thread(
|
||||
&self,
|
||||
request: Request<proto::CreateThreadRequest>,
|
||||
) -> Result<Response<proto::Empty>, Status> {
|
||||
self.requests_tx
|
||||
.send(RecordedRequest::Create(request.into_inner()))
|
||||
.expect("record create request");
|
||||
Ok(Response::new(proto::Empty {}))
|
||||
}
|
||||
|
||||
async fn resume_thread(
|
||||
&self,
|
||||
request: Request<proto::ResumeThreadRequest>,
|
||||
) -> Result<Response<proto::Empty>, Status> {
|
||||
self.requests_tx
|
||||
.send(RecordedRequest::Resume(request.into_inner()))
|
||||
.expect("record resume request");
|
||||
Ok(Response::new(proto::Empty {}))
|
||||
}
|
||||
|
||||
async fn list_threads(
|
||||
&self,
|
||||
_request: Request<proto::ListThreadsRequest>,
|
||||
) -> Result<Response<proto::ListThreadsResponse>, Status> {
|
||||
Err(Status::unimplemented("not implemented"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_store() -> (RemoteThreadStore, mpsc::UnboundedReceiver<RecordedRequest>) {
|
||||
let (requests_tx, requests_rx) = mpsc::unbounded_channel();
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
|
||||
.await
|
||||
.expect("bind test server");
|
||||
let addr = listener.local_addr().expect("test server addr");
|
||||
|
||||
tokio::spawn(async move {
|
||||
Server::builder()
|
||||
.add_service(ThreadStoreServer::new(TestServer { requests_tx }))
|
||||
.serve_with_incoming(tokio_stream::wrappers::TcpListenerStream::new(listener))
|
||||
.await
|
||||
.expect("test server");
|
||||
});
|
||||
|
||||
(
|
||||
RemoteThreadStore::new(format!("http://{addr}")),
|
||||
requests_rx,
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_thread_forwards_metadata() {
|
||||
let (store, mut requests_rx) = test_store().await;
|
||||
let metadata = ThreadPersistenceMetadata {
|
||||
cwd: Some(PathBuf::from("/workspace")),
|
||||
model_provider: "test-provider".to_string(),
|
||||
memory_mode: ThreadMemoryMode::Enabled,
|
||||
};
|
||||
|
||||
store
|
||||
.create_thread(CreateThreadParams {
|
||||
thread_id: ThreadId::new(),
|
||||
forked_from_id: None,
|
||||
source: SessionSource::Exec,
|
||||
base_instructions: BaseInstructions::default(),
|
||||
dynamic_tools: Vec::new(),
|
||||
metadata: metadata.clone(),
|
||||
event_persistence_mode: ThreadEventPersistenceMode::Limited,
|
||||
})
|
||||
.await
|
||||
.expect("create thread");
|
||||
|
||||
let Some(RecordedRequest::Create(request)) = requests_rx.recv().await else {
|
||||
panic!("expected create request");
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ThreadPersistenceMetadata>(&request.metadata_json)
|
||||
.expect("metadata json"),
|
||||
metadata
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resume_thread_forwards_metadata() {
|
||||
let (store, mut requests_rx) = test_store().await;
|
||||
let metadata = ThreadPersistenceMetadata {
|
||||
cwd: Some(PathBuf::from("/workspace")),
|
||||
model_provider: "test-provider".to_string(),
|
||||
memory_mode: ThreadMemoryMode::Disabled,
|
||||
};
|
||||
|
||||
store
|
||||
.resume_thread(ResumeThreadParams {
|
||||
thread_id: ThreadId::new(),
|
||||
rollout_path: None,
|
||||
history: None,
|
||||
include_archived: false,
|
||||
metadata: metadata.clone(),
|
||||
event_persistence_mode: ThreadEventPersistenceMode::Limited,
|
||||
})
|
||||
.await
|
||||
.expect("resume thread");
|
||||
|
||||
let Some(RecordedRequest::Resume(request)) = requests_rx.recv().await else {
|
||||
panic!("expected resume request");
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ThreadPersistenceMetadata>(&request.metadata_json)
|
||||
.expect("metadata json"),
|
||||
metadata
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ message CreateThreadRequest {
|
||||
string base_instructions_json = 4;
|
||||
repeated string dynamic_tools_json = 5;
|
||||
ThreadEventPersistenceMode event_persistence_mode = 6;
|
||||
string metadata_json = 7;
|
||||
}
|
||||
|
||||
message ResumeThreadRequest {
|
||||
@@ -40,6 +41,7 @@ message ResumeThreadRequest {
|
||||
bool has_history = 4;
|
||||
bool include_archived = 5;
|
||||
ThreadEventPersistenceMode event_persistence_mode = 6;
|
||||
string metadata_json = 7;
|
||||
}
|
||||
|
||||
message AppendThreadItemsRequest {
|
||||
|
||||
@@ -22,6 +22,8 @@ pub struct CreateThreadRequest {
|
||||
pub dynamic_tools_json: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(enumeration = "ThreadEventPersistenceMode", tag = "6")]
|
||||
pub event_persistence_mode: i32,
|
||||
#[prost(string, tag = "7")]
|
||||
pub metadata_json: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||
pub struct ResumeThreadRequest {
|
||||
@@ -37,6 +39,8 @@ pub struct ResumeThreadRequest {
|
||||
pub include_archived: bool,
|
||||
#[prost(enumeration = "ThreadEventPersistenceMode", tag = "6")]
|
||||
pub event_persistence_mode: i32,
|
||||
#[prost(string, tag = "7")]
|
||||
pub metadata_json: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||
pub struct AppendThreadItemsRequest {
|
||||
|
||||
Reference in New Issue
Block a user