Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/goose-cli/src/session/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl GooseCompleter {

/// Complete skill names for the /skills command
fn complete_skill_names(&self, line: &str) -> Result<(usize, Vec<Pair>)> {
use goose::agents::platform_extensions::skills::list_installed_skills;
use goose::skills::list_installed_skills;

let cwd = std::env::current_dir().unwrap_or_default();
let skills = list_installed_skills(Some(&cwd));
Expand Down
2 changes: 1 addition & 1 deletion crates/goose-cli/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,7 @@ impl CliSession {

async fn handle_list_skills(&mut self) -> Result<()> {
use comfy_table::{presets, Cell, ContentArrangement, Table};
use goose::agents::platform_extensions::skills::list_installed_skills;
use goose::skills::list_installed_skills;
let cwd = std::env::current_dir().unwrap_or_default();
let skills = list_installed_skills(Some(&cwd));

Expand Down
83 changes: 58 additions & 25 deletions crates/goose-sdk/src/custom_requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,15 +286,33 @@ pub struct ProviderConfigKey {
}

/// The type of source entity.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[derive(
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
)]
#[serde(rename_all = "camelCase")]
pub enum SourceType {
#[default]
Skill,
}

/// A source — a user-editable entity backed by an on-disk directory. Sources
/// may be either `global` (shared across all projects) or project-specific.
BuiltinSkill,
Recipe,
Subrecipe,
Agent,
}

impl std::fmt::Display for SourceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SourceType::Skill => write!(f, "skill"),
SourceType::BuiltinSkill => write!(f, "builtin skill"),
SourceType::Recipe => write!(f, "recipe"),
SourceType::Subrecipe => write!(f, "subrecipe"),
SourceType::Agent => write!(f, "agent"),
}
}
}

/// A source discovered by Goose and backed by an on-disk path. Sources may be
/// either `global` (shared across all projects) or project-specific.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct SourceEntry {
Expand All @@ -303,14 +321,34 @@ pub struct SourceEntry {
pub name: String,
pub description: String,
pub content: String,
/// Absolute path to the source's directory on disk.
/// Absolute path to the source on disk. A directory for skills, a file for
/// recipes and agents.
pub directory: String,
/// True when the source lives in the user's global sources directory; false
/// when it lives inside a specific project.
pub global: bool,
/// Whether this source can be modified through Goose's source CRUD APIs.
#[serde(default)]
pub editable: bool,
/// Paths (absolute) of additional files that live alongside the source.
/// Only skills currently populate this; empty for other source types.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub supporting_files: Vec<String>,
}

/// Create a new source (global or project-scoped).
impl SourceEntry {
/// Render this source as a markdown block suitable for injecting into an
/// LLM context. Used by the skills and summon runtimes when loading a
/// source into the current conversation.
pub fn to_load_text(&self) -> String {
format!(
"## {} ({})\n\n{}\n\n### Content\n\n{}",
self.name, self.source_type, self.description, self.content
)
}
}

/// Create a new source in an explicit target scope (global or project-scoped).
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/sources/create", response = CreateSourceResponse)]
#[serde(rename_all = "camelCase")]
Expand All @@ -332,8 +370,11 @@ pub struct CreateSourceResponse {
pub source: SourceEntry,
}

/// List sources. If `type` is omitted, sources of all known types are returned.
/// Both global and project-scoped sources are included when `project_dir` is set.
/// List discovered sources.
///
/// Today this endpoint only returns skills. If `type` is omitted, it defaults
/// to listing skill sources. Both global and project-scoped skills are included
/// when `project_dir` is set.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/sources/list", response = ListSourcesResponse)]
#[serde(rename_all = "camelCase")]
Expand All @@ -350,19 +391,17 @@ pub struct ListSourcesResponse {
pub sources: Vec<SourceEntry>,
}

