mirror of
https://github.com/openai/codex.git
synced 2026-10-25 18:28:38 +00:00
Extract runtime skill source boundary
This commit is contained in:
@@ -7,6 +7,7 @@ pub mod model;
|
||||
pub mod remote;
|
||||
pub mod render;
|
||||
mod root_loader;
|
||||
pub mod runtime;
|
||||
pub mod service;
|
||||
mod skill_instructions;
|
||||
pub mod system;
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::hash::Hash;
|
||||
use std::hash::Hasher;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_utils_path_uri::PathUri;
|
||||
|
||||
use crate::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 owned by the orchestrator rather than an execution environment.
|
||||
Orchestrator,
|
||||
/// A source kind for 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::Orchestrator => "orchestrator",
|
||||
Self::Custom(kind) => kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SkillSourceKind {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.as_str().fmt(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque authority identity for list/read routing.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct SkillAuthority {
|
||||
pub kind: SkillSourceKind,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
impl SkillAuthority {
|
||||
pub fn new(kind: SkillSourceKind, id: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
id: id.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque package id. Callers should not parse local paths out of this value.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct SkillPackageId(pub String);
|
||||
|
||||
/// Opaque resource id inside a skill package, optionally bound to the
|
||||
/// environment path that owns its contents.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct SkillResourceId {
|
||||
id: String,
|
||||
environment_path: Option<EnvironmentSkillResource>,
|
||||
}
|
||||
|
||||
impl SkillResourceId {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
environment_path: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn environment(
|
||||
id: impl Into<String>,
|
||||
environment_id: impl Into<String>,
|
||||
path: PathUri,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
environment_path: Some(EnvironmentSkillResource {
|
||||
environment_id: environment_id.into(),
|
||||
path,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn environment_path(&self) -> Option<(&str, &PathUri)> {
|
||||
self.environment_path
|
||||
.as_ref()
|
||||
.map(|resource| (resource.environment_id.as_str(), &resource.path))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
struct EnvironmentSkillResource {
|
||||
environment_id: String,
|
||||
path: PathUri,
|
||||
}
|
||||
|
||||
/// Metadata shown in the always-visible skills catalog.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SkillCatalogEntry {
|
||||
pub id: SkillPackageId,
|
||||
pub authority: SkillAuthority,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
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 fn rendered_path(&self) -> &str {
|
||||
self.display_path
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| self.main_prompt.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Merged catalog for one model step.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct SkillCatalog {
|
||||
pub entries: Vec<SkillCatalogEntry>,
|
||||
pub warnings: Vec<String>,
|
||||
}
|
||||
|
||||
impl SkillCatalog {
|
||||
pub fn extend(&mut self, other: SkillCatalog) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// A request to read one resource from the source that listed it.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SkillReadRequest {
|
||||
pub authority: SkillAuthority,
|
||||
pub package: SkillPackageId,
|
||||
pub resource: SkillResourceId,
|
||||
}
|
||||
|
||||
/// Contents returned after resolving a skill resource through its owner.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SkillReadResult {
|
||||
pub resource: SkillResourceId,
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SkillSourceError {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl SkillSourceError {
|
||||
pub fn new(message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SkillSourceError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.message.fmt(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for SkillSourceError {}
|
||||
|
||||
pub type SkillSourceResult<T> = Result<T, SkillSourceError>;
|
||||
|
||||
/// Boxed future used to keep heterogeneous runtime skill sources object-safe.
|
||||
pub type SkillSourceFuture<'a, T> = Pin<Box<dyn Future<Output = SkillSourceResult<T>> + Send + 'a>>;
|
||||
|
||||
/// Opaque identity of the runtime owner captured by a skill source.
|
||||
#[derive(Clone)]
|
||||
pub struct SkillSourceIdentity(Arc<dyn Any + Send + Sync>);
|
||||
|
||||
impl SkillSourceIdentity {
|
||||
pub fn from_owner<T>(owner: Arc<T>) -> Self
|
||||
where
|
||||
T: Any + Send + Sync,
|
||||
{
|
||||
Self(owner)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for SkillSourceIdentity {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.debug_tuple("SkillSourceIdentity").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for SkillSourceIdentity {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for SkillSourceIdentity {}
|
||||
|
||||
impl Hash for SkillSourceIdentity {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
(Arc::as_ptr(&self.0) as *const ()).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// One bound source of runtime skills.
|
||||
///
|
||||
/// Implementations capture the authority needed to list and read their skills,
|
||||
/// such as a host snapshot, one executor filesystem, or an orchestrator client.
|
||||
/// A resource returned by [`SkillSource::list`] must be read through the same
|
||||
/// source instead of being converted into an ambient local path.
|
||||
pub trait SkillSource: Send + Sync {
|
||||
/// Returns the authority category owned by this source.
|
||||
fn kind(&self) -> SkillSourceKind;
|
||||
|
||||
/// Returns the stable identity of the runtime owner captured by this source.
|
||||
fn identity(&self) -> SkillSourceIdentity;
|
||||
|
||||
/// Lists the skills available from the authority captured by this source.
|
||||
fn list(&self) -> SkillSourceFuture<'_, SkillCatalog>;
|
||||
|
||||
/// Reads a resource previously listed by this source.
|
||||
fn read(&self, request: SkillReadRequest) -> SkillSourceFuture<'_, SkillReadResult>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RegisteredSkillSource {
|
||||
label: String,
|
||||
source: Arc<dyn SkillSource>,
|
||||
}
|
||||
|
||||
/// Bound skill sources used to build and read one runtime catalog.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SkillSources {
|
||||
sources: Vec<RegisteredSkillSource>,
|
||||
}
|
||||
|
||||
impl SkillSources {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_source(mut self, label: impl Into<String>, source: Arc<dyn SkillSource>) -> Self {
|
||||
self.sources.push(RegisteredSkillSource {
|
||||
label: label.into(),
|
||||
source,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn list(&self) -> SkillCatalog {
|
||||
let mut catalog = SkillCatalog::default();
|
||||
for source in &self.sources {
|
||||
match source.source.list().await {
|
||||
Ok(source_catalog) => catalog.extend(source_catalog),
|
||||
Err(err) => catalog.warnings.push(format!(
|
||||
"{} skills unavailable: {}",
|
||||
source.label, err.message
|
||||
)),
|
||||
}
|
||||
}
|
||||
catalog
|
||||
}
|
||||
|
||||
pub async fn list_kind(&self, kind: &SkillSourceKind) -> SkillSourceResult<SkillCatalog> {
|
||||
let mut catalog = SkillCatalog::default();
|
||||
for source in self
|
||||
.sources
|
||||
.iter()
|
||||
.filter(|source| source.source.kind() == *kind)
|
||||
{
|
||||
let source_catalog = source.source.list().await.map_err(|err| {
|
||||
SkillSourceError::new(format!(
|
||||
"{} skills unavailable: {}",
|
||||
source.label, err.message
|
||||
))
|
||||
})?;
|
||||
catalog.extend(source_catalog);
|
||||
}
|
||||
Ok(catalog)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for SkillSources {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter
|
||||
.debug_list()
|
||||
.entries(self.sources.iter().map(|source| &source.label))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -1,212 +1,12 @@
|
||||
use codex_core_skills::model::SkillDependencies;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
|
||||
/// 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 owned by the orchestrator rather than an execution environment.
|
||||
Orchestrator,
|
||||
/// 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::Orchestrator => "orchestrator",
|
||||
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.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct SkillAuthority {
|
||||
pub kind: SkillSourceKind,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
impl SkillAuthority {
|
||||
pub fn new(kind: SkillSourceKind, id: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
id: id.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque package id. Callers should not parse local paths out of this value.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct SkillPackageId(pub String);
|
||||
|
||||
/// Opaque resource id inside a skill package, optionally bound to the
|
||||
/// environment path that owns its contents.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct SkillResourceId {
|
||||
id: String,
|
||||
environment_path: Option<EnvironmentSkillResource>,
|
||||
}
|
||||
|
||||
impl SkillResourceId {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
environment_path: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn environment(
|
||||
id: impl Into<String>,
|
||||
environment_id: impl Into<String>,
|
||||
path: PathUri,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
environment_path: Some(EnvironmentSkillResource {
|
||||
environment_id: environment_id.into(),
|
||||
path,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub(crate) fn environment_path(&self) -> Option<(&str, &PathUri)> {
|
||||
self.environment_path
|
||||
.as_ref()
|
||||
.map(|resource| (resource.environment_id.as_str(), &resource.path))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
struct EnvironmentSkillResource {
|
||||
environment_id: String,
|
||||
path: PathUri,
|
||||
}
|
||||
|
||||
/// Metadata shown in the always-visible skills catalog.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SkillCatalogEntry {
|
||||
pub id: SkillPackageId,
|
||||
pub authority: SkillAuthority,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
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_else(|| self.main_prompt.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Merged catalog for one turn.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct SkillCatalog {
|
||||
pub entries: Vec<SkillCatalogEntry>,
|
||||
pub warnings: Vec<String>,
|
||||
}
|
||||
|
||||
impl SkillCatalog {
|
||||
pub fn extend(&mut self, other: SkillCatalog) {
|
||||
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.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SkillReadResult {
|
||||
pub resource: SkillResourceId,
|
||||
pub contents: String,
|
||||
}
|
||||
pub use codex_core_skills::runtime::SkillAuthority;
|
||||
pub use codex_core_skills::runtime::SkillCatalog;
|
||||
pub use codex_core_skills::runtime::SkillCatalogEntry;
|
||||
pub use codex_core_skills::runtime::SkillPackageId;
|
||||
pub use codex_core_skills::runtime::SkillReadResult;
|
||||
pub use codex_core_skills::runtime::SkillResourceId;
|
||||
pub use codex_core_skills::runtime::SkillSourceError as SkillProviderError;
|
||||
pub use codex_core_skills::runtime::SkillSourceKind;
|
||||
pub use codex_core_skills::runtime::SkillSourceResult as SkillProviderResult;
|
||||
|
||||
/// Search results for a package whose files are not readable through ordinary
|
||||
/// executor filesystem access.
|
||||
@@ -221,26 +21,3 @@ pub struct SkillSearchMatch {
|
||||
pub title: String,
|
||||
pub snippet: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
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>;
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_core_skills::runtime::SkillReadRequest as RuntimeSkillReadRequest;
|
||||
use codex_core_skills::runtime::SkillSource;
|
||||
use codex_core_skills::runtime::SkillSourceFuture;
|
||||
use codex_core_skills::runtime::SkillSourceIdentity;
|
||||
use codex_core_skills::runtime::SkillSources;
|
||||
use codex_mcp::McpResourceClient;
|
||||
|
||||
use crate::catalog::SkillCatalog;
|
||||
use crate::catalog::SkillProviderError;
|
||||
use crate::catalog::SkillProviderResult;
|
||||
@@ -17,6 +24,7 @@ pub struct SkillProviderSource {
|
||||
kind: SkillSourceKind,
|
||||
label: String,
|
||||
provider: Arc<dyn SkillProvider>,
|
||||
identity: SkillSourceIdentity,
|
||||
}
|
||||
|
||||
impl SkillProviderSource {
|
||||
@@ -25,10 +33,12 @@ impl SkillProviderSource {
|
||||
label: impl Into<String>,
|
||||
provider: Arc<dyn SkillProvider>,
|
||||
) -> Self {
|
||||
let identity = SkillSourceIdentity::from_owner(Arc::new(Arc::clone(&provider)));
|
||||
Self {
|
||||
kind,
|
||||
label: label.into(),
|
||||
provider,
|
||||
identity,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +66,17 @@ impl SkillProviderSource {
|
||||
fn owns_kind(&self, kind: &SkillSourceKind) -> bool {
|
||||
&self.kind == kind
|
||||
}
|
||||
|
||||
fn bind(&self, query: SkillListQuery) -> Arc<dyn SkillSource> {
|
||||
Arc::new(BoundSkillProvider {
|
||||
kind: self.kind.clone(),
|
||||
provider: Arc::clone(&self.provider),
|
||||
identity: self.identity.clone(),
|
||||
host_snapshot: query.host_snapshot.clone(),
|
||||
mcp_resources: query.mcp_resources.clone(),
|
||||
query: Some(query),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for SkillProviderSource {
|
||||
@@ -108,74 +129,48 @@ impl SkillProviders {
|
||||
}
|
||||
|
||||
pub(crate) async fn list_for_turn(&self, query: SkillListQuery) -> SkillCatalog {
|
||||
self.list_matching(&query, |source| source.should_list(&query))
|
||||
.await
|
||||
self.sources_for_turn(query).list().await
|
||||
}
|
||||
|
||||
pub(crate) async fn list_orchestrator_for_turn(
|
||||
&self,
|
||||
query: SkillListQuery,
|
||||
) -> SkillProviderResult<SkillCatalog> {
|
||||
let mut catalog = SkillCatalog::default();
|
||||
|
||||
for source in self
|
||||
.sources
|
||||
.iter()
|
||||
.filter(|source| source.kind == SkillSourceKind::Orchestrator)
|
||||
{
|
||||
let source_catalog = source.provider.list(query.clone()).await.map_err(|err| {
|
||||
SkillProviderError::new(format!(
|
||||
"{} skills unavailable: {}",
|
||||
source.label, err.message
|
||||
))
|
||||
})?;
|
||||
catalog.extend(source_catalog);
|
||||
}
|
||||
|
||||
Ok(catalog)
|
||||
self.sources_for_turn(query)
|
||||
.list_kind(&SkillSourceKind::Orchestrator)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn list_matching(
|
||||
&self,
|
||||
query: &SkillListQuery,
|
||||
should_list: impl Fn(&SkillProviderSource) -> bool,
|
||||
) -> SkillCatalog {
|
||||
let mut catalog = SkillCatalog::default();
|
||||
|
||||
for source in self.sources.iter().filter(|source| should_list(source)) {
|
||||
extend_catalog(
|
||||
&mut catalog,
|
||||
source.provider.list(query.clone()).await,
|
||||
source.label.as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
catalog
|
||||
fn sources_for_turn(&self, query: SkillListQuery) -> SkillSources {
|
||||
self.sources
|
||||
.iter()
|
||||
.filter(|source| source.should_list(&query))
|
||||
.fold(SkillSources::new(), |sources, source| {
|
||||
sources.with_source(source.label.clone(), source.bind(query.clone()))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn read(
|
||||
&self,
|
||||
request: SkillReadRequest,
|
||||
) -> Result<SkillReadResult, SkillProviderError> {
|
||||
let mut last_error = None;
|
||||
for source in self
|
||||
let mut sources = 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!(
|
||||
.filter(|source| source.owns_kind(&request.authority.kind));
|
||||
let Some(source) = sources.next() else {
|
||||
return Err(SkillProviderError::new(format!(
|
||||
"{} skill provider is not configured",
|
||||
request.authority.kind
|
||||
))),
|
||||
)));
|
||||
};
|
||||
if sources.next().is_some() {
|
||||
return Err(SkillProviderError::new(format!(
|
||||
"{} skill authority is ambiguous",
|
||||
request.authority.kind
|
||||
)));
|
||||
}
|
||||
source.provider.read(request).await
|
||||
}
|
||||
|
||||
pub async fn search(
|
||||
@@ -204,15 +199,42 @@ impl SkillProviders {
|
||||
}
|
||||
}
|
||||
|
||||
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)),
|
||||
struct BoundSkillProvider {
|
||||
kind: SkillSourceKind,
|
||||
provider: Arc<dyn SkillProvider>,
|
||||
identity: SkillSourceIdentity,
|
||||
host_snapshot: Option<Arc<codex_core_skills::HostSkillsSnapshot>>,
|
||||
mcp_resources: Option<Arc<McpResourceClient>>,
|
||||
query: Option<SkillListQuery>,
|
||||
}
|
||||
|
||||
impl SkillSource for BoundSkillProvider {
|
||||
fn kind(&self) -> SkillSourceKind {
|
||||
self.kind.clone()
|
||||
}
|
||||
|
||||
fn identity(&self) -> SkillSourceIdentity {
|
||||
self.identity.clone()
|
||||
}
|
||||
|
||||
fn list(&self) -> SkillSourceFuture<'_, SkillCatalog> {
|
||||
let Some(query) = self.query.clone() else {
|
||||
return Box::pin(async {
|
||||
Err(SkillProviderError::new(
|
||||
"skill source was not bound for catalog listing",
|
||||
))
|
||||
});
|
||||
};
|
||||
self.provider.list(query)
|
||||
}
|
||||
|
||||
fn read(&self, request: RuntimeSkillReadRequest) -> SkillSourceFuture<'_, SkillReadResult> {
|
||||
self.provider.read(SkillReadRequest {
|
||||
authority: request.authority,
|
||||
package: request.package,
|
||||
resource: request.resource,
|
||||
host_snapshot: self.host_snapshot.clone(),
|
||||
mcp_resources: self.mcp_resources.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user