mirror of
https://github.com/openai/codex.git
synced 2026-11-13 21:30:47 +00:00
## Why The skills extension needs a real turn-time path before host, executor, or remote skills can be routed through it. The previous code was mostly a placeholder catalog/provider sketch, so there was no bounded available-skills fragment, no source-owned `SKILL.md` read, and no place for warnings or per-turn selection state to live. This PR makes `ext/skills` the authority-preserving flow for listing candidate skills and injecting only explicitly selected main prompts, without adding more of that logic to `codex-core`. ## What changed - Expands catalog entries with `main_prompt`, display path, short description, dependency metadata, enabled/prompt visibility flags, and authority/package-aware read requests. - Replaces the placeholder `providers/*` modules with `SkillProviderSource` and `SkillProviders`, routing list/read/search calls by source kind and surfacing provider failures as warnings. - Adds bounded available-skills rendering and `SKILL.md` main-prompt truncation before the fragments enter model context. - Resolves explicit skill selections from structured `UserInput::Skill`, skill-file mentions, `skill://...` paths, and plain `$skill` text mentions, then reads selected prompts through their owning provider. - Stores mutable per-thread skills config and per-turn catalog/selection/warning state. - Adds `install_with_providers` so tests and future host wiring can supply concrete providers. ## Testing - Not run locally. - Added `codex-rs/ext/skills/tests/skills_extension.rs` coverage for available-catalog injection, selected prompt injection through the owning provider, and prompt-hidden skills that remain invokable.
184 lines
5.1 KiB
Rust
184 lines
5.1 KiB
Rust
use std::fmt;
|
|
use std::sync::Arc;
|
|
|
|
use crate::catalog::SkillCatalog;
|
|
use crate::catalog::SkillProviderError;
|
|
use crate::catalog::SkillReadResult;
|
|
use crate::catalog::SkillSearchResult;
|
|
use crate::catalog::SkillSourceKind;
|
|
use crate::provider::SkillListQuery;
|
|
use crate::provider::SkillProvider;
|
|
use crate::provider::SkillReadRequest;
|
|
use crate::provider::SkillSearchRequest;
|
|
|
|
#[derive(Clone)]
|
|
pub struct SkillProviderSource {
|
|
kind: SkillSourceKind,
|
|
label: String,
|
|
provider: Arc<dyn SkillProvider>,
|
|
}
|
|
|
|
impl SkillProviderSource {
|
|
pub fn new(
|
|
kind: SkillSourceKind,
|
|
label: impl Into<String>,
|
|
provider: Arc<dyn SkillProvider>,
|
|
) -> Self {
|
|
Self {
|
|
kind,
|
|
label: label.into(),
|
|
provider,
|
|
}
|
|
}
|
|
|
|
pub fn host(label: impl Into<String>, provider: Arc<dyn SkillProvider>) -> Self {
|
|
Self::new(SkillSourceKind::Host, label, provider)
|
|
}
|
|
|
|
pub fn executor(label: impl Into<String>, provider: Arc<dyn SkillProvider>) -> Self {
|
|
Self::new(SkillSourceKind::Executor, label, provider)
|
|
}
|
|
|
|
pub fn remote(label: impl Into<String>, provider: Arc<dyn SkillProvider>) -> Self {
|
|
Self::new(SkillSourceKind::Remote, label, provider)
|
|
}
|
|
|
|
fn should_list(&self, query: &SkillListQuery) -> bool {
|
|
match &self.kind {
|
|
SkillSourceKind::Host => query.include_host_skills,
|
|
SkillSourceKind::Executor => !query.executor_authorities.is_empty(),
|
|
SkillSourceKind::Remote => query.include_remote_skills,
|
|
SkillSourceKind::Custom(_) => true,
|
|
}
|
|
}
|
|
|
|
fn owns_kind(&self, kind: &SkillSourceKind) -> bool {
|
|
&self.kind == kind
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for SkillProviderSource {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
formatter
|
|
.debug_struct("SkillProviderSource")
|
|
.field("kind", &self.kind)
|
|
.field("label", &self.label)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Default, Debug)]
|
|
pub struct SkillProviders {
|
|
sources: Vec<SkillProviderSource>,
|
|
}
|
|
|
|
impl SkillProviders {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn with_provider(mut self, source: SkillProviderSource) -> Self {
|
|
self.sources.push(source);
|
|
self
|
|
}
|
|
|
|
pub fn with_host_provider(mut self, provider: Arc<dyn SkillProvider>) -> Self {
|
|
self.sources
|
|
.push(SkillProviderSource::host("host", provider));
|
|
self
|
|
}
|
|
|
|
pub fn with_executor_provider(mut self, provider: Arc<dyn SkillProvider>) -> Self {
|
|
self.sources
|
|
.push(SkillProviderSource::executor("executor", provider));
|
|
self
|
|
}
|
|
|
|
pub fn with_remote_provider(mut self, provider: Arc<dyn SkillProvider>) -> Self {
|
|
self.sources
|
|
.push(SkillProviderSource::remote("remote", provider));
|
|
self
|
|
}
|
|
|
|
pub(crate) async fn list_for_turn(&self, query: SkillListQuery) -> SkillCatalog {
|
|
let mut catalog = SkillCatalog::default();
|
|
|
|
for source in self
|
|
.sources
|
|
.iter()
|
|
.filter(|source| source.should_list(&query))
|
|
{
|
|
extend_catalog(
|
|
&mut catalog,
|
|
source.provider.list(query.clone()).await,
|
|
source.label.as_str(),
|
|
);
|
|
}
|
|
|
|
catalog
|
|
}
|
|
|
|
pub(crate) async fn read(
|
|
&self,
|
|
request: SkillReadRequest,
|
|
) -> Result<SkillReadResult, SkillProviderError> {
|
|
let mut last_error = None;
|
|
for source in self
|
|
.sources
|
|
.iter()
|
|
.filter(|source| source.owns_kind(&request.authority.kind))
|
|
{
|
|
match source.provider.read(request.clone()).await {
|
|
Ok(result) => return Ok(result),
|
|
Err(err) => last_error = Some(err),
|
|
}
|
|
}
|
|
|
|
match last_error {
|
|
Some(err) => Err(err),
|
|
None => Err(SkillProviderError::new(format!(
|
|
"{} skill provider is not configured",
|
|
request.authority.kind
|
|
))),
|
|
}
|
|
}
|
|
|
|
pub async fn search(
|
|
&self,
|
|
request: SkillSearchRequest,
|
|
) -> Result<SkillSearchResult, SkillProviderError> {
|
|
let mut last_error = None;
|
|
for source in self
|
|
.sources
|
|
.iter()
|
|
.filter(|source| source.owns_kind(&request.authority.kind))
|
|
{
|
|
match source.provider.search(request.clone()).await {
|
|
Ok(result) => return Ok(result),
|
|
Err(err) => last_error = Some(err),
|
|
}
|
|
}
|
|
|
|
match last_error {
|
|
Some(err) => Err(err),
|
|
None => Err(SkillProviderError::new(format!(
|
|
"{} skill provider is not configured",
|
|
request.authority.kind
|
|
))),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn extend_catalog(
|
|
catalog: &mut SkillCatalog,
|
|
result: Result<SkillCatalog, SkillProviderError>,
|
|
label: &str,
|
|
) {
|
|
match result {
|
|
Ok(source_catalog) => catalog.extend(source_catalog),
|
|
Err(err) => catalog
|
|
.warnings
|
|
.push(format!("{label} skills unavailable: {}", err.message)),
|
|
}
|
|
}
|