use codex_extension_api::ToolCall; use codex_extension_api::ToolExecutor; use codex_extension_api::ToolExecutorFuture; use codex_extension_api::ToolName; use codex_extension_api::ToolSpec; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use crate::catalog::SkillCatalogEntry; use crate::render::truncate_catalog_skill_description; use crate::warnings::bounded_warnings; use super::MAX_HANDLE_BYTES; use super::SkillToolAuthority; use super::SkillToolContext; use super::external_json_output; use super::is_bounded_handle; use super::parse_args; use super::skill_function_tool; use super::skill_tool_name; const TOOL_NAME: &str = "list"; #[derive(Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] struct ListArgs { authority: SkillToolAuthority, } #[derive(Debug, Eq, JsonSchema, PartialEq, Serialize)] #[schemars(deny_unknown_fields)] struct ListedSkill { authority: SkillToolAuthority, package: String, name: String, description: String, main_resource: String, } #[derive(Debug, Eq, JsonSchema, PartialEq, Serialize)] #[schemars(deny_unknown_fields)] struct ListResponse { skills: Vec, warnings: Vec, } #[derive(Clone)] pub(super) struct ListTool { pub(super) context: SkillToolContext, } impl ToolExecutor for ListTool { fn tool_name(&self) -> ToolName { skill_tool_name(TOOL_NAME) } fn spec(&self) -> ToolSpec { skill_function_tool::( TOOL_NAME, "List enabled skills owned by the requested authority. Only orchestrator-owned skills are currently supported. Returns the opaque package and main-resource handles required by skills.read.", ) } fn handle(&self, call: ToolCall) -> ToolExecutorFuture<'_> { Box::pin(async move { let args: ListArgs = parse_args(&call)?; let authority = args.authority.into_authority(); let catalog = self.context.catalog(&call.turn_id, args.authority).await; let response = ListResponse { skills: catalog .entries .into_iter() .filter(|entry| entry.enabled && entry.authority == authority) .filter_map(listed_skill) .collect(), warnings: bounded_warnings(&catalog.warnings), }; external_json_output(&response) }) } } fn listed_skill(entry: SkillCatalogEntry) -> Option { let authority = SkillToolAuthority::from_authority(&entry.authority)?; if !is_bounded_handle(&entry.id.0, MAX_HANDLE_BYTES) || !is_bounded_handle(entry.main_prompt.as_str(), MAX_HANDLE_BYTES) { return None; } Some(ListedSkill { authority, package: entry.id.0, name: entry.name, description: truncate_catalog_skill_description(&entry.description).into_owned(), main_resource: entry.main_prompt.as_str().to_string(), }) }