Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions crates/goose-server/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,10 @@ derive_utoipa!(Icon as IconSchema);
super::routes::config_management::check_provider,
super::routes::config_management::set_config_provider,
super::routes::config_management::get_pricing,
super::routes::prompts::get_prompts,
super::routes::prompts::get_prompt,
super::routes::prompts::save_prompt,
super::routes::prompts::reset_prompt,
super::routes::agent::start_agent,
super::routes::agent::resume_agent,
super::routes::agent::stop_agent,
Expand Down Expand Up @@ -427,6 +431,10 @@ derive_utoipa!(Icon as IconSchema);
super::routes::config_management::PricingQuery,
super::routes::config_management::PricingResponse,
super::routes::config_management::PricingData,
super::routes::prompts::PromptsListResponse,
super::routes::prompts::PromptContentResponse,
super::routes::prompts::SavePromptRequest,
goose::prompt_template::Template,
super::routes::action_required::ConfirmToolActionRequest,
super::routes::reply::ChatRequest,
super::routes::session::ImportSessionRequest,
Expand Down
6 changes: 3 additions & 3 deletions crates/goose-server/src/routes/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use goose::agents::ExtensionConfig;
use goose::config::resolve_extensions_for_new_session;
use goose::config::{Config, GooseMode};
use goose::model::ModelConfig;
use goose::prompt_template::render_global_file;
use goose::prompt_template::render_template;
use goose::providers::create;
use goose::recipe::Recipe;
use goose::recipe_deeplink;
Expand Down Expand Up @@ -418,7 +418,7 @@ async fn update_from_session(
})?;
let context: HashMap<&str, Value> = HashMap::new();
let desktop_prompt =
render_global_file("desktop_prompt.md", &context).expect("Prompt should render");
render_template("desktop_prompt.md", &context).expect("Prompt should render");
let mut update_prompt = desktop_prompt;
if let Some(recipe) = session.recipe {
match build_recipe_with_parameter_values(
Expand Down Expand Up @@ -691,7 +691,7 @@ async fn restart_agent_internal(

let context: HashMap<&str, Value> = HashMap::new();
let desktop_prompt =
render_global_file("desktop_prompt.md", &context).expect("Prompt should render");
render_template("desktop_prompt.md", &context).expect("Prompt should render");
let mut update_prompt = desktop_prompt;

if let Some(ref recipe) = session.recipe {
Expand Down
2 changes: 2 additions & 0 deletions crates/goose-server/src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod config_management;
pub mod errors;
pub mod mcp_app_proxy;
pub mod mcp_ui_proxy;
pub mod prompts;
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module routes::prompts is declared and merged into the router, but the actual implementation file crates/goose-server/src/routes/prompts.rs does not exist. This will cause a compilation failure. The file needs to be created with implementations for get_prompts, get_prompt, save_prompt, reset_prompt, and reset_all_prompts handlers.

Copilot uses AI. Check for mistakes.
pub mod recipe;
pub mod recipe_utils;
pub mod reply;
Expand All @@ -29,6 +30,7 @@ pub fn configure(state: Arc<crate::state::AppState>, secret_key: String) -> Rout
.merge(agent::routes(state.clone()))
.merge(audio::routes(state.clone()))
.merge(config_management::routes(state.clone()))
.merge(prompts::routes())
.merge(recipe::routes(state.clone()))
.merge(session::routes(state.clone()))
.merge(schedule::routes(state.clone()))
Expand Down
133 changes: 133 additions & 0 deletions crates/goose-server/src/routes/prompts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use axum::{
extract::Path,
routing::{delete, get, put},
Json, Router,
};
use goose::prompt_template::{
get_template, list_templates, reset_template, save_template, Template,
};
use http::StatusCode;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

#[derive(Serialize, ToSchema)]
pub struct PromptsListResponse {
Comment on lines +11 to +14
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend returns Template structs containing full prompt content (default_content and user_content), but the OpenAPI spec defines PromptInfo which only includes name, description, and is_customized. This creates an API contract mismatch where clients receive more data than documented. Create a separate PromptInfo struct or update the OpenAPI spec to match the actual response.

Copilot uses AI. Check for mistakes.
pub prompts: Vec<Template>,
}

#[derive(Serialize, ToSchema)]
pub struct PromptContentResponse {
pub name: String,
pub content: String,
pub default_content: String,
pub is_customized: bool,
}

#[derive(Deserialize, ToSchema)]
pub struct SavePromptRequest {
pub content: String,
}

#[utoipa::path(
get,
path = "/config/prompts",
responses(
(status = 200, description = "List of all available prompts", body = PromptsListResponse)
)
)]
pub async fn get_prompts() -> Json<PromptsListResponse> {
Json(PromptsListResponse {
prompts: list_templates(),
})
}

#[utoipa::path(
get,
path = "/config/prompts/{name}",
params(
("name" = String, Path, description = "Prompt template name (e.g., system.md)")
),
responses(
(status = 200, description = "Prompt content retrieved successfully", body = PromptContentResponse),
(status = 404, description = "Prompt not found")
)
)]
pub async fn get_prompt(
Path(name): Path<String>,
) -> Result<Json<PromptContentResponse>, StatusCode> {
let template = get_template(&name).ok_or(StatusCode::NOT_FOUND)?;

let content = template
.user_content
.as_ref()
.unwrap_or(&template.default_content);

Ok(Json(PromptContentResponse {
name: template.name,
content: content.clone(),
default_content: template.default_content,
is_customized: template.is_customized,
}))
}

