From cf0c52504ed7fbe5d148649411884a010d71cd52 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 26 Jan 2026 19:21:52 +0000 Subject: [PATCH] switch to tool call --- .../src/agent/collab_completion_warning.rs | 95 ++++++++++++++++++- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/codex-rs/core/src/agent/collab_completion_warning.rs b/codex-rs/core/src/agent/collab_completion_warning.rs index 14030b51a0..4171658b9d 100644 --- a/codex-rs/core/src/agent/collab_completion_warning.rs +++ b/codex-rs/core/src/agent/collab_completion_warning.rs @@ -1,6 +1,10 @@ use std::sync::Arc; use codex_protocol::ThreadId; +use codex_protocol::models::FunctionCallOutputPayload; +use codex_protocol::models::ResponseItem; +use serde_json::json; +use uuid::Uuid; use crate::agent::AgentStatus; use crate::agent::status::is_final; @@ -22,8 +26,10 @@ pub(crate) fn spawn_collab_completion_warning_watcher( ) .await { - let message = completion_warning_message(agent_id, &status); - session.record_model_warning(message, &turn_context).await; + let items = synthetic_wait_items(agent_id, status); + session + .record_conversation_items(&turn_context, &items) + .await; } }); } @@ -59,6 +65,87 @@ async fn wait_for_final_status(session: &Session, agent_id: ThreadId) -> Option< } } -fn completion_warning_message(agent_id: ThreadId, status: &AgentStatus) -> String { - format!("Sub-agent {agent_id} finished with status {status:?}. Keep working.") +fn synthetic_wait_items(agent_id: ThreadId, status: AgentStatus) -> Vec { + tracing::info!("synthetic_wait_items: agent_id: {}, status: {:?}", agent_id, status); + let call_id = format!("synthetic-wait-{}", Uuid::new_v4()); + let agent_id_str = agent_id.to_string(); + let arguments = json!({ + "ids": [agent_id_str.clone()], + "timeout_ms": 300_000, + }) + .to_string(); + let output = json!({ + "status": { agent_id_str: status }, + "timed_out": false, + }) + .to_string(); + + let call = ResponseItem::FunctionCall { + id: None, + name: "wait".to_string(), + arguments, + call_id: call_id.clone(), + }; + let output = ResponseItem::FunctionCallOutput { + call_id, + output: FunctionCallOutputPayload { + content: output, + ..Default::default() + }, + }; + + vec![call, output] +} + +#[cfg(test)] +mod tests { + use super::synthetic_wait_items; + use crate::agent::AgentStatus; + use codex_protocol::ThreadId; + use codex_protocol::models::ResponseItem; + use pretty_assertions::assert_eq; + use serde_json::Value; + + #[test] + fn synthetic_wait_items_look_like_a_real_wait_result() { + let agent_id = + ThreadId::from_string("00000000-0000-7000-0000-000000000001").expect("valid id"); + let status = AgentStatus::Completed(Some("done".to_string())); + + let items = synthetic_wait_items(agent_id, status.clone()); + assert_eq!(items.len(), 2); + + let (call_id, arguments_json) = match &items[0] { + ResponseItem::FunctionCall { + name, + call_id, + arguments, + .. + } => { + assert_eq!(name, "wait"); + (call_id.clone(), arguments.clone()) + } + other => panic!("expected function call, got {other:?}"), + }; + + let args: Value = serde_json::from_str(&arguments_json).expect("arguments should be json"); + let agent_id_string = agent_id.to_string(); + assert_eq!(args["ids"][0].as_str(), Some(agent_id_string.as_str())); + + match &items[1] { + ResponseItem::FunctionCallOutput { + call_id: out_id, + output, + } => { + assert_eq!(out_id, &call_id); + let out: Value = + serde_json::from_str(&output.content).expect("output should be json"); + assert_eq!(out["timed_out"].as_bool(), Some(false)); + let expected_status = + serde_json::to_value(status).expect("status should serialize"); + assert_eq!(out["status"][agent_id_string.as_str()], expected_status); + } + other => panic!("expected function call output, got {other:?}"), + } + } }