//! Responses API tool definitions for persisted thread goals. //! //! These specs expose goal read/update primitives to the model while keeping //! usage accounting system-managed. use crate::JsonSchema; use crate::ResponsesApiTool; use crate::ToolSpec; use serde_json::json; use std::collections::BTreeMap; pub const GET_GOAL_TOOL_NAME: &str = "get_goal"; pub const CREATE_GOAL_TOOL_NAME: &str = "create_goal"; pub const UPDATE_GOAL_TOOL_NAME: &str = "update_goal"; pub fn create_get_goal_tool() -> ToolSpec { ToolSpec::Function(ResponsesApiTool { name: GET_GOAL_TOOL_NAME.to_string(), description: "Get the current goal for this thread, including status, budgets, token and elapsed-time usage, and remaining token budget." .to_string(), strict: false, defer_loading: None, parameters: JsonSchema::object(BTreeMap::new(), Some(Vec::new()), Some(false.into())), output_schema: None, }) } pub fn create_create_goal_tool() -> ToolSpec { let properties = BTreeMap::from([ ( "objective".to_string(), JsonSchema::string(Some( "Required. The concrete objective to start pursuing. This starts a new active goal only when no goal is currently defined; if a goal already exists, this tool fails." .to_string(), )), ), ( "token_budget".to_string(), JsonSchema::integer(Some( "Optional positive token budget for the new active goal.".to_string(), )), ), ]); ToolSpec::Function(ResponsesApiTool { name: CREATE_GOAL_TOOL_NAME.to_string(), description: format!( r#"Create a goal only when explicitly requested by the user or system/developer instructions; do not infer goals from ordinary tasks. Set token_budget only when an explicit token budget is requested. Fails if a goal exists; use {UPDATE_GOAL_TOOL_NAME} only for status."# ), strict: false, defer_loading: None, parameters: JsonSchema::object( properties, /*required*/ Some(vec!["objective".to_string()]), Some(false.into()), ), output_schema: None, }) } pub fn create_update_goal_tool() -> ToolSpec { let properties = BTreeMap::from([( "status".to_string(), JsonSchema::string_enum( vec![json!("complete")], Some( "Required. Set to complete only when the objective is achieved and no required work remains." .to_string(), ), ), )]); ToolSpec::Function(ResponsesApiTool { name: UPDATE_GOAL_TOOL_NAME.to_string(), description: r#"Update the existing goal. Use this tool only to mark the goal achieved. Set status to `complete` only when the objective has actually been achieved and no required work remains. Do not mark a goal complete merely because its budget is nearly exhausted or because you are stopping work. You cannot use this tool to pause, resume, or budget-limit a goal; those status changes are controlled by the user or system. When marking a budgeted goal achieved with status `complete`, report the final token usage from the tool result to the user."# .to_string(), strict: false, defer_loading: None, parameters: JsonSchema::object( properties, /*required*/ Some(vec!["status".to_string()]), Some(false.into()), ), output_schema: None, }) } #[cfg(test)] mod tests { use super::*; #[test] fn update_goal_tool_only_exposes_complete_status() { let ToolSpec::Function(tool) = create_update_goal_tool() else { panic!("update_goal should be a function tool"); }; let status = tool .parameters .properties .as_ref() .and_then(|properties| properties.get("status")) .expect("status property should exist"); assert_eq!(status.enum_values, Some(vec![json!("complete")])); } }