Skip to content

Commit 92f15b9

Browse files
committed
changes
1 parent 981e2f7 commit 92f15b9

File tree

4 files changed

+126
-0
lines changed

4 files changed

+126
-0
lines changed

codex-rs/app-server-protocol/src/protocol/common.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ server_notification_definitions! {
506506
TurnStarted => "turn/started" (v2::TurnStartedNotification),
507507
TurnCompleted => "turn/completed" (v2::TurnCompletedNotification),
508508
TurnDiffUpdated => "turn/diff/updated" (v2::TurnDiffUpdatedNotification),
509+
TurnPlanUpdated => "turn/plan/updated" (v2::TurnPlanUpdatedNotification),
509510
ItemStarted => "item/started" (v2::ItemStartedNotification),
510511
ItemCompleted => "item/completed" (v2::ItemCompletedNotification),
511512
AgentMessageDelta => "item/agentMessage/delta" (v2::AgentMessageDeltaNotification),

codex-rs/app-server-protocol/src/protocol/v2.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use codex_protocol::items::AgentMessageContent as CoreAgentMessageContent;
1111
use codex_protocol::items::TurnItem as CoreTurnItem;
1212
use codex_protocol::models::ResponseItem;
1313
use codex_protocol::parse_command::ParsedCommand as CoreParsedCommand;
14+
use codex_protocol::plan_tool::PlanItemArg as CorePlanItemArg;
15+
use codex_protocol::plan_tool::StepStatus as CorePlanStepStatus;
1416
use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo;
1517
use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;
1618
use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
@@ -1210,6 +1212,51 @@ pub struct TurnDiffUpdatedNotification {
12101212
pub diff: String,
12111213
}
12121214

1215+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
1216+
#[serde(rename_all = "camelCase")]
1217+
#[ts(export_to = "v2/")]
1218+
pub struct TurnPlanUpdatedNotification {
1219+
pub turn_id: String,
1220+
pub explanation: Option<String>,
1221+
pub plan: Vec<TurnPlanStep>,
1222+
}
1223+
1224+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
1225+
#[serde(rename_all = "camelCase")]
1226+
#[ts(export_to = "v2/")]
1227+
pub struct TurnPlanStep {
1228+
pub step: String,
1229+
pub status: TurnPlanStepStatus,
1230+
}
1231+
1232+
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
1233+
#[serde(rename_all = "camelCase")]
1234+
#[ts(export_to = "v2/")]
1235+
pub enum TurnPlanStepStatus {
1236+
Pending,
1237+
InProgress,
1238+
Completed,
1239+
}
1240+
1241+
impl From<CorePlanItemArg> for TurnPlanStep {
1242+
fn from(value: CorePlanItemArg) -> Self {
1243+
Self {
1244+
step: value.step,
1245+
status: value.status.into(),
1246+
}
1247+
}
1248+
}
1249+
1250+
impl From<CorePlanStepStatus> for TurnPlanStepStatus {
1251+
fn from(value: CorePlanStepStatus) -> Self {
1252+
match value {
1253+
CorePlanStepStatus::Pending => Self::Pending,
1254+
CorePlanStepStatus::InProgress => Self::InProgress,
1255+
CorePlanStepStatus::Completed => Self::Completed,
1256+
}
1257+
}
1258+
}
1259+
12131260
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
12141261
#[serde(rename_all = "camelCase")]
12151262
#[ts(export_to = "v2/")]

codex-rs/app-server/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ The app-server streams JSON-RPC notifications while a turn is running. Each turn
244244

245245
- `turn/started``{ turn }` with the turn id, empty `items`, and `status: "inProgress"`.
246246
- `turn/completed``{ turn }` where `turn.status` is `completed`, `interrupted`, or `failed`; failures carry `{ error: { message, codexErrorInfo? } }`.
247+
- `turn/plan/updated``{ turnId, explanation?, plan }` whenever the agent shares or changes its plan; each `plan` entry is `{ step, status }` with `status` in `pending`, `inProgress`, or `completed`.
247248

248249
Today both notifications carry an empty `items` array even when item events were streamed; rely on `item/*` notifications for the canonical item list until this is fixed.
249250

codex-rs/app-server/src/bespoke_event_handling.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ use codex_app_server_protocol::TurnCompletedNotification;
4343
use codex_app_server_protocol::TurnDiffUpdatedNotification;
4444
use codex_app_server_protocol::TurnError;
4545
use codex_app_server_protocol::TurnInterruptResponse;
46+
use codex_app_server_protocol::TurnPlanStep;
47+
use codex_app_server_protocol::TurnPlanUpdatedNotification;
4648
use codex_app_server_protocol::TurnStatus;
4749
use codex_core::CodexConversation;
4850
use codex_core::parse_command::shlex_join;
@@ -60,6 +62,7 @@ use codex_core::protocol::TokenCountEvent;
6062
use codex_core::protocol::TurnDiffEvent;
6163
use codex_core::review_format::format_review_findings_block;
6264
use codex_protocol::ConversationId;
65+
use codex_protocol::plan_tool::UpdatePlanArgs;
6366
use codex_protocol::protocol::ReviewOutputEvent;
6467
use std::collections::HashMap;
6568
use std::convert::TryFrom;
@@ -570,6 +573,15 @@ pub(crate) async fn apply_bespoke_event_handling(
570573
)
571574
.await;
572575
}
576+
EventMsg::PlanUpdate(plan_update_event) => {
577+
handle_turn_plan_update(
578+
&event_turn_id,
579+
plan_update_event,
580+
api_version,
581+
outgoing.as_ref(),
582+
)
583+
.await;
584+
}
573585