/// Update an existing source's description and content.
/// Update an existing source's name, description, and content by absolute path.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/sources/update", response = UpdateSourceResponse)]
#[serde(rename_all = "camelCase")]
pub struct UpdateSourceRequest {
#[serde(rename = "type")]
pub source_type: SourceType,
pub path: String,
pub name: String,
pub description: String,
pub content: String,
pub global: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_dir: Option<String>,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
Expand All @@ -371,30 +410,24 @@ pub struct UpdateSourceResponse {
pub source: SourceEntry,
}

/// Delete a source and its on-disk directory.
/// Delete a source and its on-disk directory by absolute path.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/sources/delete", response = EmptyResponse)]
#[serde(rename_all = "camelCase")]
pub struct DeleteSourceRequest {
#[serde(rename = "type")]
pub source_type: SourceType,
pub name: String,
pub global: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_dir: Option<String>,
pub path: String,
}

/// Export a source as a portable JSON payload.
/// Export a source at an absolute path as a portable JSON payload.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/sources/export", response = ExportSourceResponse)]
#[serde(rename_all = "camelCase")]
pub struct ExportSourceRequest {
#[serde(rename = "type")]
pub source_type: SourceType,
pub name: String,
pub global: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_dir: Option<String>,
pub path: String,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
Expand All @@ -405,8 +438,8 @@ pub struct ExportSourceResponse {
}

/// Import a source from a JSON export payload produced by `_goose/sources/export`.
/// The imported source is written under the given scope; on name collisions a
/// `-imported` suffix is appended.
/// The imported source is written into the explicit target scope; on name
/// collisions a `-imported` suffix is appended.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/sources/import", response = ImportSourcesResponse)]
#[serde(rename_all = "camelCase")]
Expand Down
10 changes: 4 additions & 6 deletions crates/goose-server/src/routes/config_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use goose::config::declarative_providers::LoadedProvider;
use goose::config::paths::Paths;
use goose::config::ExtensionEntry;
use goose::config::{Config, ConfigError};
use goose::custom_requests::SourceType;
use goose::model::ModelConfig;
use goose::providers::base::{ProviderMetadata, ProviderType};
use goose::providers::canonical::maybe_get_canonical_model;
Expand Down Expand Up @@ -427,9 +428,7 @@ pub async fn get_slash_commands(
}

let working_dir = query.working_dir.map(std::path::PathBuf::from);
for source in
goose::agents::platform_extensions::skills::list_installed_skills(working_dir.as_deref())
{
for source in goose::skills::list_installed_skills(working_dir.as_deref()) {
commands.push(SlashCommand {
command: source.name,
help: source.description,
Expand All @@ -443,10 +442,9 @@ pub async fn get_slash_commands(
for source in
goose::agents::platform_extensions::summon::discover_filesystem_sources(discover_dir)
{
use goose::agents::platform_extensions::SourceKind;
if matches!(
source.kind,
SourceKind::Agent | SourceKind::Recipe | SourceKind::Subrecipe
source.source_type,
SourceType::Agent | SourceType::Recipe | SourceType::Subrecipe
) && !source.content.is_empty()
{
commands.push(SlashCommand {
Expand Down
78 changes: 34 additions & 44 deletions crates/goose/acp-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -804,14 +804,18 @@
"content",
"global"
],
"description": "Create a new source (global or project-scoped).",
"description": "Create a new source in an explicit target scope (global or project-scoped).",
"x-side": "agent",
"x-method": "_goose/sources/create"
},
"SourceType": {
"type": "string",
"enum": [
"skill"
"skill",
"builtinSkill",
"recipe",
"subrecipe",
"agent"
],
"description": "The type of source entity."
},
Expand Down Expand Up @@ -845,11 +849,23 @@
},
"directory": {
"type": "string",
"description": "Absolute path to the source's directory on disk."
"description": "Absolute path to the source on disk. A directory for skills, a file for\nrecipes and agents."
},
"global": {
"type": "boolean",
"description": "True when the source lives in the user's global sources directory; false\nwhen it lives inside a specific project."
},
"editable": {
"type": "boolean",
"description": "Whether this source can be modified through Goose's source CRUD APIs.",
"default": false
},
"supportingFiles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Paths (absolute) of additional files that live alongside the source.\nOnly skills currently populate this; empty for other source types."
}
},
"required": [
Expand All @@ -860,7 +876,7 @@
"directory",
"global"
],
"description": "A source — a user-editable entity backed by an on-disk directory. Sources\nmay be either `global` (shared across all projects) or project-specific."
"description": "A source discovered by Goose and backed by an on-disk path. Sources may be\neither `global` (shared across all projects) or project-specific."
},
"ListSourcesRequest": {
"type": "object",
Expand All @@ -882,7 +898,7 @@
]
}
},
"description": "List sources. If `type` is omitted, sources of all known types are returned.\nBoth global and project-scoped sources are included when `project_dir` is set.",
"description": "List discovered sources.\n\nToday this endpoint only returns skills. If `type` is omitted, it defaults\nto listing skill sources. Both global and project-scoped skills are included\nwhen `project_dir` is set.",
"x-side": "agent",
"x-method": "_goose/sources/list"
},
Expand All @@ -908,6 +924,9 @@
"type": {
"$ref": "#/$defs/SourceType"
},
"path": {
"type": "string"
},
"name": {
"type": "string"
},
Expand All @@ -916,25 +935,16 @@
},
"content": {
"type": "string"
},
"global": {
"type": "boolean"
},
"projectDir": {
"type": [
"string",
"null"
]
}
},
"required": [
"type",
"path",
"name",
"description",
"content",
"global"
"content"
],
"description": "Update an existing source's description and content.",
"description": "Update an existing source's name, description, and content by absolute path.",
"x-side": "agent",
"x-method": "_goose/sources/update"
},
Expand All @@ -957,25 +967,15 @@
"type": {
"$ref": "#/$defs/SourceType"
},
"name": {
"path": {
"type": "string"
},
"global": {
"type": "boolean"
},
"projectDir": {
"type": [
"string",
"null"
]
}
},
"required": [
"type",
"name",
"global"
"path"
],
"description": "Delete a source and its on-disk directory.",
"description": "Delete a source and its on-disk directory by absolute path.",
"x-side": "agent",
"x-method": "_goose/sources/delete"
},
Expand All @@ -985,25 +985,15 @@
"type": {
"$ref": "#/$defs/SourceType"
},
"name": {
"path": {
"type": "string"
},
"global": {
"type": "boolean"
},
"projectDir": {
"type": [
"string",
"null"
]
}
},
"required": [
"type",
"name",
"global"
"path"
],
"description": "Export a source as a portable JSON payload.",
"description": "Export a source at an absolute path as a portable JSON payload.",
"x-side": "agent",
"x-method": "_goose/sources/export"
},
Expand Down Expand Up @@ -1044,7 +1034,7 @@
"data",
"global"
],
"description": "Import a source from a JSON export payload produced by `_goose/sources/export`.\nThe imported source is written under the given scope; on name collisions a\n`-imported` suffix is appended.",
"description": "Import a source from a JSON export payload produced by `_goose/sources/export`.\nThe imported source is written into the explicit target scope; on name\ncollisions a `-imported` suffix is appended.",
"x-side": "agent",
"x-method": "_goose/sources/import"
},
Expand Down
Loading
Loading