feat: add plugin package resolver
This commit is contained in:
parent
4772c4d6a5
commit
a03a9da64a
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1798,6 +1798,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_ignored",
|
"serde_ignored",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2 0.10.9",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"toml",
|
"toml",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ protocol = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
serde_ignored = "0.1.14"
|
serde_ignored = "0.1.14"
|
||||||
|
sha2 = "0.10"
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::defaults;
|
use crate::defaults;
|
||||||
use crate::model::{AuthRef, ModelManifest, ReasoningControl};
|
use crate::model::{AuthRef, ModelManifest, ReasoningControl};
|
||||||
|
use crate::plugin::PluginConfig;
|
||||||
use crate::{
|
use crate::{
|
||||||
CompactionConfig, FeatureConfig, FeatureFlagConfig, FileUploadLimits, MemoryConfig,
|
CompactionConfig, FeatureConfig, FeatureFlagConfig, FileUploadLimits, MemoryConfig,
|
||||||
PodManifest, PodMeta, ScopeConfig, SessionConfig, SkillsConfig, TicketFeatureAccessConfig,
|
PodManifest, PodMeta, ScopeConfig, SessionConfig, SkillsConfig, TicketFeatureAccessConfig,
|
||||||
|
|
@ -52,6 +53,10 @@ pub struct PodManifestConfig {
|
||||||
/// disabled after cascade merge.
|
/// disabled after cascade merge.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub feature: FeatureConfigPartial,
|
pub feature: FeatureConfigPartial,
|
||||||
|
/// Explicit plugin package enablement entries. Discovery/resolution is a
|
||||||
|
/// separate step and does not run during config merge.
|
||||||
|
#[serde(default)]
|
||||||
|
pub plugins: PluginConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub compaction: Option<CompactionConfigPartial>,
|
pub compaction: Option<CompactionConfigPartial>,
|
||||||
/// First-class web tool opt-in. See [`WebConfig`].
|
/// First-class web tool opt-in. See [`WebConfig`].
|
||||||
|
|
@ -444,6 +449,7 @@ impl PodManifestConfig {
|
||||||
PermissionConfigPartial::merge,
|
PermissionConfigPartial::merge,
|
||||||
),
|
),
|
||||||
feature: self.feature.merge(upper.feature),
|
feature: self.feature.merge(upper.feature),
|
||||||
|
plugins: merge_plugin_config(self.plugins, upper.plugins),
|
||||||
compaction: merge_option(
|
compaction: merge_option(
|
||||||
self.compaction,
|
self.compaction,
|
||||||
upper.compaction,
|
upper.compaction,
|
||||||
|
|
@ -463,6 +469,11 @@ impl SkillsConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn merge_plugin_config(mut base: PluginConfig, upper: PluginConfig) -> PluginConfig {
|
||||||
|
base.enabled.extend(upper.enabled);
|
||||||
|
base
|
||||||
|
}
|
||||||
|
|
||||||
impl WebConfig {
|
impl WebConfig {
|
||||||
fn merge(self, upper: Self) -> Self {
|
fn merge(self, upper: Self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -827,6 +838,7 @@ impl TryFrom<PodManifestConfig> for PodManifest {
|
||||||
session,
|
session,
|
||||||
permissions,
|
permissions,
|
||||||
feature: FeatureConfig::from(cfg.feature),
|
feature: FeatureConfig::from(cfg.feature),
|
||||||
|
plugins: cfg.plugins,
|
||||||
compaction,
|
compaction,
|
||||||
web: cfg.web,
|
web: cfg.web,
|
||||||
memory: cfg.memory,
|
memory: cfg.memory,
|
||||||
|
|
@ -873,6 +885,7 @@ mod tests {
|
||||||
delegation_scope: ScopeConfig::default(),
|
delegation_scope: ScopeConfig::default(),
|
||||||
permissions: None,
|
permissions: None,
|
||||||
feature: FeatureConfigPartial::default(),
|
feature: FeatureConfigPartial::default(),
|
||||||
|
plugins: PluginConfig::default(),
|
||||||
session: None,
|
session: None,
|
||||||
compaction: None,
|
compaction: None,
|
||||||
web: None,
|
web: None,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ mod config;
|
||||||
pub mod defaults;
|
pub mod defaults;
|
||||||
mod model;
|
mod model;
|
||||||
pub mod paths;
|
pub mod paths;
|
||||||
|
pub mod plugin;
|
||||||
mod profile;
|
mod profile;
|
||||||
mod scope;
|
mod scope;
|
||||||
|
|
||||||
|
|
@ -57,6 +58,10 @@ pub struct PodManifest {
|
||||||
/// resolve disabled so Profile authors choose the exposed built-in surfaces.
|
/// resolve disabled so Profile authors choose the exposed built-in surfaces.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub feature: FeatureConfig,
|
pub feature: FeatureConfig,
|
||||||
|
/// Explicit plugin package enablement. Discovery remains read-only; only
|
||||||
|
/// source-qualified entries listed here may resolve to active plugin metadata.
|
||||||
|
#[serde(default)]
|
||||||
|
pub plugins: plugin::PluginConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub compaction: Option<CompactionConfig>,
|
pub compaction: Option<CompactionConfig>,
|
||||||
/// Memory subsystem configuration. Presence of `[memory]` configures memory
|
/// Memory subsystem configuration. Presence of `[memory]` configures memory
|
||||||
|
|
@ -867,6 +872,32 @@ model_id = "claude-sonnet-4-20250514"
|
||||||
assert!(PodManifest::from_toml(toml).is_err());
|
assert!(PodManifest::from_toml(toml).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_plugin_enablement_config() {
|
||||||
|
let toml = format!(
|
||||||
|
"{MINIMAL_REQUIRED}\n\
|
||||||
|
[[plugins.enabled]]\n\
|
||||||
|
id = \"project:example\"\n\
|
||||||
|
digest = \"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n\
|
||||||
|
surfaces = [\"hook\"]\n\n\
|
||||||
|
[plugins.enabled.config]\n\
|
||||||
|
greeting = \"hello\"\n"
|
||||||
|
);
|
||||||
|
let manifest = PodManifest::from_toml(&toml).unwrap();
|
||||||
|
assert_eq!(manifest.plugins.enabled.len(), 1);
|
||||||
|
let enabled = &manifest.plugins.enabled[0];
|
||||||
|
assert_eq!(enabled.id, "project:example");
|
||||||
|
assert_eq!(enabled.surfaces, vec![plugin::PluginSurface::Hook]);
|
||||||
|
assert_eq!(
|
||||||
|
enabled
|
||||||
|
.config
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|value| value.get("greeting"))
|
||||||
|
.and_then(|value| value.as_str()),
|
||||||
|
Some("hello")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_max_turns() {
|
fn parse_max_turns() {
|
||||||
let toml = MINIMAL_REQUIRED.replace("[worker]\n", "[worker]\nmax_turns = 50\n");
|
let toml = MINIMAL_REQUIRED.replace("[worker]\n", "[worker]\nmax_turns = 50\n");
|
||||||
|
|
|
||||||
1680
crates/manifest/src/plugin.rs
Normal file
1680
crates/manifest/src/plugin.rs
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -17,6 +17,7 @@ use crate::config::{
|
||||||
CompactionConfigPartial, FeatureConfigPartial, PermissionConfigPartial, SessionConfigPartial,
|
CompactionConfigPartial, FeatureConfigPartial, PermissionConfigPartial, SessionConfigPartial,
|
||||||
};
|
};
|
||||||
use crate::model::{AuthRef, ModelManifest};
|
use crate::model::{AuthRef, ModelManifest};
|
||||||
|
use crate::plugin::PluginConfig;
|
||||||
use crate::{
|
use crate::{
|
||||||
MemoryConfig, Permission, PodManifest, PodManifestConfig, PodMetaConfig, ResolveError,
|
MemoryConfig, Permission, PodManifest, PodManifestConfig, PodMetaConfig, ResolveError,
|
||||||
ScopeConfig, ScopeRule, SkillsConfig, WebConfig, WorkerManifestConfig, paths,
|
ScopeConfig, ScopeRule, SkillsConfig, WebConfig, WorkerManifestConfig, paths,
|
||||||
|
|
@ -626,6 +627,7 @@ fn resolve_lua_profile_value(
|
||||||
session: profile.session,
|
session: profile.session,
|
||||||
permissions: profile.permissions,
|
permissions: profile.permissions,
|
||||||
feature: profile.feature,
|
feature: profile.feature,
|
||||||
|
plugins: profile.plugins,
|
||||||
compaction,
|
compaction,
|
||||||
web: profile.web,
|
web: profile.web,
|
||||||
memory: profile.memory,
|
memory: profile.memory,
|
||||||
|
|
@ -687,6 +689,8 @@ struct ProfileConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
feature: FeatureConfigPartial,
|
feature: FeatureConfigPartial,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
plugins: PluginConfig,
|
||||||
|
#[serde(default)]
|
||||||
compaction: Option<serde_json::Value>,
|
compaction: Option<serde_json::Value>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
web: Option<WebConfig>,
|
web: Option<WebConfig>,
|
||||||
|
|
|
||||||
|
|
@ -775,6 +775,7 @@ fn manifest_to_reusable_config(manifest: &PodManifest) -> PodManifestConfig {
|
||||||
rules: p.rules.clone(),
|
rules: p.rules.clone(),
|
||||||
}),
|
}),
|
||||||
feature: manifest.feature.clone().into(),
|
feature: manifest.feature.clone().into(),
|
||||||
|
plugins: manifest.plugins.clone(),
|
||||||
compaction: manifest
|
compaction: manifest
|
||||||
.compaction
|
.compaction
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ rustPlatform.buildRustPackage rec {
|
||||||
filter = sourceFilter;
|
filter = sourceFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
cargoHash = "sha256-pIDYnbBs3U8Z3IndgH10rirv8/IdFv1WlgwpCbKXy+M=";
|
cargoHash = "sha256-Y1siH1oDe9It7ntx83DJO5fzV9LtC7+qq9V6RPlRxUY=";
|
||||||
|
|
||||||
depsExtraArgs = {
|
depsExtraArgs = {
|
||||||
# Older fetchCargoVendor utilities used crates.io's API download endpoint,
|
# Older fetchCargoVendor utilities used crates.io's API download endpoint,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user