mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Add experimental thread queue APIs to app server (#38456)
## What changed - Add experimental `thread/queue/add`, `list`, `update`, `delete`, `reorder`, and `start` requests for persistent queued user submissions. - Automatically dispatch queued submissions in FIFO order after completed or failed turns, while leaving the queue paused after interruption. - Emit `thread/queue/changed` notifications and expose paginated queue contents with stable submission and client message IDs. - Export the new protocol types through the generated JSON Schema and TypeScript bindings. ## Testing - Add app-server integration coverage for capability gating, CRUD, pagination, capacity limits, notifications, automatic dispatch, cold-thread resume, interruption, and explicit queue starts. GitOrigin-RevId: 89caf5577192199bda77cf6c372a59d26cdd910e
This commit is contained in:
1 parent
4d9f3021c8
commit
9341b38310
35 files changed
+1952
-105
No files matched your search
@@ -6,6 +6,7 @@ use codex_analytics::AnalyticsEventsClient;
|
||||
use codex_app_server_protocol::ServerNotification;
|
||||
use codex_app_server_protocol::ThreadGoal;
|
||||
use codex_app_server_protocol::ThreadGoalUpdatedNotification;
|
||||
use codex_app_server_protocol::ThreadQueueChangedNotification;
|
||||
use codex_app_server_protocol::WarningNotification;
|
||||
use codex_core::NewThread;
|
||||
use codex_core::StartThreadOptions;
|
||||
@@ -26,8 +27,8 @@ use codex_protocol::ThreadId;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::protocol::Event;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_queue_extension::QueuedItemService;
|
||||
use codex_rollout::state_db::StateDbHandle;
|
||||
use codex_thread_store::QueueStore;
|
||||
|
||||
use crate::outgoing_message::OutgoingMessageSender;
|
||||
use crate::outgoing_message::ThreadScopedOutgoingMessageSender;
|
||||
@@ -45,8 +46,8 @@ pub(crate) struct ThreadExtensionDependencies {
|
||||
pub(crate) executor_skill_provider: Arc<dyn codex_skills_extension::SkillProvider>,
|
||||
pub(crate) git_attribution_base_url: String,
|
||||
pub(crate) http_client_factory: HttpClientFactory,
|
||||
/// Process-scoped persistence backend for queued user messages.
|
||||
pub(crate) queue_store: Option<Arc<dyn QueueStore>>,
|
||||
/// Process-scoped queue shared by idle dispatch and app-server requests.
|
||||
pub(crate) queue_service: Option<Arc<QueuedItemService>>,
|
||||
}
|
||||
|
||||
pub(crate) fn thread_extensions<S>(
|
||||
@@ -67,15 +68,10 @@ where
|
||||
executor_skill_provider,
|
||||
git_attribution_base_url,
|
||||
http_client_factory,
|
||||
queue_store,
|
||||
queue_service,
|
||||
} = dependencies;
|
||||
let mut builder = ExtensionRegistryBuilder::<Config>::with_event_sink(Arc::clone(&event_sink));
|
||||
if let Some(queue) = queue_store {
|
||||
let queue_service = Arc::new(codex_queue_extension::QueuedItemService::new(
|
||||
queue,
|
||||
thread_manager.clone(),
|
||||
event_sink,
|
||||
));
|
||||
if let Some(queue_service) = queue_service {
|
||||
codex_queue_extension::install(&mut builder, queue_service);
|
||||
}
|
||||
if let Some(state_db) = state_db {
|
||||
@@ -171,6 +167,40 @@ const EXTENSION_WARNING_SUBSCRIBER_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
impl ExtensionEventSink for AppServerExtensionEventSink {
|
||||
fn emit(&self, event: Event) {
|
||||
match event.msg {
|
||||
EventMsg::ThreadQueueChanged(queue_event) => {
|
||||
let thread_id = queue_event.thread_id;
|
||||
if let Some(listener_command_tx) = self
|
||||
.thread_state_manager
|
||||
.current_listener_command_tx(thread_id)
|
||||
{
|
||||
let command = ThreadListenerCommand::EmitThreadQueueChanged;
|
||||
if listener_command_tx.send(command).is_ok() {
|
||||
return;
|
||||
}
|
||||
tracing::warn!(
|
||||
"failed to enqueue extension queue update for {thread_id}: listener command channel is closed"
|
||||
);
|
||||
}
|
||||
let outgoing = Arc::clone(&self.outgoing);
|
||||
let thread_state_manager = self.thread_state_manager.clone();
|
||||
tokio::spawn(async move {
|
||||
let subscribed_connection_ids = thread_state_manager
|
||||
.subscribed_connection_ids(thread_id)
|
||||
.await;
|
||||
let outgoing = ThreadScopedOutgoingMessageSender::new(
|
||||
outgoing,
|
||||
subscribed_connection_ids,
|
||||
thread_id,
|
||||
);
|
||||
outgoing
|
||||
.send_server_notification(ServerNotification::ThreadQueueChanged(
|
||||
ThreadQueueChangedNotification {
|
||||
thread_id: thread_id.to_string(),
|
||||
},
|
||||
))
|
||||
.await;
|
||||
});
|
||||
}
|
||||
EventMsg::ThreadGoalUpdated(thread_goal_event) => {
|
||||
let thread_id = thread_goal_event.thread_id;
|
||||
let turn_id = thread_goal_event.turn_id;
|
||||
|
||||
Reference in new issue
Block a user