From 167210ff1daffd1c4a582ffdb3d10d38e27696a3 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Thu, 11 Jun 2026 22:40:16 -0700 Subject: [PATCH] codex: adapt skill search to extension API changes --- codex-rs/Cargo.lock | 2 +- codex-rs/core/tests/suite/skills.rs | 31 ++++++----- codex-rs/ext/extension-api/tests/registry.rs | 1 + codex-rs/ext/goal/src/extension.rs | 1 + .../ext/image-generation/src/extension.rs | 1 + codex-rs/ext/skill-search/Cargo.toml | 2 +- codex-rs/ext/skill-search/src/extension.rs | 21 +++++--- codex-rs/ext/skill-search/src/tool.rs | 54 +++++++++++-------- codex-rs/ext/skills/src/extension.rs | 1 + codex-rs/ext/web-search/src/extension.rs | 1 + 10 files changed, 71 insertions(+), 44 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 2c41881481..3749f8e700 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -3815,9 +3815,9 @@ dependencies = [ name = "codex-skill-search-extension" version = "0.0.0" dependencies = [ - "async-trait", "bm25", "codex-core", + "codex-core-skills", "codex-extension-api", "codex-features", "codex-protocol", diff --git a/codex-rs/core/tests/suite/skills.rs b/codex-rs/core/tests/suite/skills.rs index 24f0aa151a..3369faf57a 100644 --- a/codex-rs/core/tests/suite/skills.rs +++ b/codex-rs/core/tests/suite/skills.rs @@ -200,24 +200,29 @@ async fn skill_search_tool_is_visible_and_returns_matching_repo_skill() -> Resul let (sandbox_policy, permission_profile) = turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path()); test.codex - .submit(Op::UserTurn { - environments: None, + .submit(Op::UserInput { items: vec![UserInput::Text { text: "Find the right repo skill.".to_string(), text_elements: Vec::new(), }], final_output_json_schema: None, - cwd: test.config.cwd.to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy, - permission_profile, - model: session_model, - effort: None, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, + responsesapi_client_metadata: None, + additional_context: Default::default(), + thread_settings: codex_protocol::protocol::ThreadSettingsOverrides { + environments: Some(local_selections(test.config.cwd.clone())), + approval_policy: Some(AskForApproval::Never), + sandbox_policy: Some(sandbox_policy), + permission_profile, + collaboration_mode: Some(codex_protocol::config_types::CollaborationMode { + mode: codex_protocol::config_types::ModeKind::Default, + settings: codex_protocol::config_types::Settings { + model: session_model, + reasoning_effort: None, + developer_instructions: None, + }, + }), + ..Default::default() + }, }) .await?; diff --git a/codex-rs/ext/extension-api/tests/registry.rs b/codex-rs/ext/extension-api/tests/registry.rs index 42cb18af62..8bbca65c4a 100644 --- a/codex-rs/ext/extension-api/tests/registry.rs +++ b/codex-rs/ext/extension-api/tests/registry.rs @@ -70,6 +70,7 @@ impl ToolContributor for AllContributors { &self, _session_store: &ExtensionData, _thread_store: &ExtensionData, + _turn_store: &ExtensionData, ) -> Vec>> { Vec::new() } diff --git a/codex-rs/ext/goal/src/extension.rs b/codex-rs/ext/goal/src/extension.rs index 8e5b20e7f5..3506c7c655 100644 --- a/codex-rs/ext/goal/src/extension.rs +++ b/codex-rs/ext/goal/src/extension.rs @@ -411,6 +411,7 @@ where &self, _session_store: &ExtensionData, thread_store: &ExtensionData, + _turn_store: &ExtensionData, ) -> Vec>> { let Some(runtime) = goal_runtime_handle(thread_store) else { return Vec::new(); diff --git a/codex-rs/ext/image-generation/src/extension.rs b/codex-rs/ext/image-generation/src/extension.rs index 6a0016c037..b373b2b75e 100644 --- a/codex-rs/ext/image-generation/src/extension.rs +++ b/codex-rs/ext/image-generation/src/extension.rs @@ -75,6 +75,7 @@ impl ToolContributor for ImageGenerationExtension { &self, _session_store: &ExtensionData, thread_store: &ExtensionData, + _turn_store: &ExtensionData, ) -> Vec>> { let Some(config) = thread_store.get::() else { return Vec::new(); diff --git a/codex-rs/ext/skill-search/Cargo.toml b/codex-rs/ext/skill-search/Cargo.toml index 6fb5b166b4..04a64b14df 100644 --- a/codex-rs/ext/skill-search/Cargo.toml +++ b/codex-rs/ext/skill-search/Cargo.toml @@ -13,9 +13,9 @@ doctest = false workspace = true [dependencies] -async-trait = { workspace = true } bm25 = { workspace = true } codex-core = { workspace = true } +codex-core-skills = { workspace = true } codex-extension-api = { workspace = true } codex-features = { workspace = true } codex-protocol = { workspace = true } diff --git a/codex-rs/ext/skill-search/src/extension.rs b/codex-rs/ext/skill-search/src/extension.rs index 89bcf6ffae..f817960f1a 100644 --- a/codex-rs/ext/skill-search/src/extension.rs +++ b/codex-rs/ext/skill-search/src/extension.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use codex_core::config::Config; -use codex_core::skills::SkillLoadOutcome; +use codex_core_skills::HostLoadedSkills; use codex_extension_api::ConfigContributor; use codex_extension_api::ContextContributor; use codex_extension_api::ExtensionData; @@ -54,10 +54,15 @@ impl ContextContributor for SkillSearchExtension { } impl ThreadLifecycleContributor for SkillSearchExtension { - fn on_thread_start(&self, input: ThreadStartInput<'_, Config>) { - input - .thread_store - .insert(SkillSearchExtensionConfig::from_config(input.config)); + fn on_thread_start<'a>( + &'a self, + input: ThreadStartInput<'a, Config>, + ) -> codex_extension_api::ExtensionFuture<'a, ()> { + Box::pin(async move { + input + .thread_store + .insert(SkillSearchExtensionConfig::from_config(input.config)); + }) } } @@ -88,9 +93,9 @@ impl ToolContributor for SkillSearchExtension { } let skills = turn_store - .get::() - .map_or_else(Vec::new, |outcome| { - outcome.allowed_skills_for_implicit_invocation() + .get::() + .map_or_else(Vec::new, |skills| { + skills.outcome().allowed_skills_for_implicit_invocation() }); let tool = turn_store.get_or_init(|| SkillSearchTool::new(skills)); vec![tool] diff --git a/codex-rs/ext/skill-search/src/tool.rs b/codex-rs/ext/skill-search/src/tool.rs index 3b8d496b2e..351b678a37 100644 --- a/codex-rs/ext/skill-search/src/tool.rs +++ b/codex-rs/ext/skill-search/src/tool.rs @@ -99,14 +99,13 @@ struct SkillSearchArgs { limit: Option, } -#[async_trait::async_trait] impl ToolExecutor for SkillSearchTool { fn tool_name(&self) -> ToolName { ToolName::plain(SKILL_SEARCH_TOOL_NAME) } - fn spec(&self) -> Option { - Some(ToolSpec::Function(ResponsesApiTool { + fn spec(&self) -> ToolSpec { + ToolSpec::Function(ResponsesApiTool { name: SKILL_SEARCH_TOOL_NAME.to_string(), description: "Search available Codex skills by relevance and return plain-text matches with their descriptions and SKILL.md paths.".to_string(), strict: false, @@ -128,31 +127,33 @@ impl ToolExecutor for SkillSearchTool { Some(false.into()), ), output_schema: None, - })) + }) } fn supports_parallel_tool_calls(&self) -> bool { true } - async fn handle(&self, call: ToolCall) -> Result, FunctionCallError> { - let args = parse_args(&call)?; - let query = args.query.trim(); - if query.is_empty() { - return Err(FunctionCallError::RespondToModel( - "query must not be empty".to_string(), - )); - } - let limit = args.limit.unwrap_or(DEFAULT_SKILL_SEARCH_LIMIT); - if limit == 0 { - return Err(FunctionCallError::RespondToModel( - "limit must be greater than zero".to_string(), - )); - } + fn handle(&self, call: ToolCall) -> codex_extension_api::ToolExecutorFuture<'_> { + Box::pin(async move { + let args = parse_args(&call)?; + let query = args.query.trim(); + if query.is_empty() { + return Err(FunctionCallError::RespondToModel( + "query must not be empty".to_string(), + )); + } + let limit = args.limit.unwrap_or(DEFAULT_SKILL_SEARCH_LIMIT); + if limit == 0 { + return Err(FunctionCallError::RespondToModel( + "limit must be greater than zero".to_string(), + )); + } - Ok(Box::new(PlainTextToolOutput { - text: self.search(query, limit), - })) + Ok(Box::new(PlainTextToolOutput { + text: self.search(query, limit), + }) as Box) + }) } } @@ -194,9 +195,14 @@ impl ToolOutput for PlainTextToolOutput { #[cfg(test)] mod tests { + use std::sync::Arc; + use codex_core::skills::SkillPolicy; + use codex_extension_api::ConversationHistory; + use codex_extension_api::NoopTurnItemEmitter; use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::protocol::SkillScope; + use codex_protocol::protocol::TruncationPolicy; use codex_tools::ToolPayload; use codex_utils_absolute_path::test_support::PathBufExt; use codex_utils_absolute_path::test_support::test_path_buf; @@ -224,8 +230,14 @@ mod tests { fn call(arguments: serde_json::Value) -> ToolCall { ToolCall { + turn_id: "turn-skill-search".to_string(), call_id: "call-skill-search".to_string(), tool_name: ToolName::plain(SKILL_SEARCH_TOOL_NAME), + model: "gpt-test".to_string(), + truncation_policy: TruncationPolicy::Bytes(1024), + conversation_history: ConversationHistory::default(), + turn_item_emitter: Arc::new(NoopTurnItemEmitter), + environments: Vec::new(), payload: ToolPayload::Function { arguments: arguments.to_string(), }, diff --git a/codex-rs/ext/skills/src/extension.rs b/codex-rs/ext/skills/src/extension.rs index 23dee27ebb..d9f7c9dd2d 100644 --- a/codex-rs/ext/skills/src/extension.rs +++ b/codex-rs/ext/skills/src/extension.rs @@ -138,6 +138,7 @@ where &self, session_store: &ExtensionData, _thread_store: &ExtensionData, + _turn_store: &ExtensionData, ) -> Vec>> { if !self.providers.has_orchestrator_provider() { return Vec::new(); diff --git a/codex-rs/ext/web-search/src/extension.rs b/codex-rs/ext/web-search/src/extension.rs index 688b504dec..6b8aa8b84e 100644 --- a/codex-rs/ext/web-search/src/extension.rs +++ b/codex-rs/ext/web-search/src/extension.rs @@ -111,6 +111,7 @@ impl ToolContributor for WebSearchExtension { &self, session_store: &ExtensionData, thread_store: &ExtensionData, + _turn_store: &ExtensionData, ) -> Vec>> { let Some(config) = thread_store.get::() else { return Vec::new();