diff --git a/crates/goose-sdk-types/src/custom_requests.rs b/crates/goose-sdk-types/src/custom_requests.rs index 89061ba33e1f..c784b37fecf7 100644 --- a/crates/goose-sdk-types/src/custom_requests.rs +++ b/crates/goose-sdk-types/src/custom_requests.rs @@ -1,4 +1,4 @@ -use agent_client_protocol::schema::{ContentBlock, McpServer, SessionInfo}; +use agent_client_protocol::schema::{AvailableCommand, ContentBlock, McpServer, SessionInfo}; use agent_client_protocol::{JsonRpcRequest, JsonRpcResponse}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -1134,6 +1134,58 @@ pub struct ListSourcesResponse { pub sources: Vec, } +/// A user-facing `@` mention target backed by an agent, recipe, or subrecipe source. +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct AgentMention { + pub name: String, + pub description: String, + pub source_type: SourceType, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub source_path: Option, + pub mention: String, +} + +/// List user-facing agent mention targets for `@` autocomplete. +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)] +#[request( + method = "_goose/unstable/agent-mentions/list", + response = ListAgentMentionsResponse +)] +#[serde(rename_all = "camelCase")] +pub struct ListAgentMentionsRequest { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cwd: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub session_id: Option, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)] +#[serde(rename_all = "camelCase")] +pub struct ListAgentMentionsResponse { + pub agents: Vec, +} + +/// List slash commands available for `/` autocomplete. +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)] +#[request( + method = "_goose/unstable/slash-commands/list", + response = ListSlashCommandsResponse +)] +#[serde(rename_all = "camelCase")] +pub struct ListSlashCommandsRequest { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cwd: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub session_id: Option, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)] +#[serde(rename_all = "camelCase")] +pub struct ListSlashCommandsResponse { + pub available_commands: Vec, +} + /// Update an existing source's name, description, and content by absolute path. #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)] #[request(method = "_goose/unstable/sources/update", response = UpdateSourceResponse)] diff --git a/crates/goose/acp-meta.json b/crates/goose/acp-meta.json index f0efcda0fb45..32a774406255 100644 --- a/crates/goose/acp-meta.json +++ b/crates/goose/acp-meta.json @@ -290,6 +290,16 @@ "requestType": "ListSourcesRequest_unstable", "responseType": "ListSourcesResponse_unstable" }, + { + "method": "_goose/unstable/agent-mentions/list", + "requestType": "ListAgentMentionsRequest_unstable", + "responseType": "ListAgentMentionsResponse_unstable" + }, + { + "method": "_goose/unstable/slash-commands/list", + "requestType": "ListSlashCommandsRequest_unstable", + "responseType": "ListSlashCommandsResponse_unstable" + }, { "method": "_goose/unstable/sources/update", "requestType": "UpdateSourceRequest_unstable", diff --git a/crates/goose/acp-schema.json b/crates/goose/acp-schema.json index 9124a9a363ef..3ec1bc974c11 100644 --- a/crates/goose/acp-schema.json +++ b/crates/goose/acp-schema.json @@ -3887,6 +3887,175 @@ "x-side": "agent", "x-method": "_goose/unstable/sources/list" }, + "ListAgentMentionsRequest_unstable": { + "type": "object", + "properties": { + "cwd": { + "type": [ + "string", + "null" + ] + }, + "sessionId": { + "type": [ + "string", + "null" + ] + } + }, + "description": "List user-facing agent mention targets for `@` autocomplete.", + "x-side": "agent", + "x-method": "_goose/unstable/agent-mentions/list" + }, + "ListAgentMentionsResponse_unstable": { + "type": "object", + "properties": { + "agents": { + "type": "array", + "items": { + "$ref": "#/$defs/AgentMention" + } + } + }, + "required": [ + "agents" + ], + "x-side": "agent", + "x-method": "_goose/unstable/agent-mentions/list" + }, + "AgentMention": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "sourceType": { + "$ref": "#/$defs/SourceType" + }, + "sourcePath": { + "type": [ + "string", + "null" + ] + }, + "mention": { + "type": "string" + } + }, + "required": [ + "name", + "description", + "sourceType", + "mention" + ], + "description": "A user-facing `@` mention target backed by an agent, recipe, or subrecipe source." + }, + "ListSlashCommandsRequest_unstable": { + "type": "object", + "properties": { + "cwd": { + "type": [ + "string", + "null" + ] + }, + "sessionId": { + "type": [ + "string", + "null" + ] + } + }, + "description": "List slash commands available for `/` autocomplete.", + "x-side": "agent", + "x-method": "_goose/unstable/slash-commands/list" + }, + "ListSlashCommandsResponse_unstable": { + "type": "object", + "properties": { + "availableCommands": { + "type": "array", + "items": { + "$ref": "#/$defs/AvailableCommand" + } + } + }, + "required": [ + "availableCommands" + ], + "x-side": "agent", + "x-method": "_goose/unstable/slash-commands/list" + }, + "AvailableCommand": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Command name (e.g., `create_plan`, `research_codebase`)." + }, + "description": { + "type": "string", + "description": "Human-readable description of what the command does." + }, + "input": { + "anyOf": [ + { + "$ref": "#/$defs/AvailableCommandInput" + }, + { + "type": "null" + } + ], + "description": "Input for the command if required" + }, + "_meta": { + "type": [ + "object", + "null" + ], + "additionalProperties": {}, + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)" + } + }, + "required": [ + "name", + "description" + ], + "description": "Information about a command." + }, + "AvailableCommandInput": { + "anyOf": [ + { + "$ref": "#/$defs/UnstructuredCommandInput", + "description": "All text that was typed after the command name is provided as input." + } + ], + "description": "The input specification for a command." + }, + "UnstructuredCommandInput": { + "type": "object", + "properties": { + "hint": { + "type": "string", + "description": "A hint to display when the input hasn't been provided yet" + }, + "_meta": { + "type": [ + "object", + "null" + ], + "additionalProperties": {}, + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)" + } + }, + "required": [ + "hint" + ], + "description": "All text that was typed after the command name is provided as input." + }, "UpdateSourceRequest_unstable": { "type": "object", "properties": { @@ -5098,6 +5267,24 @@ "description": "Params for _goose/unstable/sources/list", "title": "ListSourcesRequest_unstable" }, + { + "allOf": [ + { + "$ref": "#/$defs/ListAgentMentionsRequest_unstable" + } + ], + "description": "Params for _goose/unstable/agent-mentions/list", + "title": "ListAgentMentionsRequest_unstable" + }, + { + "allOf": [ + { + "$ref": "#/$defs/ListSlashCommandsRequest_unstable" + } + ], + "description": "Params for _goose/unstable/slash-commands/list", + "title": "ListSlashCommandsRequest_unstable" + }, { "allOf": [ { @@ -5558,6 +5745,22 @@ ], "title": "ListSourcesResponse_unstable" }, + { + "allOf": [ + { + "$ref": "#/$defs/ListAgentMentionsResponse_unstable" + } + ], + "title": "ListAgentMentionsResponse_unstable" + }, + { + "allOf": [ + { + "$ref": "#/$defs/ListSlashCommandsResponse_unstable" + } + ], + "title": "ListSlashCommandsResponse_unstable" + }, { "allOf": [ { diff --git a/crates/goose/src/acp/response_builder.rs b/crates/goose/src/acp/response_builder.rs index d307d911529c..d8e6efbeab06 100644 --- a/crates/goose/src/acp/response_builder.rs +++ b/crates/goose/src/acp/response_builder.rs @@ -2,6 +2,7 @@ use crate::agents::ExtensionLoadResult; use crate::config::{Config, GooseMode}; use crate::providers::inventory::{ProviderInventoryEntry, ProviderInventoryService}; use crate::session::Session; +use crate::slash_commands::types::{SlashCommandEntry, SlashCommandSource}; use agent_client_protocol::schema::{ AvailableCommand, AvailableCommandInput, AvailableCommandsUpdate, ModelId, ModelInfo, SessionConfigOption, SessionConfigOptionCategory, SessionConfigSelectOption, SessionId, @@ -334,21 +335,54 @@ fn current_thinking_effort_value(model_config: &ModelConfig) -> String { } } -fn available_commands_update(working_dir: &std::path::Path) -> AvailableCommandsUpdate { - let commands = crate::slash_commands::slash_command::list_acp_commands(Some(working_dir)) +fn slash_command_meta(entry: &SlashCommandEntry) -> serde_json::Map { + let mut meta = serde_json::Map::new(); + let command_type = match entry.source { + SlashCommandSource::Builtin => "Builtin", + SlashCommandSource::Recipe => "Recipe", + SlashCommandSource::Skill => "Skill", + }; + meta.insert( + "commandType".to_string(), + serde_json::Value::String(command_type.to_string()), + ); + if let Some(source_path) = &entry.source_path { + meta.insert( + "sourcePath".to_string(), + serde_json::Value::String(source_path.clone()), + ); + } + meta +} + +fn slash_command_to_available_command(entry: SlashCommandEntry) -> AvailableCommand { + let meta = slash_command_meta(&entry); + let mut command = AvailableCommand::new(entry.name, entry.description); + if let Some(input_hint) = entry.input_hint { + command = command.input(AvailableCommandInput::Unstructured( + UnstructuredCommandInput::new(input_hint), + )); + } + command.meta(meta) +} + +pub(super) fn available_commands_for_working_dir( + working_dir: &std::path::Path, +) -> Vec { + available_commands_for_optional_working_dir(Some(working_dir)) +} + +pub(super) fn available_commands_for_optional_working_dir( + working_dir: Option<&std::path::Path>, +) -> Vec { + crate::slash_commands::slash_command::list_acp_commands(working_dir) .into_iter() - .map(|entry| { - let mut command = AvailableCommand::new(entry.name, entry.description); - if let Some(input_hint) = entry.input_hint { - command = command.input(AvailableCommandInput::Unstructured( - UnstructuredCommandInput::new(input_hint), - )); - } - command - }) - .collect(); + .map(slash_command_to_available_command) + .collect() +} - AvailableCommandsUpdate::new(commands) +fn available_commands_update(working_dir: &std::path::Path) -> AvailableCommandsUpdate { + AvailableCommandsUpdate::new(available_commands_for_working_dir(working_dir)) } pub(super) fn send_session_setup_notifications( @@ -468,6 +502,49 @@ mod tests { build_mode_state(current_mode) } + #[test] + fn test_slash_command_to_available_command_maps_core_fields_to_acp() { + let cases = [ + (SlashCommandSource::Builtin, "Builtin", None), + ( + SlashCommandSource::Recipe, + "Recipe", + Some("/tmp/release.yaml".to_string()), + ), + (SlashCommandSource::Skill, "Skill", None), + ]; + + for (source, expected_command_type, expected_source_path) in cases { + let command = slash_command_to_available_command(SlashCommandEntry { + name: "release".to_string(), + description: "Run release workflow".to_string(), + source, + source_path: expected_source_path.clone(), + input_hint: Some("[task]".to_string()), + }); + + assert_eq!(command.name, "release"); + assert_eq!(command.description, "Run release workflow"); + + match command.input.as_ref() { + Some(AvailableCommandInput::Unstructured(input)) => { + assert_eq!(input.hint, "[task]"); + } + other => panic!("unexpected command input: {other:?}"), + } + + let meta = command.meta.as_ref().expect("command _meta"); + let expected_command_type = serde_json::json!(expected_command_type); + assert_eq!(meta.get("commandType"), Some(&expected_command_type)); + if let Some(source_path) = expected_source_path { + let expected_source_path = serde_json::json!(source_path); + assert_eq!(meta.get("sourcePath"), Some(&expected_source_path)); + } else { + assert!(meta.get("sourcePath").is_none()); + } + } + } + #[test_case( build_mode_state(GooseMode::Auto).unwrap(), "openai", diff --git a/crates/goose/src/acp/server.rs b/crates/goose/src/acp/server.rs index c8e9f0fb5161..bb14040dbd05 100644 --- a/crates/goose/src/acp/server.rs +++ b/crates/goose/src/acp/server.rs @@ -82,6 +82,7 @@ use uuid::Uuid; mod agent_requests; pub use agent_requests::agent_request_schemas; +mod agent_mentions; mod config; mod custom_dispatch; mod diagnostics; @@ -98,6 +99,7 @@ mod onboarding; mod providers; mod recipe; mod resources; +mod slash_commands; mod sources; mod tool_notifications; mod tools; diff --git a/crates/goose/src/acp/server/agent_mentions.rs b/crates/goose/src/acp/server/agent_mentions.rs new file mode 100644 index 000000000000..7f2f53b69ffc --- /dev/null +++ b/crates/goose/src/acp/server/agent_mentions.rs @@ -0,0 +1,119 @@ +use super::*; +use crate::session::Session; +use goose_sdk_types::custom_requests::{AgentMention, SourceEntry, SourceType}; +use std::collections::HashSet; +use std::path::PathBuf; + +fn add_session_subrecipes( + session: &Session, + sources: &mut Vec, + seen: &mut HashSet, +) { + let Some(sub_recipes) = session + .recipe + .as_ref() + .and_then(|recipe| recipe.sub_recipes.as_ref()) + else { + return; + }; + + for sub_recipe in sub_recipes { + if !seen.insert(sub_recipe.name.clone()) { + continue; + } + + sources.push(SourceEntry { + source_type: SourceType::Subrecipe, + name: sub_recipe.name.clone(), + description: sub_recipe.description.clone().unwrap_or_default(), + content: String::new(), + path: sub_recipe.path.clone(), + global: false, + writable: true, + supporting_files: Vec::new(), + properties: std::collections::HashMap::new(), + }); + } +} + +impl GooseAcpAgent { + pub(super) async fn on_list_agent_mentions( + &self, + req: ListAgentMentionsRequest, + ) -> Result { + let session = if let Some(session_id) = req + .session_id + .as_deref() + .map(str::trim) + .filter(|session_id| !session_id.is_empty()) + { + Some( + self.session_manager + .get_session(session_id, false) + .await + .map_err(|_| { + agent_client_protocol::Error::resource_not_found(Some( + session_id.to_string(), + )) + .data(format!("Session not found: {}", session_id)) + })?, + ) + } else { + None + }; + + let cwd = if let Some(cwd) = req + .cwd + .as_deref() + .map(str::trim) + .filter(|path| !path.is_empty()) + { + PathBuf::from(cwd) + } else if let Some(session) = &session { + session.working_dir.clone() + } else { + return Err(agent_client_protocol::Error::invalid_params() + .data("Either cwd or sessionId is required")); + }; + + let filesystem_sources = + crate::agents::platform_extensions::summon::discover_filesystem_sources(&cwd); + let mut sources = Vec::new(); + let mut seen = HashSet::new(); + + if let Some(session) = &session { + add_session_subrecipes(session, &mut sources, &mut seen); + } + + for source in filesystem_sources { + if seen.insert(source.name.clone()) { + sources.push(source); + } + } + + let agents = sources + .into_iter() + .filter(|source| { + matches!( + source.source_type, + SourceType::Agent | SourceType::Recipe | SourceType::Subrecipe + ) && (matches!( + source.source_type, + SourceType::Recipe | SourceType::Subrecipe + ) || !source.content.is_empty()) + }) + .map(|source| { + let mention = format!("@{}", source.name); + AgentMention { + name: source.name, + description: source.description, + source_type: source.source_type, + source_path: (!source.path.is_empty()).then_some(source.path), + mention, + } + }) + .collect(); + + Ok(ListAgentMentionsResponse { agents }) + } +} diff --git a/crates/goose/src/acp/server/custom_dispatch.rs b/crates/goose/src/acp/server/custom_dispatch.rs index 7b10cc3178ca..df4a89e8bee1 100644 --- a/crates/goose/src/acp/server/custom_dispatch.rs +++ b/crates/goose/src/acp/server/custom_dispatch.rs @@ -480,6 +480,22 @@ impl GooseAcpAgent { self.on_list_sources(req).await } + #[custom_method(ListAgentMentionsRequest)] + async fn dispatch_list_agent_mentions( + &self, + req: ListAgentMentionsRequest, + ) -> Result { + self.on_list_agent_mentions(req).await + } + + #[custom_method(ListSlashCommandsRequest)] + async fn dispatch_list_slash_commands( + &self, + req: ListSlashCommandsRequest, + ) -> Result { + self.on_list_slash_commands(req).await + } + #[custom_method(UpdateSourceRequest)] async fn dispatch_update_source( &self, diff --git a/crates/goose/src/acp/server/slash_commands.rs b/crates/goose/src/acp/server/slash_commands.rs new file mode 100644 index 000000000000..3d31a7614a37 --- /dev/null +++ b/crates/goose/src/acp/server/slash_commands.rs @@ -0,0 +1,45 @@ +use super::*; +use std::path::PathBuf; + +impl GooseAcpAgent { + pub(super) async fn on_list_slash_commands( + &self, + req: ListSlashCommandsRequest, + ) -> Result { + let cwd = if let Some(cwd) = req + .cwd + .as_deref() + .map(str::trim) + .filter(|path| !path.is_empty()) + { + Some(PathBuf::from(cwd)) + } else if let Some(session_id) = req + .session_id + .as_deref() + .map(str::trim) + .filter(|session_id| !session_id.is_empty()) + { + Some( + self.session_manager + .get_session(session_id, false) + .await + .map_err(|_| { + agent_client_protocol::Error::resource_not_found(Some( + session_id.to_string(), + )) + .data(format!("Session not found: {}", session_id)) + })? + .working_dir, + ) + } else { + None + }; + + Ok(ListSlashCommandsResponse { + available_commands: + crate::acp::response_builder::available_commands_for_optional_working_dir( + cwd.as_deref(), + ), + }) + } +} diff --git a/crates/goose/src/slash_commands/recipe_slash_command.rs b/crates/goose/src/slash_commands/recipe_slash_command.rs index 78e80e272111..18e4c82b3d7e 100644 --- a/crates/goose/src/slash_commands/recipe_slash_command.rs +++ b/crates/goose/src/slash_commands/recipe_slash_command.rs @@ -80,6 +80,7 @@ pub(super) fn commands_from_mappings(mappings: Vec) -> Vec< name, description: metadata.description, source: SlashCommandSource::Recipe, + source_path: Some(mapping.recipe_path), input_hint: metadata.input_hint, }) }) diff --git a/crates/goose/src/slash_commands/skill_slash_command.rs b/crates/goose/src/slash_commands/skill_slash_command.rs index f85554dcb239..62f941e5fa61 100644 --- a/crates/goose/src/slash_commands/skill_slash_command.rs +++ b/crates/goose/src/slash_commands/skill_slash_command.rs @@ -73,6 +73,7 @@ pub(super) fn commands_from_sources(sources: Vec) -> Vec Vec { name: command.name.to_string(), description: command.description.to_string(), source: SlashCommandSource::Builtin, + source_path: None, input_hint: None, }) .collect() @@ -80,6 +81,7 @@ mod tests { name: name.to_string(), description: format!("{name} description"), source, + source_path: None, input_hint: None, } } diff --git a/crates/goose/src/slash_commands/types.rs b/crates/goose/src/slash_commands/types.rs index 53783d9b5785..89edfc8f98ed 100644 --- a/crates/goose/src/slash_commands/types.rs +++ b/crates/goose/src/slash_commands/types.rs @@ -10,5 +10,6 @@ pub struct SlashCommandEntry { pub name: String, pub description: String, pub source: SlashCommandSource, + pub source_path: Option, pub input_hint: Option, } diff --git a/ui/desktop/src/acp/__tests__/autocomplete.test.ts b/ui/desktop/src/acp/__tests__/autocomplete.test.ts new file mode 100644 index 000000000000..9ce48bc847b1 --- /dev/null +++ b/ui/desktop/src/acp/__tests__/autocomplete.test.ts @@ -0,0 +1,113 @@ +import type { AgentMention, AvailableCommand } from '@aaif/goose-sdk'; +import { describe, expect, it } from 'vitest'; +import { agentMentionToDisplayItem, availableCommandToDisplayItem } from '../autocomplete'; + +function command(overrides: Partial): AvailableCommand { + return { + name: 'release', + description: 'Run release workflow', + ...overrides, + }; +} + +function agent(overrides: Partial = {}): AgentMention { + return { + name: 'reviewer', + description: 'Review code changes', + sourceType: 'agent', + mention: '@reviewer', + ...overrides, + }; +} + +describe('ACP autocomplete mapping', () => { + it('maps builtin commands to display items with descriptions', () => { + expect( + availableCommandToDisplayItem( + command({ + _meta: { commandType: 'Builtin' }, + }) + ) + ).toEqual({ + name: 'release', + extra: 'Run release workflow', + itemType: 'Builtin', + relativePath: 'release', + }); + }); + + it('maps skill commands to display items with descriptions', () => { + expect( + availableCommandToDisplayItem( + command({ + _meta: { commandType: 'Skill' }, + }) + ) + ).toEqual({ + name: 'release', + extra: 'Run release workflow', + itemType: 'Skill', + relativePath: 'release', + }); + }); + + it('maps recipe commands and prefers sourcePath for display text', () => { + expect( + availableCommandToDisplayItem( + command({ + _meta: { + commandType: 'Recipe', + sourcePath: '/tmp/release.yaml', + }, + }) + ) + ).toEqual({ + name: 'release', + extra: '/tmp/release.yaml', + itemType: 'Recipe', + relativePath: 'release', + }); + }); + + it('falls back to recipe descriptions when sourcePath is missing', () => { + expect( + availableCommandToDisplayItem( + command({ + _meta: { commandType: 'Recipe' }, + }) + ) + ).toEqual({ + name: 'release', + extra: 'Run release workflow', + itemType: 'Recipe', + relativePath: 'release', + }); + }); + + it('skips commands without a valid commandType', () => { + expect(availableCommandToDisplayItem(command({}))).toBeNull(); + expect( + availableCommandToDisplayItem( + command({ + _meta: { commandType: 'Agent' }, + }) + ) + ).toBeNull(); + }); + + it('maps agent mentions and uses server-provided mention text', () => { + expect(agentMentionToDisplayItem(agent())).toEqual({ + name: 'reviewer', + extra: 'Review code changes', + itemType: 'Agent', + relativePath: 'reviewer', + insertText: '@reviewer ', + }); + }); + + it('does not add a second trailing space to agent mention text', () => { + expect(agentMentionToDisplayItem(agent({ mention: '@reviewer ' }))).toMatchObject({ + insertText: '@reviewer ', + }); + }); +}); diff --git a/ui/desktop/src/acp/autocomplete.ts b/ui/desktop/src/acp/autocomplete.ts new file mode 100644 index 000000000000..d60817fcdea8 --- /dev/null +++ b/ui/desktop/src/acp/autocomplete.ts @@ -0,0 +1,76 @@ +import type { AgentMention, AvailableCommand } from '@aaif/goose-sdk'; +import type { DisplayItem } from '../components/MentionPopover'; +import { getAcpClient } from './acpConnection'; + +type SlashCommandItemType = Extract; +type AutocompleteDisplayItem = DisplayItem; + +const SLASH_COMMAND_ITEM_TYPES = new Set(['Builtin', 'Recipe', 'Skill']); + +function isSlashCommandItemType(value: unknown): value is SlashCommandItemType { + return typeof value === 'string' && SLASH_COMMAND_ITEM_TYPES.has(value); +} + +function stringMetaValue( + meta: AvailableCommand['_meta'], + key: string +): string | undefined { + const value = meta?.[key]; + return typeof value === 'string' && value.trim() ? value : undefined; +} + +function cwdParam(cwd: string): { cwd?: string } { + const trimmed = cwd.trim(); + return trimmed ? { cwd: trimmed } : {}; +} + +export function availableCommandToDisplayItem( + command: AvailableCommand +): AutocompleteDisplayItem | null { + const commandType = stringMetaValue(command._meta, 'commandType'); + if (!isSlashCommandItemType(commandType)) { + return null; + } + + const sourcePath = stringMetaValue(command._meta, 'sourcePath'); + const extra = commandType === 'Recipe' ? sourcePath ?? command.description : command.description; + + return { + name: command.name, + extra, + itemType: commandType, + relativePath: command.name, + }; +} + +export function agentMentionToDisplayItem(agent: AgentMention): AutocompleteDisplayItem { + const mention = agent.mention.trim() || `@${agent.name}`; + + return { + name: agent.name, + extra: agent.description, + itemType: 'Agent', + relativePath: agent.name, + insertText: mention.endsWith(' ') ? mention : `${mention} `, + }; +} + +export async function listSlashCommandItems(cwd: string): Promise { + const client = await getAcpClient(); + const response = await client.goose.slashCommandsList_unstable(cwdParam(cwd)); + return response.availableCommands + .map(availableCommandToDisplayItem) + .filter((item): item is AutocompleteDisplayItem => item !== null); +} + +export async function listAgentMentionItems( + cwd: string, + sessionId?: string +): Promise { + const client = await getAcpClient(); + const response = await client.goose.agentMentionsList_unstable({ + ...cwdParam(cwd), + ...(sessionId ? { sessionId } : {}), + }); + return response.agents.map(agentMentionToDisplayItem); +} diff --git a/ui/desktop/src/components/ChatInput.tsx b/ui/desktop/src/components/ChatInput.tsx index d1f7e264c328..6752098890e3 100644 --- a/ui/desktop/src/components/ChatInput.tsx +++ b/ui/desktop/src/components/ChatInput.tsx @@ -1797,6 +1797,7 @@ export default function ChatInput({ setMentionPopover((prev) => ({ ...prev, selectedIndex: index })) } workingDir={currentWorkingDir} + sessionId={sessionId} /> diff --git a/ui/desktop/src/components/MentionPopover.tsx b/ui/desktop/src/components/MentionPopover.tsx index 80d0933abd20..ccdcadb5e8ae 100644 --- a/ui/desktop/src/components/MentionPopover.tsx +++ b/ui/desktop/src/components/MentionPopover.tsx @@ -8,9 +8,9 @@ import { useState, } from 'react'; import { ItemIcon } from './ItemIcon'; -import { CommandType, getSlashCommands } from '../api'; import { getInitialWorkingDir } from '../utils/workingDir'; import { defineMessages, useIntl } from '../i18n'; +import { listAgentMentionItems, listSlashCommandItems } from '../acp/autocomplete'; const i18n = defineMessages({ scanningFiles: { @@ -35,7 +35,8 @@ const i18n = defineMessages({ }, }); -type DisplayItemType = CommandType | 'Directory' | 'File'; +type CommandItemType = 'Builtin' | 'Recipe' | 'Skill' | 'Agent'; +type DisplayItemType = CommandItemType | 'Directory' | 'File'; const typeOrder: Record = { Agent: 0, @@ -51,6 +52,7 @@ export interface DisplayItem { extra: string; itemType: DisplayItemType; relativePath: string; + insertText?: string; } export interface DisplayItemWithMatch extends DisplayItem { @@ -69,6 +71,7 @@ interface MentionPopoverProps { selectedIndex: number; onSelectedIndexChange: (index: number) => void; workingDir?: string; + sessionId?: string | null; } // Enhanced fuzzy matching algorithm @@ -150,6 +153,7 @@ const MentionPopover = forwardRef< selectedIndex, onSelectedIndexChange, workingDir, + sessionId, }, ref ) => { @@ -452,6 +456,9 @@ const MentionPopover = forwardRef< }, [items, query, currentWorkingDir]); const getSelectionText = (item: DisplayItem): string => { + if (item.insertText) { + return item.insertText; + } if (item.itemType === 'Agent') { return '@' + item.name + ' '; } @@ -484,38 +491,16 @@ const MentionPopover = forwardRef< setIsLoading(true); try { if (isSlashCommand) { - const response = await getSlashCommands({ - query: { working_dir: currentWorkingDir }, - throwOnError: true, - }); + const commandItems = await listSlashCommandItems(currentWorkingDir); if (cancelled) return; - const commandItems: DisplayItem[] = (response.data?.commands || []) - .filter((cmd) => cmd.command_type !== 'Agent') - .map((cmd) => ({ - name: cmd.command, - extra: cmd.help, - itemType: cmd.command_type, - relativePath: cmd.command, - })); setItems(commandItems); } else { // Fetch agents from server and scan files in parallel - const [agentResponse, scannedFiles] = await Promise.all([ - getSlashCommands({ - query: { working_dir: currentWorkingDir }, - throwOnError: true, - }).catch(() => null), + const [agentItems, scannedFiles] = await Promise.all([ + listAgentMentionItems(currentWorkingDir, sessionId ?? undefined).catch(() => []), scanDirectoryFromRoot(currentWorkingDir || getDefaultStartPath()), ]); if (cancelled) return; - const agentItems: DisplayItem[] = (agentResponse?.data?.commands || []) - .filter((cmd) => cmd.command_type === 'Agent') - .map((cmd) => ({ - name: cmd.command, - extra: cmd.help, - itemType: cmd.command_type, - relativePath: cmd.command, - })); setItems([...agentItems, ...scannedFiles]); } } catch (error) { @@ -537,7 +522,7 @@ const MentionPopover = forwardRef< return () => { cancelled = true; }; - }, [isOpen, isSlashCommand, scanDirectoryFromRoot, currentWorkingDir]); + }, [isOpen, isSlashCommand, scanDirectoryFromRoot, currentWorkingDir, sessionId]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -610,7 +595,7 @@ const MentionPopover = forwardRef< > {displayItems.map((item, index) => (
handleItemClick(index)} data-selected={index === selectedIndex} className={`flex items-center gap-3 p-2 rounded-md cursor-pointer transition-colors ${ diff --git a/ui/sdk/src/generated/client.gen.ts b/ui/sdk/src/generated/client.gen.ts index fa13ca5e8abf..b6905c6fac18 100644 --- a/ui/sdk/src/generated/client.gen.ts +++ b/ui/sdk/src/generated/client.gen.ts @@ -69,10 +69,14 @@ import type { ImportSessionResponse_unstable, ImportSourcesRequest_unstable, ImportSourcesResponse_unstable, + ListAgentMentionsRequest_unstable, + ListAgentMentionsResponse_unstable, ListProvidersRequest_unstable, ListProvidersResponse_unstable, ListRecipesRequest_unstable, ListRecipesResponse_unstable, + ListSlashCommandsRequest_unstable, + ListSlashCommandsResponse_unstable, ListSourcesRequest_unstable, ListSourcesResponse_unstable, OnboardingImportApplyRequest_unstable, @@ -154,8 +158,10 @@ import { zGooseToolCallResponse_unstable, zImportSessionResponse_unstable, zImportSourcesResponse_unstable, + zListAgentMentionsResponse_unstable, zListProvidersResponse_unstable, zListRecipesResponse_unstable, + zListSlashCommandsResponse_unstable, zListSourcesResponse_unstable, zOnboardingImportApplyResponse_unstable, zOnboardingImportScanResponse_unstable, @@ -777,6 +783,30 @@ export class GooseExtClient { ) as ListSourcesResponse_unstable; } + async agentMentionsList_unstable( + params: ListAgentMentionsRequest_unstable, + ): Promise { + const raw = await this.conn.extMethod( + "_goose/unstable/agent-mentions/list", + params, + ); + return zListAgentMentionsResponse_unstable.parse( + raw, + ) as ListAgentMentionsResponse_unstable; + } + + async slashCommandsList_unstable( + params: ListSlashCommandsRequest_unstable, + ): Promise { + const raw = await this.conn.extMethod( + "_goose/unstable/slash-commands/list", + params, + ); + return zListSlashCommandsResponse_unstable.parse( + raw, + ) as ListSlashCommandsResponse_unstable; + } + async sourcesUpdate_unstable( params: UpdateSourceRequest_unstable, ): Promise { diff --git a/ui/sdk/src/generated/index.ts b/ui/sdk/src/generated/index.ts index fc97f037657f..6cbbdc85fc04 100644 --- a/ui/sdk/src/generated/index.ts +++ b/ui/sdk/src/generated/index.ts @@ -1,6 +1,6 @@ // This file is auto-generated by @hey-api/openapi-ts -export type { AddConfigExtensionRequest_unstable, AddSessionExtensionRequest_unstable, Annotations, ArchiveSessionRequest_unstable, AudioContent, BlobResourceContents, ContentBlock, CreateSourceRequest_unstable, CreateSourceResponse_unstable, CustomProviderConfigDto, CustomProviderCreateRequest_unstable, CustomProviderCreateResponse_unstable, CustomProviderDeleteRequest_unstable, CustomProviderDeleteResponse_unstable, CustomProviderReadRequest_unstable, CustomProviderReadResponse_unstable, CustomProviderUpdateRequest_unstable, CustomProviderUpdateResponse_unstable, DecodeRecipeRequest_unstable, DecodeRecipeResponse_unstable, DefaultsReadRequest_unstable, DefaultsReadResponse_unstable, DefaultsSaveRequest_unstable, DeleteRecipeRequest_unstable, DeleteSessionRequest, DeleteSourceRequest_unstable, DiagnosticsGetRequest_unstable, DiagnosticsGetResponse_unstable, DiagnosticsReportLevel, DictationConfigRequest_unstable, DictationConfigResponse_unstable, DictationDownloadProgress, DictationLocalModelStatus, DictationModelCancelRequest_unstable, DictationModelDeleteRequest_unstable, DictationModelDownloadProgressRequest_unstable, DictationModelDownloadProgressResponse_unstable, DictationModelDownloadRequest_unstable, DictationModelOption, DictationModelSelectRequest_unstable, DictationModelsListRequest_unstable, DictationModelsListResponse_unstable, DictationProviderStatusEntry, DictationSecretDeleteRequest_unstable, DictationSecretSaveRequest_unstable, DictationTranscribeRequest_unstable, DictationTranscribeResponse_unstable, EmbeddedResource, EmbeddedResourceResource, EmptyResponse, EncodeRecipeRequest_unstable, EncodeRecipeResponse_unstable, EnvVariable, ExportSessionRequest_unstable, ExportSessionResponse_unstable, ExportSourceRequest_unstable, ExportSourceResponse_unstable, ExtAgentRequest, ExtAgentResponse, ExtNotification, ExtRequest, ExtResponse, GetAvailableExtensionsRequest_unstable, GetAvailableExtensionsResponse_unstable, GetConfigExtensionsRequest_unstable, GetConfigExtensionsResponse_unstable, GetSessionExtensionsRequest_unstable, GetSessionExtensionsResponse_unstable, GetSessionInfoRequest_unstable, GetSessionInfoResponse_unstable, GetToolsRequest_unstable, GetToolsResponse_unstable, GooseExtension, GooseExtensionEntry, GooseSessionNotification_unstable, GooseSessionUpdate, GooseToolCallRequest_unstable, GooseToolCallResponse_unstable, HttpHeader, ImageContent, ImportSessionRequest_unstable, ImportSessionResponse_unstable, ImportSourcesRequest_unstable, ImportSourcesResponse_unstable, ListProvidersRequest_unstable, ListProvidersResponse_unstable, ListRecipesRequest_unstable, ListRecipesResponse_unstable, ListSourcesRequest_unstable, ListSourcesResponse_unstable, McpServer, McpServerHttp, McpServerSse, McpServerStdio, OnboardingImportApplyRequest_unstable, OnboardingImportApplyResponse_unstable, OnboardingImportCandidate, OnboardingImportCounts, OnboardingImportScanRequest_unstable, OnboardingImportScanResponse_unstable, OnboardingImportSourceKind, ParseRecipeRequest_unstable, ParseRecipeResponse_unstable, PreferenceKey, PreferencesReadRequest_unstable, PreferencesReadResponse_unstable, PreferencesRemoveRequest_unstable, PreferencesSaveRequest_unstable, PreferenceValue, ProviderCatalogListRequest_unstable, ProviderCatalogListResponse_unstable, ProviderCatalogTemplateRequest_unstable, ProviderCatalogTemplateResponse_unstable, ProviderConfigAuthenticateRequest_unstable, ProviderConfigChangeResponse_unstable, ProviderConfigDeleteRequest_unstable, ProviderConfigFieldUpdate, ProviderConfigFieldValueDto, ProviderConfigKey, ProviderConfigReadRequest_unstable, ProviderConfigReadResponse_unstable, ProviderConfigSaveRequest_unstable, ProviderConfigStatusDto, ProviderConfigStatusRequest_unstable, ProviderConfigStatusResponse_unstable, ProviderInventoryEntryDto, ProviderInventoryModelDto, ProviderSetupCatalogEntryDto, ProviderSetupCatalogListRequest_unstable, ProviderSetupCatalogListResponse_unstable, ProviderSetupCategoryDto, ProviderSetupFieldDto, ProviderSetupGroupDto, ProviderSetupMethodDto, ProviderSupportedModelsListRequest_unstable, ProviderSupportedModelsListResponse_unstable, ProviderTemplateCapabilitiesDto, ProviderTemplateCatalogEntryDto, ProviderTemplateDto, ProviderTemplateModelDto, ReadResourceRequest_unstable, ReadResourceResponse_unstable, RecipeAuthorDto, RecipeDto, RecipeExtensionDto, RecipeListEntryDto, RecipeParameterDto, RecipeParameterInputTypeDto, RecipeParameterRequirementDto, RecipeParamsAction, RecipeParamsResponse_unstable, RecipeResponseDto, RecipeRetryConfigDto, RecipeSettingsDto, RecipeSuccessCheckDto, RecipeToYamlRequest_unstable, RecipeToYamlResponse_unstable, RefreshProviderInventoryRequest_unstable, RefreshProviderInventoryResponse_unstable, RefreshProviderInventorySkipDto, RefreshProviderInventorySkipReasonDto, RemoveConfigExtensionRequest_unstable, RemoveSessionExtensionRequest_unstable, RenameSessionRequest_unstable, RequestRecipeParams_unstable, ResourceLink, Role, SaveRecipeRequest_unstable, SaveRecipeResponse_unstable, ScanRecipeRequest_unstable, ScanRecipeResponse_unstable, ScheduleRecipeRequest_unstable, SessionId, SessionInfo, SessionSystemPromptMode, SessionUsageUpdate, SetConfigExtensionEnabledRequest_unstable, SetRecipeSlashCommandRequest_unstable, SetSessionSystemPromptRequest_unstable, SourceEntry, SourceScope, SourceType, StatusMessage, StatusMessageUpdate, SteerSessionRequest_unstable, SteerSessionResponse_unstable, SubRecipeDto, TextContent, TextResourceContents, TruncateSessionConversationRequest_unstable, UnarchiveSessionRequest_unstable, UpdateSessionProjectRequest_unstable, UpdateSourceRequest_unstable, UpdateSourceResponse_unstable, UpdateWorkingDirRequest_unstable } from './types.gen.js'; +export type { AddConfigExtensionRequest_unstable, AddSessionExtensionRequest_unstable, AgentMention, Annotations, ArchiveSessionRequest_unstable, AudioContent, AvailableCommand, AvailableCommandInput, BlobResourceContents, ContentBlock, CreateSourceRequest_unstable, CreateSourceResponse_unstable, CustomProviderConfigDto, CustomProviderCreateRequest_unstable, CustomProviderCreateResponse_unstable, CustomProviderDeleteRequest_unstable, CustomProviderDeleteResponse_unstable, CustomProviderReadRequest_unstable, CustomProviderReadResponse_unstable, CustomProviderUpdateRequest_unstable, CustomProviderUpdateResponse_unstable, DecodeRecipeRequest_unstable, DecodeRecipeResponse_unstable, DefaultsReadRequest_unstable, DefaultsReadResponse_unstable, DefaultsSaveRequest_unstable, DeleteRecipeRequest_unstable, DeleteSessionRequest, DeleteSourceRequest_unstable, DiagnosticsGetRequest_unstable, DiagnosticsGetResponse_unstable, DiagnosticsReportLevel, DictationConfigRequest_unstable, DictationConfigResponse_unstable, DictationDownloadProgress, DictationLocalModelStatus, DictationModelCancelRequest_unstable, DictationModelDeleteRequest_unstable, DictationModelDownloadProgressRequest_unstable, DictationModelDownloadProgressResponse_unstable, DictationModelDownloadRequest_unstable, DictationModelOption, DictationModelSelectRequest_unstable, DictationModelsListRequest_unstable, DictationModelsListResponse_unstable, DictationProviderStatusEntry, DictationSecretDeleteRequest_unstable, DictationSecretSaveRequest_unstable, DictationTranscribeRequest_unstable, DictationTranscribeResponse_unstable, EmbeddedResource, EmbeddedResourceResource, EmptyResponse, EncodeRecipeRequest_unstable, EncodeRecipeResponse_unstable, EnvVariable, ExportSessionRequest_unstable, ExportSessionResponse_unstable, ExportSourceRequest_unstable, ExportSourceResponse_unstable, ExtAgentRequest, ExtAgentResponse, ExtNotification, ExtRequest, ExtResponse, GetAvailableExtensionsRequest_unstable, GetAvailableExtensionsResponse_unstable, GetConfigExtensionsRequest_unstable, GetConfigExtensionsResponse_unstable, GetSessionExtensionsRequest_unstable, GetSessionExtensionsResponse_unstable, GetSessionInfoRequest_unstable, GetSessionInfoResponse_unstable, GetToolsRequest_unstable, GetToolsResponse_unstable, GooseExtension, GooseExtensionEntry, GooseSessionNotification_unstable, GooseSessionUpdate, GooseToolCallRequest_unstable, GooseToolCallResponse_unstable, HttpHeader, ImageContent, ImportSessionRequest_unstable, ImportSessionResponse_unstable, ImportSourcesRequest_unstable, ImportSourcesResponse_unstable, ListAgentMentionsRequest_unstable, ListAgentMentionsResponse_unstable, ListProvidersRequest_unstable, ListProvidersResponse_unstable, ListRecipesRequest_unstable, ListRecipesResponse_unstable, ListSlashCommandsRequest_unstable, ListSlashCommandsResponse_unstable, ListSourcesRequest_unstable, ListSourcesResponse_unstable, McpServer, McpServerHttp, McpServerSse, McpServerStdio, OnboardingImportApplyRequest_unstable, OnboardingImportApplyResponse_unstable, OnboardingImportCandidate, OnboardingImportCounts, OnboardingImportScanRequest_unstable, OnboardingImportScanResponse_unstable, OnboardingImportSourceKind, ParseRecipeRequest_unstable, ParseRecipeResponse_unstable, PreferenceKey, PreferencesReadRequest_unstable, PreferencesReadResponse_unstable, PreferencesRemoveRequest_unstable, PreferencesSaveRequest_unstable, PreferenceValue, ProviderCatalogListRequest_unstable, ProviderCatalogListResponse_unstable, ProviderCatalogTemplateRequest_unstable, ProviderCatalogTemplateResponse_unstable, ProviderConfigAuthenticateRequest_unstable, ProviderConfigChangeResponse_unstable, ProviderConfigDeleteRequest_unstable, ProviderConfigFieldUpdate, ProviderConfigFieldValueDto, ProviderConfigKey, ProviderConfigReadRequest_unstable, ProviderConfigReadResponse_unstable, ProviderConfigSaveRequest_unstable, ProviderConfigStatusDto, ProviderConfigStatusRequest_unstable, ProviderConfigStatusResponse_unstable, ProviderInventoryEntryDto, ProviderInventoryModelDto, ProviderSetupCatalogEntryDto, ProviderSetupCatalogListRequest_unstable, ProviderSetupCatalogListResponse_unstable, ProviderSetupCategoryDto, ProviderSetupFieldDto, ProviderSetupGroupDto, ProviderSetupMethodDto, ProviderSupportedModelsListRequest_unstable, ProviderSupportedModelsListResponse_unstable, ProviderTemplateCapabilitiesDto, ProviderTemplateCatalogEntryDto, ProviderTemplateDto, ProviderTemplateModelDto, ReadResourceRequest_unstable, ReadResourceResponse_unstable, RecipeAuthorDto, RecipeDto, RecipeExtensionDto, RecipeListEntryDto, RecipeParameterDto, RecipeParameterInputTypeDto, RecipeParameterRequirementDto, RecipeParamsAction, RecipeParamsResponse_unstable, RecipeResponseDto, RecipeRetryConfigDto, RecipeSettingsDto, RecipeSuccessCheckDto, RecipeToYamlRequest_unstable, RecipeToYamlResponse_unstable, RefreshProviderInventoryRequest_unstable, RefreshProviderInventoryResponse_unstable, RefreshProviderInventorySkipDto, RefreshProviderInventorySkipReasonDto, RemoveConfigExtensionRequest_unstable, RemoveSessionExtensionRequest_unstable, RenameSessionRequest_unstable, RequestRecipeParams_unstable, ResourceLink, Role, SaveRecipeRequest_unstable, SaveRecipeResponse_unstable, ScanRecipeRequest_unstable, ScanRecipeResponse_unstable, ScheduleRecipeRequest_unstable, SessionId, SessionInfo, SessionSystemPromptMode, SessionUsageUpdate, SetConfigExtensionEnabledRequest_unstable, SetRecipeSlashCommandRequest_unstable, SetSessionSystemPromptRequest_unstable, SourceEntry, SourceScope, SourceType, StatusMessage, StatusMessageUpdate, SteerSessionRequest_unstable, SteerSessionResponse_unstable, SubRecipeDto, TextContent, TextResourceContents, TruncateSessionConversationRequest_unstable, UnarchiveSessionRequest_unstable, UnstructuredCommandInput, UpdateSessionProjectRequest_unstable, UpdateSourceRequest_unstable, UpdateSourceResponse_unstable, UpdateWorkingDirRequest_unstable } from './types.gen.js'; export const GOOSE_EXT_METHODS = [ { @@ -293,6 +293,16 @@ export const GOOSE_EXT_METHODS = [ requestType: "ListSourcesRequest_unstable", responseType: "ListSourcesResponse_unstable", }, + { + method: "_goose/unstable/agent-mentions/list", + requestType: "ListAgentMentionsRequest_unstable", + responseType: "ListAgentMentionsResponse_unstable", + }, + { + method: "_goose/unstable/slash-commands/list", + requestType: "ListSlashCommandsRequest_unstable", + responseType: "ListSlashCommandsResponse_unstable", + }, { method: "_goose/unstable/sources/update", requestType: "UpdateSourceRequest_unstable", diff --git a/ui/sdk/src/generated/types.gen.ts b/ui/sdk/src/generated/types.gen.ts index b2a000b773bf..5df4290b153a 100644 --- a/ui/sdk/src/generated/types.gen.ts +++ b/ui/sdk/src/generated/types.gen.ts @@ -1538,6 +1538,94 @@ export type ListSourcesResponse_unstable = { sources: Array; }; +/** + * List user-facing agent mention targets for `@` autocomplete. + */ +export type ListAgentMentionsRequest_unstable = { + cwd?: string | null; + sessionId?: string | null; +}; + +export type ListAgentMentionsResponse_unstable = { + agents: Array; +}; + +/** + * A user-facing `@` mention target backed by an agent, recipe, or subrecipe source. + */ +export type AgentMention = { + name: string; + description: string; + sourceType: SourceType; + sourcePath?: string | null; + mention: string; +}; + +/** + * List slash commands available for `/` autocomplete. + */ +export type ListSlashCommandsRequest_unstable = { + cwd?: string | null; + sessionId?: string | null; +}; + +export type ListSlashCommandsResponse_unstable = { + availableCommands: Array; +}; + +/** + * Information about a command. + */ +export type AvailableCommand = { + /** + * Command name (e.g., `create_plan`, `research_codebase`). + */ + name: string; + /** + * Human-readable description of what the command does. + */ + description: string; + /** + * Input for the command if required + */ + input?: AvailableCommandInput | null; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Implementations MUST NOT make assumptions about values at + * these keys. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * All text that was typed after the command name is provided as input. + */ +export type AvailableCommandInput = UnstructuredCommandInput; + +/** + * All text that was typed after the command name is provided as input. + */ +export type UnstructuredCommandInput = { + /** + * A hint to display when the input hasn't been provided yet + */ + hint: string; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Implementations MUST NOT make assumptions about values at + * these keys. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + /** * Update an existing source's name, description, and content by absolute path. */ @@ -1817,14 +1905,14 @@ export type RecipeParamsAction = 'submit' | 'cancel'; export type ExtRequest = { id: string; method: string; - params?: AddSessionExtensionRequest_unstable | RemoveSessionExtensionRequest_unstable | GetToolsRequest_unstable | GooseToolCallRequest_unstable | ReadResourceRequest_unstable | UpdateWorkingDirRequest_unstable | SetSessionSystemPromptRequest_unstable | SteerSessionRequest_unstable | DiagnosticsGetRequest_unstable | DeleteSessionRequest | GetConfigExtensionsRequest_unstable | GetAvailableExtensionsRequest_unstable | AddConfigExtensionRequest_unstable | RemoveConfigExtensionRequest_unstable | SetConfigExtensionEnabledRequest_unstable | GetSessionExtensionsRequest_unstable | ListProvidersRequest_unstable | ProviderSupportedModelsListRequest_unstable | ProviderCatalogListRequest_unstable | ProviderSetupCatalogListRequest_unstable | ProviderCatalogTemplateRequest_unstable | CustomProviderCreateRequest_unstable | CustomProviderReadRequest_unstable | CustomProviderUpdateRequest_unstable | CustomProviderDeleteRequest_unstable | RefreshProviderInventoryRequest_unstable | ProviderConfigReadRequest_unstable | ProviderConfigStatusRequest_unstable | ProviderConfigSaveRequest_unstable | ProviderConfigDeleteRequest_unstable | ProviderConfigAuthenticateRequest_unstable | PreferencesReadRequest_unstable | PreferencesSaveRequest_unstable | PreferencesRemoveRequest_unstable | DefaultsReadRequest_unstable | DefaultsSaveRequest_unstable | OnboardingImportScanRequest_unstable | OnboardingImportApplyRequest_unstable | ExportSessionRequest_unstable | ImportSessionRequest_unstable | EncodeRecipeRequest_unstable | DecodeRecipeRequest_unstable | ScanRecipeRequest_unstable | ListRecipesRequest_unstable | DeleteRecipeRequest_unstable | ScheduleRecipeRequest_unstable | SetRecipeSlashCommandRequest_unstable | SaveRecipeRequest_unstable | ParseRecipeRequest_unstable | RecipeToYamlRequest_unstable | GetSessionInfoRequest_unstable | TruncateSessionConversationRequest_unstable | UpdateSessionProjectRequest_unstable | RenameSessionRequest_unstable | ArchiveSessionRequest_unstable | UnarchiveSessionRequest_unstable | CreateSourceRequest_unstable | ListSourcesRequest_unstable | UpdateSourceRequest_unstable | DeleteSourceRequest_unstable | ExportSourceRequest_unstable | ImportSourcesRequest_unstable | DictationTranscribeRequest_unstable | DictationConfigRequest_unstable | DictationSecretSaveRequest_unstable | DictationSecretDeleteRequest_unstable | DictationModelsListRequest_unstable | DictationModelDownloadRequest_unstable | DictationModelDownloadProgressRequest_unstable | DictationModelCancelRequest_unstable | DictationModelDeleteRequest_unstable | DictationModelSelectRequest_unstable | { + params?: AddSessionExtensionRequest_unstable | RemoveSessionExtensionRequest_unstable | GetToolsRequest_unstable | GooseToolCallRequest_unstable | ReadResourceRequest_unstable | UpdateWorkingDirRequest_unstable | SetSessionSystemPromptRequest_unstable | SteerSessionRequest_unstable | DiagnosticsGetRequest_unstable | DeleteSessionRequest | GetConfigExtensionsRequest_unstable | GetAvailableExtensionsRequest_unstable | AddConfigExtensionRequest_unstable | RemoveConfigExtensionRequest_unstable | SetConfigExtensionEnabledRequest_unstable | GetSessionExtensionsRequest_unstable | ListProvidersRequest_unstable | ProviderSupportedModelsListRequest_unstable | ProviderCatalogListRequest_unstable | ProviderSetupCatalogListRequest_unstable | ProviderCatalogTemplateRequest_unstable | CustomProviderCreateRequest_unstable | CustomProviderReadRequest_unstable | CustomProviderUpdateRequest_unstable | CustomProviderDeleteRequest_unstable | RefreshProviderInventoryRequest_unstable | ProviderConfigReadRequest_unstable | ProviderConfigStatusRequest_unstable | ProviderConfigSaveRequest_unstable | ProviderConfigDeleteRequest_unstable | ProviderConfigAuthenticateRequest_unstable | PreferencesReadRequest_unstable | PreferencesSaveRequest_unstable | PreferencesRemoveRequest_unstable | DefaultsReadRequest_unstable | DefaultsSaveRequest_unstable | OnboardingImportScanRequest_unstable | OnboardingImportApplyRequest_unstable | ExportSessionRequest_unstable | ImportSessionRequest_unstable | EncodeRecipeRequest_unstable | DecodeRecipeRequest_unstable | ScanRecipeRequest_unstable | ListRecipesRequest_unstable | DeleteRecipeRequest_unstable | ScheduleRecipeRequest_unstable | SetRecipeSlashCommandRequest_unstable | SaveRecipeRequest_unstable | ParseRecipeRequest_unstable | RecipeToYamlRequest_unstable | GetSessionInfoRequest_unstable | TruncateSessionConversationRequest_unstable | UpdateSessionProjectRequest_unstable | RenameSessionRequest_unstable | ArchiveSessionRequest_unstable | UnarchiveSessionRequest_unstable | CreateSourceRequest_unstable | ListSourcesRequest_unstable | ListAgentMentionsRequest_unstable | ListSlashCommandsRequest_unstable | UpdateSourceRequest_unstable | DeleteSourceRequest_unstable | ExportSourceRequest_unstable | ImportSourcesRequest_unstable | DictationTranscribeRequest_unstable | DictationConfigRequest_unstable | DictationSecretSaveRequest_unstable | DictationSecretDeleteRequest_unstable | DictationModelsListRequest_unstable | DictationModelDownloadRequest_unstable | DictationModelDownloadProgressRequest_unstable | DictationModelCancelRequest_unstable | DictationModelDeleteRequest_unstable | DictationModelSelectRequest_unstable | { [key: string]: unknown; } | null; }; export type ExtResponse = { id: string; - result?: EmptyResponse | GetToolsResponse_unstable | GooseToolCallResponse_unstable | ReadResourceResponse_unstable | SteerSessionResponse_unstable | DiagnosticsGetResponse_unstable | GetConfigExtensionsResponse_unstable | GetAvailableExtensionsResponse_unstable | GetSessionExtensionsResponse_unstable | ListProvidersResponse_unstable | ProviderSupportedModelsListResponse_unstable | ProviderCatalogListResponse_unstable | ProviderSetupCatalogListResponse_unstable | ProviderCatalogTemplateResponse_unstable | CustomProviderCreateResponse_unstable | CustomProviderReadResponse_unstable | CustomProviderUpdateResponse_unstable | CustomProviderDeleteResponse_unstable | RefreshProviderInventoryResponse_unstable | ProviderConfigReadResponse_unstable | ProviderConfigStatusResponse_unstable | ProviderConfigChangeResponse_unstable | PreferencesReadResponse_unstable | DefaultsReadResponse_unstable | OnboardingImportScanResponse_unstable | OnboardingImportApplyResponse_unstable | ExportSessionResponse_unstable | ImportSessionResponse_unstable | EncodeRecipeResponse_unstable | DecodeRecipeResponse_unstable | ScanRecipeResponse_unstable | ListRecipesResponse_unstable | SaveRecipeResponse_unstable | ParseRecipeResponse_unstable | RecipeToYamlResponse_unstable | GetSessionInfoResponse_unstable | CreateSourceResponse_unstable | ListSourcesResponse_unstable | UpdateSourceResponse_unstable | ExportSourceResponse_unstable | ImportSourcesResponse_unstable | DictationTranscribeResponse_unstable | DictationConfigResponse_unstable | DictationModelsListResponse_unstable | DictationModelDownloadProgressResponse_unstable | unknown; + result?: EmptyResponse | GetToolsResponse_unstable | GooseToolCallResponse_unstable | ReadResourceResponse_unstable | SteerSessionResponse_unstable | DiagnosticsGetResponse_unstable | GetConfigExtensionsResponse_unstable | GetAvailableExtensionsResponse_unstable | GetSessionExtensionsResponse_unstable | ListProvidersResponse_unstable | ProviderSupportedModelsListResponse_unstable | ProviderCatalogListResponse_unstable | ProviderSetupCatalogListResponse_unstable | ProviderCatalogTemplateResponse_unstable | CustomProviderCreateResponse_unstable | CustomProviderReadResponse_unstable | CustomProviderUpdateResponse_unstable | CustomProviderDeleteResponse_unstable | RefreshProviderInventoryResponse_unstable | ProviderConfigReadResponse_unstable | ProviderConfigStatusResponse_unstable | ProviderConfigChangeResponse_unstable | PreferencesReadResponse_unstable | DefaultsReadResponse_unstable | OnboardingImportScanResponse_unstable | OnboardingImportApplyResponse_unstable | ExportSessionResponse_unstable | ImportSessionResponse_unstable | EncodeRecipeResponse_unstable | DecodeRecipeResponse_unstable | ScanRecipeResponse_unstable | ListRecipesResponse_unstable | SaveRecipeResponse_unstable | ParseRecipeResponse_unstable | RecipeToYamlResponse_unstable | GetSessionInfoResponse_unstable | CreateSourceResponse_unstable | ListSourcesResponse_unstable | ListAgentMentionsResponse_unstable | ListSlashCommandsResponse_unstable | UpdateSourceResponse_unstable | ExportSourceResponse_unstable | ImportSourcesResponse_unstable | DictationTranscribeResponse_unstable | DictationConfigResponse_unstable | DictationModelsListResponse_unstable | DictationModelDownloadProgressResponse_unstable | unknown; } | { error: { code: number; diff --git a/ui/sdk/src/generated/zod.gen.ts b/ui/sdk/src/generated/zod.gen.ts index 2306a8be35f7..1cb7f1cb72af 100644 --- a/ui/sdk/src/generated/zod.gen.ts +++ b/ui/sdk/src/generated/zod.gen.ts @@ -1639,6 +1639,88 @@ export const zListSourcesResponse_unstable = z.object({ sources: z.array(zSourceEntry) }); +/** + * List user-facing agent mention targets for `@` autocomplete. + */ +export const zListAgentMentionsRequest_unstable = z.object({ + cwd: z.union([ + z.string(), + z.null() + ]).optional(), + sessionId: z.union([ + z.string(), + z.null() + ]).optional() +}); + +/** + * A user-facing `@` mention target backed by an agent, recipe, or subrecipe source. + */ +export const zAgentMention = z.object({ + name: z.string(), + description: z.string(), + sourceType: zSourceType, + sourcePath: z.union([ + z.string(), + z.null() + ]).optional(), + mention: z.string() +}); + +export const zListAgentMentionsResponse_unstable = z.object({ + agents: z.array(zAgentMention) +}); + +/** + * List slash commands available for `/` autocomplete. + */ +export const zListSlashCommandsRequest_unstable = z.object({ + cwd: z.union([ + z.string(), + z.null() + ]).optional(), + sessionId: z.union([ + z.string(), + z.null() + ]).optional() +}); + +/** + * All text that was typed after the command name is provided as input. + */ +export const zUnstructuredCommandInput = z.object({ + hint: z.string(), + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() +}); + +/** + * All text that was typed after the command name is provided as input. + */ +export const zAvailableCommandInput = zUnstructuredCommandInput; + +/** + * Information about a command. + */ +export const zAvailableCommand = z.object({ + name: z.string(), + description: z.string(), + input: z.union([ + zAvailableCommandInput, + z.null() + ]).optional(), + _meta: z.union([ + z.record(z.unknown()), + z.null() + ]).optional() +}); + +export const zListSlashCommandsResponse_unstable = z.object({ + availableCommands: z.array(zAvailableCommand) +}); + /** * Update an existing source's name, description, and content by absolute path. */ @@ -1982,6 +2064,8 @@ export const zExtRequest = z.object({ zUnarchiveSessionRequest_unstable, zCreateSourceRequest_unstable, zListSourcesRequest_unstable, + zListAgentMentionsRequest_unstable, + zListSlashCommandsRequest_unstable, zUpdateSourceRequest_unstable, zDeleteSourceRequest_unstable, zExportSourceRequest_unstable, @@ -2047,6 +2131,8 @@ export const zExtResponse = z.union([ zGetSessionInfoResponse_unstable, zCreateSourceResponse_unstable, zListSourcesResponse_unstable, + zListAgentMentionsResponse_unstable, + zListSlashCommandsResponse_unstable, zUpdateSourceResponse_unstable, zExportSourceResponse_unstable, zImportSourcesResponse_unstable,