agcodex_core/
plan_tool.rs1use std::collections::BTreeMap;
2use std::sync::LazyLock;
3
4use crate::codex::Session;
5use crate::models::FunctionCallOutputPayload;
6use crate::models::ResponseInputItem;
7use crate::openai_tools::JsonSchema;
8use crate::openai_tools::OpenAiTool;
9use crate::openai_tools::ResponsesApiTool;
10use crate::protocol::Event;
11use crate::protocol::EventMsg;
12
13pub use agcodex_protocol::plan_tool::PlanItemArg;
16pub use agcodex_protocol::plan_tool::StepStatus;
17pub use agcodex_protocol::plan_tool::UpdatePlanArgs;
18
19pub(crate) static PLAN_TOOL: LazyLock<OpenAiTool> = LazyLock::new(|| {
22 let mut plan_item_props = BTreeMap::new();
23 plan_item_props.insert("step".to_string(), JsonSchema::String { description: None });
24 plan_item_props.insert(
25 "status".to_string(),
26 JsonSchema::String {
27 description: Some("One of: pending, in_progress, completed".to_string()),
28 },
29 );
30
31 let plan_items_schema = JsonSchema::Array {
32 description: Some("The list of steps".to_string()),
33 items: Box::new(JsonSchema::Object {
34 properties: plan_item_props,
35 required: Some(vec!["step".to_string(), "status".to_string()]),
36 additional_properties: Some(false),
37 }),
38 };
39
40 let mut properties = BTreeMap::new();
41 properties.insert(
42 "explanation".to_string(),
43 JsonSchema::String { description: None },
44 );
45 properties.insert("plan".to_string(), plan_items_schema);
46
47 OpenAiTool::Function(ResponsesApiTool {
48 name: "update_plan".to_string(),
49 description: r#"Updates the task plan.
50Provide an optional explanation and a list of plan items, each with a step and status.
51At most one step can be in_progress at a time.
52"#
53 .to_string(),
54 strict: false,
55 parameters: JsonSchema::Object {
56 properties,
57 required: Some(vec!["plan".to_string()]),
58 additional_properties: Some(false),
59 },
60 })
61});
62
63pub(crate) async fn handle_update_plan(
67 session: &Session,
68 arguments: String,
69 sub_id: String,
70 call_id: String,
71) -> ResponseInputItem {
72 match parse_update_plan_arguments(arguments, &call_id) {
73 Ok(args) => {
74 let output = ResponseInputItem::FunctionCallOutput {
75 call_id,
76 output: FunctionCallOutputPayload {
77 content: "Plan updated".to_string(),
78 success: Some(true),
79 },
80 };
81 session
82 .send_event(Event {
83 id: sub_id.to_string(),
84 msg: EventMsg::PlanUpdate(args),
85 })
86 .await;
87 output
88 }
89 Err(output) => *output,
90 }
91}
92
93fn parse_update_plan_arguments(
94 arguments: String,
95 call_id: &str,
96) -> Result<UpdatePlanArgs, Box<ResponseInputItem>> {
97 match serde_json::from_str::<UpdatePlanArgs>(&arguments) {
98 Ok(args) => Ok(args),
99 Err(e) => {
100 let output = ResponseInputItem::FunctionCallOutput {
101 call_id: call_id.to_string(),
102 output: FunctionCallOutputPayload {
103 content: format!("failed to parse function arguments: {e}"),
104 success: None,
105 },
106 };
107 Err(Box::new(output))
108 }
109 }
110}