Reimplement skills loading using SkillsManager + skills/list op. (#7914)

refactor the way we load and manage skills:
1. Move skill discovery/caching into SkillsManager and reuse it across
sessions.
2. Add the skills/list API (Op::ListSkills/SkillsListResponse) to fetch
skills for one or more cwds. Also update app-server for VSCE/App;
3. Trigger skills/list during session startup so UIs preload skills and
handle errors immediately.
This commit is contained in:
xl-openai
2025-12-14 09:58:17 -08:00
committed by GitHub
parent a2c86e5d88
commit 5d77d4db6b
29 changed files with 579 additions and 137 deletions

View File

@@ -81,6 +81,8 @@ use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::SessionConfiguredNotification;
use codex_app_server_protocol::SetDefaultModelParams;
use codex_app_server_protocol::SetDefaultModelResponse;
use codex_app_server_protocol::SkillsListParams;
use codex_app_server_protocol::SkillsListResponse;
use codex_app_server_protocol::Thread;
use codex_app_server_protocol::ThreadArchiveParams;
use codex_app_server_protocol::ThreadArchiveResponse;
@@ -373,6 +375,9 @@ impl CodexMessageProcessor {
self.send_unimplemented_error(request_id, "thread/compact")
.await;
}
ClientRequest::SkillsList { request_id, params } => {
self.skills_list(request_id, params).await;
}
ClientRequest::TurnStart { request_id, params } => {
self.turn_start(request_id, params).await;
}
@@ -2615,6 +2620,42 @@ impl CodexMessageProcessor {
.await;
}
async fn skills_list(&self, request_id: RequestId, params: SkillsListParams) {
let SkillsListParams { cwds } = params;
let cwds = if cwds.is_empty() {
vec![self.config.cwd.clone()]
} else {
cwds
};
let data = if self.config.features.enabled(Feature::Skills) {
let skills_manager = self.conversation_manager.skills_manager();
cwds.into_iter()
.map(|cwd| {
let outcome = skills_manager.skills_for_cwd(&cwd);
let errors = errors_to_info(&outcome.errors);
let skills = skills_to_info(&outcome.skills);
codex_app_server_protocol::SkillsListEntry {
cwd,
skills,
errors,
}
})
.collect()
} else {
cwds.into_iter()
.map(|cwd| codex_app_server_protocol::SkillsListEntry {
cwd,
skills: Vec::new(),
errors: Vec::new(),
})
.collect()
};
self.outgoing
.send_response(request_id, SkillsListResponse { data })
.await;
}
async fn interrupt_conversation(
&mut self,
request_id: RequestId,
@@ -3260,6 +3301,32 @@ impl CodexMessageProcessor {
}
}
fn skills_to_info(
skills: &[codex_core::skills::SkillMetadata],
) -> Vec<codex_app_server_protocol::SkillMetadata> {
skills
.iter()
.map(|skill| codex_app_server_protocol::SkillMetadata {
name: skill.name.clone(),
description: skill.description.clone(),
path: skill.path.clone(),
scope: skill.scope.into(),
})
.collect()
}
fn errors_to_info(
errors: &[codex_core::skills::SkillError],
) -> Vec<codex_app_server_protocol::SkillErrorInfo> {
errors
.iter()
.map(|err| codex_app_server_protocol::SkillErrorInfo {
path: err.path.clone(),
message: err.message.clone(),
})
.collect()
}
async fn derive_config_from_params(
overrides: ConfigOverrides,
cli_overrides: Option<std::collections::HashMap<String, serde_json::Value>>,