Simplify goal extension refactor

This commit is contained in:
Eric Traut
2026-05-27 00:34:13 -07:00
parent 98042e7a27
commit 7782d6733b
10 changed files with 53 additions and 90 deletions

View File

@@ -334,7 +334,6 @@ use codex_feedback::FeedbackUploadOptions;
use codex_git_utils::git_diff_to_remote;
use codex_git_utils::resolve_root_git_project_for_trust;
use codex_goal_extension::GoalRuntimeHandle;
use codex_goal_extension::PreviousGoalSnapshot;
use codex_login::AuthManager;
use codex_login::CLIENT_ID;
use codex_login::CodexAuth;

View File

@@ -145,15 +145,14 @@ impl ThreadGoalRequestProcessor {
prepare_external_goal_mutation(running_thread.as_deref()).await;
let should_set_thread_preview = objective.is_some();
let (goal, previous_goal) = (if let Some(objective) = objective {
let existing_goal = state_db
.thread_goals()
.get_thread_goal(thread_id)
.await
.map_err(|err| invalid_request(err.to_string()))?;
if let Some(goal) = existing_goal.as_ref() {
let previous_goal = PreviousGoalSnapshot::from(goal);
state_db
let existing_goal = state_db
.thread_goals()
.get_thread_goal(thread_id)
.await
.map_err(|err| invalid_request(err.to_string()))?;
let (goal, previous_goal) = match (objective, existing_goal) {
(Some(objective), Some(existing_goal)) => {
let goal = state_db
.thread_goals()
.update_thread_goal(
thread_id,
@@ -161,20 +160,20 @@ impl ThreadGoalRequestProcessor {
objective: Some(objective.to_string()),
status,
token_budget: params.token_budget,
expected_goal_id: Some(goal.goal_id.clone()),
expected_goal_id: Some(existing_goal.goal_id.clone()),
},
)
.await
.and_then(|goal| {
goal.ok_or_else(|| {
anyhow::anyhow!(
"cannot update goal for thread {thread_id}: no goal exists"
)
})
})
.map(|goal| (goal, Some(previous_goal)))
} else {
state_db
.map_err(|err| invalid_request(err.to_string()))?
.ok_or_else(|| {
invalid_request(format!(
"cannot update goal for thread {thread_id}: no goal exists"
))
})?;
(goal, Some(existing_goal))
}
(Some(objective), None) => {
let goal = state_db
.thread_goals()
.replace_thread_goal(
thread_id,
@@ -183,40 +182,36 @@ impl ThreadGoalRequestProcessor {
params.token_budget.flatten(),
)
.await
.map(|goal| (goal, None))
.map_err(|err| invalid_request(err.to_string()))?;
(goal, None)
}
} else {
let existing_goal = state_db
.thread_goals()
.get_thread_goal(thread_id)
.await
.map_err(|err| invalid_request(err.to_string()))?;
let Some(existing_goal) = existing_goal else {
(None, Some(existing_goal)) => {
let goal = state_db
.thread_goals()
.update_thread_goal(
thread_id,
codex_state::GoalUpdate {
objective: None,
status,
token_budget: params.token_budget,
expected_goal_id: None,
},
)
.await
.map_err(|err| invalid_request(err.to_string()))?
.ok_or_else(|| {
invalid_request(format!(
"cannot update goal for thread {thread_id}: no goal exists"
))
})?;
(goal, Some(existing_goal))
}
(None, None) => {
return Err(invalid_request(format!(
"cannot update goal for thread {thread_id}: no goal exists"
)));
};
let previous_goal = PreviousGoalSnapshot::from(&existing_goal);
state_db
.thread_goals()
.update_thread_goal(
thread_id,
codex_state::GoalUpdate {
objective: None,
status,
token_budget: params.token_budget,
expected_goal_id: None,
},
)
.await
.and_then(|goal| {
goal.ok_or_else(|| {
anyhow::anyhow!("cannot update goal for thread {thread_id}: no goal exists")
})
})
.map(|goal| (goal, Some(previous_goal)))
})
.map_err(|err| invalid_request(err.to_string()))?;
}
};
if should_set_thread_preview
&& let Err(err) = state_db
.set_thread_preview_if_empty(thread_id, goal.objective.as_str())
@@ -453,7 +448,7 @@ async fn prepare_external_goal_mutation(thread: Option<&CodexThread>) {
async fn apply_external_goal_set(
thread: Option<&CodexThread>,
goal: codex_state::ThreadGoal,
previous_goal: Option<PreviousGoalSnapshot>,
previous_goal: Option<codex_state::ThreadGoal>,
) {
let Some(thread) = thread else {
return;

View File

@@ -512,7 +512,7 @@ impl Session {
})
.await
{
requested_items = Some(request.items);
requested_items = Some(request);
break;
}
}

View File

@@ -230,7 +230,6 @@ pub(crate) fn extension_tool_executors(
contributor.tools_for_turn(codex_extension_api::ToolContributionInput {
session_store: &session.services.session_extension_data,
thread_store: &session.services.thread_extension_data,
turn_store: Some(turn_context.extension_data.as_ref()),
session_source: &turn_context.session_source,
persistent_thread: !turn_context.config.ephemeral && session.state_db().is_some(),
})

View File

@@ -141,19 +141,12 @@ pub struct ToolContributionInput<'a> {
pub session_store: &'a ExtensionData,
/// Store scoped to this thread runtime.
pub thread_store: &'a ExtensionData,
/// Store scoped to this turn runtime, if the host is collecting for a turn.
pub turn_store: Option<&'a ExtensionData>,
/// Source for the current turn's session.
pub session_source: &'a SessionSource,
/// Whether the current thread has persistent state available.
pub persistent_thread: bool,
}
/// Extension request to start a new idle turn.
pub struct IdleTurnRequest {
pub items: Vec<ResponseInputItem>,
}
/// Context supplied when the host is idle and extensions may request work.
pub struct IdleTurnInput<'a> {
/// Effective collaboration mode for the next default turn.
@@ -164,7 +157,8 @@ pub struct IdleTurnInput<'a> {
pub thread_store: &'a ExtensionData,
}
pub type IdleTurnFuture<'a> = Pin<Box<dyn Future<Output = Option<IdleTurnRequest>> + Send + 'a>>;
pub type IdleTurnFuture<'a> =
Pin<Box<dyn Future<Output = Option<Vec<ResponseInputItem>>> + Send + 'a>>;
/// Extension contribution that can request host-owned work while a thread is idle.
pub trait IdleTurnContributor: Send + Sync {

View File

@@ -28,7 +28,6 @@ pub use contributors::ContextContributor;
pub use contributors::IdleTurnContributor;
pub use contributors::IdleTurnFuture;
pub use contributors::IdleTurnInput;
pub use contributors::IdleTurnRequest;
pub use contributors::PromptFragment;
pub use contributors::PromptSlot;
pub use contributors::ThreadLifecycleContributor;

View File

@@ -10,7 +10,6 @@ use codex_extension_api::ExtensionRegistryBuilder;
use codex_extension_api::IdleTurnContributor;
use codex_extension_api::IdleTurnFuture;
use codex_extension_api::IdleTurnInput;
use codex_extension_api::IdleTurnRequest;
use codex_extension_api::ThreadLifecycleContributor;
use codex_extension_api::ThreadResumeInput;
use codex_extension_api::ThreadStartInput;
@@ -429,7 +428,7 @@ where
.idle_continuation_items(input.collaboration_mode.mode)
.await
{
Ok(Some(items)) => Some(IdleTurnRequest { items }),
Ok(Some(items)) => Some(items),
Ok(None) => None,
Err(err) => {
tracing::warn!("failed to request idle goal continuation: {err}");

View File

@@ -17,7 +17,6 @@ pub use extension::GoalExtension;
pub use extension::GoalExtensionConfig;
pub use extension::install_with_backend;
pub use runtime::GoalRuntimeHandle;
pub use runtime::PreviousGoalSnapshot;
pub use spec::CREATE_GOAL_TOOL_NAME;
pub use spec::GET_GOAL_TOOL_NAME;
pub use spec::UPDATE_GOAL_TOOL_NAME;

View File

@@ -37,23 +37,6 @@ pub(crate) struct AccountedGoalProgress {
pub(crate) goal_id: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PreviousGoalSnapshot {
pub goal_id: String,
pub status: codex_state::ThreadGoalStatus,
pub objective: String,
}
impl From<&codex_state::ThreadGoal> for PreviousGoalSnapshot {
fn from(goal: &codex_state::ThreadGoal) -> Self {
Self {
goal_id: goal.goal_id.clone(),
status: goal.status,
objective: goal.objective.clone(),
}
}
}
impl std::fmt::Debug for GoalRuntimeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GoalRuntimeHandle").finish_non_exhaustive()
@@ -127,7 +110,7 @@ impl GoalRuntimeHandle {
pub async fn apply_external_goal_set(
&self,
goal: codex_state::ThreadGoal,
previous_goal: Option<PreviousGoalSnapshot>,
previous_goal: Option<codex_state::ThreadGoal>,
) -> Result<(), String> {
if !self.is_enabled() {
return Ok(());

View File

@@ -21,7 +21,6 @@ use codex_extension_api::TurnErrorInput;
use codex_extension_api::TurnStartInput;
use codex_extension_api::TurnStopInput;
use codex_goal_extension::GoalRuntimeHandle;
use codex_goal_extension::PreviousGoalSnapshot;
use codex_goal_extension::install_with_backend;
use codex_protocol::ThreadId;
use codex_protocol::config_types::CollaborationMode;
@@ -661,10 +660,7 @@ async fn external_goal_set_active_resets_baseline_without_live_thread() -> anyho
.ok_or_else(|| anyhow::anyhow!("goal update should succeed"))?;
harness
.runtime_handle()
.apply_external_goal_set(
updated_goal,
Some(PreviousGoalSnapshot::from(&previous_goal)),
)
.apply_external_goal_set(updated_goal, Some(previous_goal))
.await
.map_err(anyhow::Error::msg)?;
@@ -924,7 +920,7 @@ impl GoalExtensionHarness {
})
.await
{
return Some(request.items);
return Some(request);
}
}
None