#[utoipa::path(
put,
path = "/config/prompts/{name}",
params(
("name" = String, Path, description = "Prompt template name (e.g., system.md)")
),
request_body = SavePromptRequest,
responses(
(status = 200, description = "Prompt saved successfully", body = String),
(status = 404, description = "Prompt not found"),
(status = 500, description = "Failed to save prompt")
)
)]
pub async fn save_prompt(
Path(name): Path<String>,
Json(request): Json<SavePromptRequest>,
) -> Result<Json<String>, StatusCode> {
save_template(&name, &request.content).map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
StatusCode::NOT_FOUND
} else {
tracing::error!("Failed to save prompt {}: {}", name, e);
StatusCode::INTERNAL_SERVER_ERROR
}
})?;

Ok(Json(format!("Saved prompt: {}", name)))
}

#[utoipa::path(
delete,
path = "/config/prompts/{name}",
params(
("name" = String, Path, description = "Prompt template name (e.g., system.md)")
),
responses(
(status = 200, description = "Prompt reset to default successfully", body = String),
(status = 404, description = "Prompt not found"),
(status = 500, description = "Failed to reset prompt")
)
)]
pub async fn reset_prompt(Path(name): Path<String>) -> Result<Json<String>, StatusCode> {
reset_template(&name).map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
StatusCode::NOT_FOUND
} else {
tracing::error!("Failed to reset prompt {}: {}", name, e);
StatusCode::INTERNAL_SERVER_ERROR
}
})?;

Ok(Json(format!("Reset prompt to default: {}", name)))
}

pub fn routes() -> Router {
Router::new()
Comment on lines +122 to +128
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new prompts routes lack test coverage. Other routes in this codebase include tests for their endpoint handlers (see action_required.rs, audio.rs, config_management.rs, recipe.rs, reply.rs). Add tests to verify the behavior of get_prompts, get_prompt, save_prompt, and reset_prompt handlers.

Copilot uses AI. Check for mistakes.
.route("/config/prompts", get(get_prompts))
.route("/config/prompts/{name}", get(get_prompt))
.route("/config/prompts/{name}", put(save_prompt))
.route("/config/prompts/{name}", delete(reset_prompt))
}
4 changes: 2 additions & 2 deletions crates/goose-server/src/routes/recipe_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::state::AppState;
use anyhow::Result;
use axum::http::StatusCode;
use goose::agents::Agent;
use goose::prompt_template::render_global_file;
use goose::prompt_template::render_template;
use goose::recipe::build_recipe::{build_recipe_from_template, RecipeError};
use goose::recipe::local_recipes::{get_recipe_library_dir, list_local_recipes};
use goose::recipe::validate_recipe::validate_recipe_template_from_content;
Expand Down Expand Up @@ -173,6 +173,6 @@ pub async fn apply_recipe_to_agent(
recipe.instructions.as_ref().map(|instructions| {
let mut context: HashMap<&str, Value> = HashMap::new();
context.insert("recipe_instructions", Value::String(instructions.clone()));
render_global_file("desktop_recipe_instruction.md", &context).expect("Prompt should render")
render_template("desktop_recipe_instruction.md", &context).expect("Prompt should render")
})
}
2 changes: 1 addition & 1 deletion crates/goose/src/agents/extension_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,7 @@ impl ExtensionManager {
let mut context: HashMap<&str, Value> = HashMap::new();
context.insert("tools", serde_json::to_value(tools_info).unwrap());

prompt_template::render_global_file("plan.md", &context).expect("Prompt should render")
prompt_template::render_template("plan.md", &context).expect("Prompt should render")
}