574586
_ => {}
575587
}
@@ -592,6 +604,28 @@ async fn handle_turn_diff(
592604
}
593605
}
594606

607+
async fn handle_turn_plan_update(
608+
event_turn_id: &str,
609+
plan_update_event: UpdatePlanArgs,
610+
api_version: ApiVersion,
611+
outgoing: &OutgoingMessageSender,
612+
) {
613+
if let ApiVersion::V2 = api_version {
614+
let notification = TurnPlanUpdatedNotification {
615+
turn_id: event_turn_id.to_string(),
616+
explanation: plan_update_event.explanation,
617+
plan: plan_update_event
618+
.plan
619+
.into_iter()
620+
.map(TurnPlanStep::from)
621+
.collect(),
622+
};
623+
outgoing
624+
.send_server_notification(ServerNotification::TurnPlanUpdated(notification))
625+
.await;
626+
}
627+
}
628+
595629
async fn emit_turn_completed_with_status(
596630
conversation_id: ConversationId,
597631
event_turn_id: String,
@@ -1108,12 +1142,15 @@ mod tests {
11081142
use anyhow::Result;
11091143
use anyhow::anyhow;
11101144
use anyhow::bail;
1145+
use codex_app_server_protocol::TurnPlanStepStatus;
11111146
use codex_core::protocol::CreditsSnapshot;
11121147
use codex_core::protocol::McpInvocation;
11131148
use codex_core::protocol::RateLimitSnapshot;
11141149
use codex_core::protocol::RateLimitWindow;
11151150
use codex_core::protocol::TokenUsage;
11161151
use codex_core::protocol::TokenUsageInfo;
1152+
use codex_protocol::plan_tool::PlanItemArg;
1153+
use codex_protocol::plan_tool::StepStatus;
11171154
use mcp_types::CallToolResult;
11181155
use mcp_types::ContentBlock;
11191156
use mcp_types::TextContent;
@@ -1273,6 +1310,46 @@ mod tests {
12731310
Ok(())
12741311
}
12751312

1313+
#[tokio::test]
1314+
async fn test_handle_turn_plan_update_emits_notification_for_v2() -> Result<()> {
1315+
let (tx, mut rx) = mpsc::channel(CHANNEL_CAPACITY);
1316+
let outgoing = OutgoingMessageSender::new(tx);
1317+
let update = UpdatePlanArgs {
1318+
explanation: Some("need plan".to_string()),
1319+
plan: vec![
1320+
PlanItemArg {
1321+
step: "first".to_string(),
1322+
status: StepStatus::Pending,
1323+
},
1324+
PlanItemArg {
1325+
step: "second".to_string(),
1326+
status: StepStatus::Completed,
1327+
},
1328+
],
1329+
};
1330+
1331+
handle_turn_plan_update("turn-123", update, ApiVersion::V2, &outgoing).await;
1332+
1333+
let msg = rx
1334+
.recv()
1335+
.await
1336+
.ok_or_else(|| anyhow!("should send one notification"))?;
1337+
match msg {
1338+
OutgoingMessage::AppServerNotification(ServerNotification::TurnPlanUpdated(n)) => {
1339+
assert_eq!(n.turn_id, "turn-123");
1340+
assert_eq!(n.explanation.as_deref(), Some("need plan"));
1341+
assert_eq!(n.plan.len(), 2);
1342+
assert_eq!(n.plan[0].step, "first");
1343+
assert_eq!(n.plan[0].status, TurnPlanStepStatus::Pending);
1344+
assert_eq!(n.plan[1].step, "second");
1345+
assert_eq!(n.plan[1].status, TurnPlanStepStatus::Completed);
1346+
}
1347+
other => bail!("unexpected message: {other:?}"),
1348+
}
1349+
assert!(rx.try_recv().is_err(), "no extra messages expected");
1350+
Ok(())
1351+
}
1352+
12761353
#[tokio::test]
12771354
async fn test_handle_token_count_event_emits_usage_and_rate_limits() -> Result<()> {
12781355
let conversation_id = ConversationId::new();

0 commit comments

Comments
 (0)