mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Implement v1 skills extension prompt injection (#26167)
## 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.
This commit is contained in:
15 files changed
+1001
-335
No files matched your search
@@ -1,9 +1,39 @@
|
||||
use codex_core_skills::model::SkillDependencies;
|
||||
|
||||
/// Source authority that owns a skill package and must be used to read it.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum SkillSourceKind {
|
||||
/// Codex-hosted skills, including bundled, user, repo, plugin-installed,
|
||||
/// and downloaded/materialized remote skills.
|
||||
Host,
|
||||
/// Skills owned by an execution environment.
|
||||
Executor,
|
||||
/// Skills read through an authenticated remote catalog/API.
|
||||
Remote,
|
||||
/// Extension-private source kind for future providers that do not fit an
|
||||
/// existing transport category.
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl SkillSourceKind {
|
||||
pub fn custom(kind: impl Into<String>) -> Self {
|
||||
Self::Custom(kind.into())
|
||||
}
|
||||
|
||||
fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Self::Host => "host",
|
||||
Self::Executor => "executor",
|
||||
Self::Remote => "remote",
|
||||
Self::Custom(kind) => kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SkillSourceKind {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.as_str().fmt(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque authority identity for list/read routing.
|
||||
@@ -37,7 +67,66 @@ pub struct SkillCatalogEntry {
|
||||
pub authority: SkillAuthority,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub entrypoint: SkillResourceId,
|
||||
pub short_description: Option<String>,
|
||||
pub main_prompt: SkillResourceId,
|
||||
pub display_path: Option<String>,
|
||||
pub dependencies: Option<SkillDependencies>,
|
||||
pub enabled: bool,
|
||||
pub prompt_visible: bool,
|
||||
}
|
||||
|
||||
impl SkillCatalogEntry {
|
||||
pub fn new(
|
||||
id: SkillPackageId,
|
||||
authority: SkillAuthority,
|
||||
name: impl Into<String>,
|
||||
description: impl Into<String>,
|
||||
main_prompt: SkillResourceId,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
authority,
|
||||
name: name.into(),
|
||||
description: description.into(),
|
||||
short_description: None,
|
||||
main_prompt,
|
||||
display_path: None,
|
||||
dependencies: None,
|
||||
enabled: true,
|
||||
prompt_visible: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_short_description(mut self, short_description: Option<String>) -> Self {
|
||||
self.short_description = short_description;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_display_path(mut self, display_path: impl Into<String>) -> Self {
|
||||
self.display_path = Some(display_path.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_dependencies(mut self, dependencies: Option<SkillDependencies>) -> Self {
|
||||
self.dependencies = dependencies;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn disabled(mut self) -> Self {
|
||||
self.enabled = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn hidden_from_prompt(mut self) -> Self {
|
||||
self.prompt_visible = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn rendered_path(&self) -> &str {
|
||||
self.display_path
|
||||
.as_deref()
|
||||
.unwrap_or(self.main_prompt.0.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Merged catalog for one turn.
|
||||
@@ -49,12 +138,23 @@ pub struct SkillCatalog {
|
||||
|
||||
impl SkillCatalog {
|
||||
pub fn extend(&mut self, other: SkillCatalog) {
|
||||
// TODO(skills-extension): dedupe by authority-bound id first, then
|
||||
// apply name precedence/conflict rules for user-facing mention
|
||||
// resolution. Names are not stable identities.
|
||||
self.entries.extend(other.entries);
|
||||
for entry in other.entries {
|
||||
self.push_entry(entry);
|
||||
}
|
||||
self.warnings.extend(other.warnings);
|
||||
}
|
||||
|
||||
pub fn push_entry(&mut self, entry: SkillCatalogEntry) {
|
||||
if self
|
||||
.entries
|
||||
.iter()
|
||||
.any(|existing| existing.authority == entry.authority && existing.id == entry.id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self.entries.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// Contents returned after resolving a skill resource through its owner.
|
||||
@@ -83,4 +183,20 @@ pub struct SkillProviderError {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl SkillProviderError {
|
||||
pub fn new(message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SkillProviderError {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.message.fmt(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for SkillProviderError {}
|
||||
|
||||
pub type SkillProviderResult<T> = Result<T, SkillProviderError>;
|
||||
Reference in new issue
Block a user