/// Find and return a reference to the appropriate client for a tool call
Expand Down
6 changes: 3 additions & 3 deletions crates/goose/src/agents/prompt_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,9 @@ impl<'a> SystemPromptBuilder<'a, PromptManager> {

let base_prompt = if let Some(override_prompt) = &self.manager.system_prompt_override {
let sanitized_override_prompt = sanitize_unicode_tags(override_prompt);
prompt_template::render_inline_once(&sanitized_override_prompt, &context)
prompt_template::render_string(&sanitized_override_prompt, &context)
} else {
prompt_template::render_global_file("system.md", &context)
prompt_template::render_template("system.md", &context)
}
.unwrap_or_else(|_| {
"You are a general-purpose AI agent called goose, created by Block".to_string()
Expand Down Expand Up @@ -245,7 +245,7 @@ impl PromptManager {

pub async fn get_recipe_prompt(&self) -> String {
let context: HashMap<&str, Value> = HashMap::new();
prompt_template::render_global_file("recipe.md", &context)
prompt_template::render_template("recipe.md", &context)
.unwrap_or_else(|_| "The recipe prompt is busted. Tell the user.".to_string())
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/goose/src/agents/subagent_handler.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
agents::{subagent_task_config::TaskConfig, Agent, AgentConfig, AgentEvent, SessionConfig},
conversation::{message::Message, Conversation},
prompt_template::render_global_file,
prompt_template::render_template,
recipe::Recipe,
};
use anyhow::{anyhow, Result};
Expand Down Expand Up @@ -148,7 +148,7 @@ fn get_agent_messages(
.await;

let tools = agent.list_tools(&session_id, None).await;
let subagent_prompt = render_global_file(
let subagent_prompt = render_template(
"subagent_system.md",
&SubagentPromptContext {
max_turns: task_config
Expand Down
4 changes: 2 additions & 2 deletions crates/goose/src/context_mgmt/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::conversation::message::{ActionRequiredData, MessageMetadata};
use crate::conversation::message::{Message, MessageContent};
use crate::conversation::{merge_consecutive_messages, Conversation};
use crate::prompt_template::render_global_file;
use crate::prompt_template::render_template;
use crate::providers::base::{Provider, ProviderUsage};
use crate::providers::errors::ProviderError;
use crate::{config::Config, token_counter::create_token_counter};
Expand Down Expand Up @@ -294,7 +294,7 @@ async fn do_compact(
messages: messages_text,
};

let system_prompt = render_global_file("summarize_oneshot.md", &context)?;
let system_prompt = render_template("compaction.md", &context)?;

let user_message = Message::user()
.with_text("Please summarize the conversation history provided in the system prompt.");
Expand Down
4 changes: 2 additions & 2 deletions crates/goose/src/permission/permission_judge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::config::permission::PermissionLevel;
use crate::config::PermissionManager;
use crate::conversation::message::{Message, MessageContent, ToolRequest};
use crate::conversation::Conversation;
use crate::prompt_template::render_global_file;
use crate::prompt_template::render_template;
use crate::providers::base::Provider;
use chrono::Utc;
use indoc::indoc;
Expand Down Expand Up @@ -140,7 +140,7 @@ pub async fn detect_read_only_tools(
let check_messages = create_check_messages(tool_requests);

let context = PermissionJudgeContext {};
let system_prompt = render_global_file("permission_judge.md", &context)
let system_prompt = render_template("permission_judge.md", &context)
.unwrap_or_else(|_| "You are a good analyst and can detect operations whether they have read-only operations.".to_string());

let res = provider
Expand Down
Loading
Loading