User configurable templates (#6420)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -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,
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
@@ -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(
|
||||
@@ -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 {
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod config_management;
|
||||
pub mod errors;
|
||||
pub mod mcp_app_proxy;
|
||||
pub mod mcp_ui_proxy;
|
||||
pub mod prompts;
|
||||
pub mod recipe;
|
||||
pub mod recipe_utils;
|
||||
pub mod reply;
|
||||
@@ -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()))
|
||||
|
||||
@@ -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 {
|
||||
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()
|
||||
.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))
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user