Add ACP session system prompt setter (#9478)
Signed-off-by: Bradley Axen <baxen@squareup.com>
This commit is contained in:
@@ -108,6 +108,37 @@ pub struct UpdateWorkingDirRequest {
|
||||
pub working_dir: String,
|
||||
}
|
||||
|
||||
/// How a session system prompt update should be applied.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SessionSystemPromptMode {
|
||||
/// Replace Goose's base system prompt with the provided text.
|
||||
Set,
|
||||
/// Append the provided text under Goose's "Additional Instructions" section.
|
||||
#[default]
|
||||
Append,
|
||||
}
|
||||
|
||||
/// Set, append, or clear system prompt text for a session.
|
||||
///
|
||||
/// `mode: "set"` replaces Goose's base system prompt. `mode: "append"` adds an
|
||||
/// instruction under "Additional Instructions". Reusing a key replaces the
|
||||
/// previous value for that mode/key; sending empty text clears it.
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
|
||||
#[request(
|
||||
method = "_goose/unstable/session/system-prompt/set",
|
||||
response = EmptyResponse
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SetSessionSystemPromptRequest {
|
||||
pub session_id: String,
|
||||
#[serde(default)]
|
||||
pub mode: SessionSystemPromptMode,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub key: Option<String>,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
/// Delete a session.
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
|
||||
#[request(method = "session/delete", response = EmptyResponse)]
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
"requestType": "UpdateWorkingDirRequest_unstable",
|
||||
"responseType": "EmptyResponse"
|
||||
},
|
||||
{
|
||||
"method": "_goose/unstable/session/system-prompt/set",
|
||||
"requestType": "SetSessionSystemPromptRequest_unstable",
|
||||
"responseType": "EmptyResponse"
|
||||
},
|
||||
{
|
||||
"method": "session/delete",
|
||||
"requestType": "DeleteSessionRequest",
|
||||
|
||||
@@ -167,6 +167,49 @@
|
||||
"x-side": "agent",
|
||||
"x-method": "_goose/unstable/session/working-dir/update"
|
||||
},
|
||||
"SetSessionSystemPromptRequest_unstable": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionId": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"$ref": "#/$defs/SessionSystemPromptMode",
|
||||
"default": "append"
|
||||
},
|
||||
"key": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionId",
|
||||
"text"
|
||||
],
|
||||
"description": "Set, append, or clear system prompt text for a session.\n\n`mode: \"set\"` replaces Goose's base system prompt. `mode: \"append\"` adds an\ninstruction under \"Additional Instructions\". Reusing a key replaces the\nprevious value for that mode/key; sending empty text clears it.",
|
||||
"x-side": "agent",
|
||||
"x-method": "_goose/unstable/session/system-prompt/set"
|
||||
},
|
||||
"SessionSystemPromptMode": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"const": "set",
|
||||
"description": "Replace Goose's base system prompt with the provided text."
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "append",
|
||||
"description": "Append the provided text under Goose's \"Additional Instructions\" section."
|
||||
}
|
||||
],
|
||||
"description": "How a session system prompt update should be applied."
|
||||
},
|
||||
"DeleteSessionRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -2640,6 +2683,15 @@
|
||||
"description": "Params for _goose/unstable/session/working-dir/update",
|
||||
"title": "UpdateWorkingDirRequest_unstable"
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/SetSessionSystemPromptRequest_unstable"
|
||||
}
|
||||
],
|
||||
"description": "Params for _goose/unstable/session/system-prompt/set",
|
||||
"title": "SetSessionSystemPromptRequest_unstable"
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
|
||||
@@ -59,6 +59,14 @@ impl GooseAcpAgent {
|
||||
self.on_update_working_dir(req).await
|
||||
}
|
||||
|
||||
#[custom_method(SetSessionSystemPromptRequest)]
|
||||
async fn dispatch_set_session_system_prompt(
|
||||
&self,
|
||||
req: SetSessionSystemPromptRequest,
|
||||
) -> Result<EmptyResponse, agent_client_protocol::Error> {
|
||||
self.on_set_session_system_prompt(req).await
|
||||
}
|
||||
|
||||
#[custom_method(DeleteSessionRequest)]
|
||||
async fn dispatch_delete_session(
|
||||
&self,
|
||||
|
||||
@@ -34,6 +34,47 @@ impl GooseAcpAgent {
|
||||
Ok(EmptyResponse {})
|
||||
}
|
||||
|
||||
pub(super) async fn on_set_session_system_prompt(
|
||||
&self,
|
||||
req: SetSessionSystemPromptRequest,
|
||||
) -> Result<EmptyResponse, agent_client_protocol::Error> {
|
||||
let session_id = req.session_id.trim();
|
||||
if session_id.is_empty() {
|
||||
return Err(
|
||||
agent_client_protocol::Error::invalid_params().data("sessionId cannot be empty")
|
||||
);
|
||||
}
|
||||
|
||||
let agent = self.get_session_agent_provider_ready(session_id).await?;
|
||||
match req.mode {
|
||||
SessionSystemPromptMode::Set => {
|
||||
if req.text.trim().is_empty() {
|
||||
agent.clear_system_prompt_override().await;
|
||||
} else {
|
||||
agent.override_system_prompt(req.text).await;
|
||||
}
|
||||
}
|
||||
SessionSystemPromptMode::Append => {
|
||||
let key = req
|
||||
.key
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|key| !key.is_empty())
|
||||
.ok_or_else(|| {
|
||||
agent_client_protocol::Error::invalid_params()
|
||||
.data("key cannot be empty for append mode")
|
||||
})?;
|
||||
if req.text.trim().is_empty() {
|
||||
agent.remove_system_prompt_extra(key).await;
|
||||
} else {
|
||||
agent.extend_system_prompt(key.to_string(), req.text).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(EmptyResponse {})
|
||||
}
|
||||
|
||||
pub(super) async fn on_delete_session(
|
||||
&self,
|
||||
req: DeleteSessionRequest,
|
||||
|
||||
@@ -66,8 +66,7 @@ use tracing::{debug, error, info, instrument, warn};
|
||||
|
||||
const DEFAULT_MAX_TURNS: u32 = 1000;
|
||||
const COMPACTION_THINKING_TEXT: &str = "goose is compacting the conversation...";
|
||||
const DEFAULT_FRONTEND_INSTRUCTIONS: &str =
|
||||
"The following tools are provided directly by the frontend and will be executed by the frontend when called.";
|
||||
const DEFAULT_FRONTEND_INSTRUCTIONS: &str = "The following tools are provided directly by the frontend and will be executed by the frontend when called.";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ToolCategory {
|
||||
@@ -2293,6 +2292,11 @@ impl Agent {
|
||||
prompt_manager.add_system_prompt_extra(key, instruction);
|
||||
}
|
||||
|
||||
pub async fn remove_system_prompt_extra(&self, key: &str) {
|
||||
let mut prompt_manager = self.prompt_manager.lock().await;
|
||||
prompt_manager.remove_system_prompt_extra(key);
|
||||
}
|
||||
|
||||
pub async fn set_goal(&self, goal: Option<String>) {
|
||||
*self.goal.lock().await = goal;
|
||||
}
|
||||
@@ -2463,6 +2467,11 @@ impl Agent {
|
||||
prompt_manager.set_system_prompt_override(template);
|
||||
}
|
||||
|
||||
pub async fn clear_system_prompt_override(&self) {
|
||||
let mut prompt_manager = self.prompt_manager.lock().await;
|
||||
prompt_manager.clear_system_prompt_override();
|
||||
}
|
||||
|
||||
pub async fn list_extension_prompts(&self, session_id: &str) -> HashMap<String, Vec<Prompt>> {
|
||||
self.extension_manager
|
||||
.list_prompts(session_id, CancellationToken::default())
|
||||
|
||||
@@ -224,6 +224,10 @@ impl PromptManager {
|
||||
self.system_prompt_extras.insert(key, instruction);
|
||||
}
|
||||
|
||||
pub fn remove_system_prompt_extra(&mut self, key: &str) {
|
||||
self.system_prompt_extras.shift_remove(key);
|
||||
}
|
||||
|
||||
pub fn record_tool_arguments(
|
||||
&mut self,
|
||||
arguments: &Option<serde_json::Map<String, serde_json::Value>>,
|
||||
@@ -247,6 +251,10 @@ impl PromptManager {
|
||||
self.system_prompt_override = Some(template);
|
||||
}
|
||||
|
||||
pub fn clear_system_prompt_override(&mut self) {
|
||||
self.system_prompt_override = None;
|
||||
}
|
||||
|
||||
pub fn builder<'a>(&'a self) -> SystemPromptBuilder<'a, Self> {
|
||||
SystemPromptBuilder {
|
||||
manager: self,
|
||||
@@ -326,6 +334,29 @@ mod tests {
|
||||
assert!(result.contains("Thirdinstruction"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_system_prompt_extra() {
|
||||
let mut manager = PromptManager::new();
|
||||
manager.add_system_prompt_extra("agent".to_string(), "Agent instruction".to_string());
|
||||
manager.add_system_prompt_extra("project".to_string(), "Project instruction".to_string());
|
||||
|
||||
manager.remove_system_prompt_extra("agent");
|
||||
let result = manager.builder().build();
|
||||
|
||||
assert!(!result.contains("Agent instruction"));
|
||||
assert!(result.contains("Project instruction"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear_system_prompt_override() {
|
||||
let mut manager = PromptManager::new();
|
||||
manager.set_system_prompt_override("Replacement prompt".to_string());
|
||||
assert!(manager.builder().build().contains("Replacement prompt"));
|
||||
|
||||
manager.clear_system_prompt_override();
|
||||
assert!(!manager.builder().build().contains("Replacement prompt"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_system_prompt_preserves_legitimate_unicode_in_extras() {
|
||||
let mut manager = PromptManager::new();
|
||||
|
||||
Reference in New Issue
Block a user