Skip to content

Commit b1bb06d

Browse files
fix(sdk,server,leptos): fix dynamic image serving and markdown path rendering for generate_image tool calls
- Implement Axum agent server '/media' endpoint to serve workspace files - Add absolute and relative image path rewriting in Leptos 'render_markdown' using '/media?path=...' - Add helper 'extract_image_paths_from_result' to parse structured or fallback absolute paths - Add unit tests in Leptos for markdown rewriting and image path extraction - Fix local and wasm SDK tool call extraction for GENERATE_IMAGE - Allow clippy::too_many_lines for extract_builtin_tool_call functions
1 parent 5538997 commit b1bb06d

4 files changed

Lines changed: 427 additions & 8 deletions

File tree

examples/agent_server/src/main.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,61 @@ async fn resolve_folder_handler(
806806
)
807807
}
808808

809+
// ---------------------------------------------------------------------------
810+
// GET /media — serve generated images and other local files
811+
// ---------------------------------------------------------------------------
812+
813+
/// Query parameters for `GET /media`.
814+
#[derive(Debug, Deserialize)]
815+
struct MediaParams {
816+
path: String,
817+
}
818+
819+
async fn media_handler(
820+
axum::extract::Query(params): axum::extract::Query<MediaParams>,
821+
) -> impl IntoResponse {
822+
use axum::http::header;
823+
824+
let path = std::path::Path::new(&params.path);
825+
826+
// Security: only serve files, not directories or symlinks to directories
827+
if !path.is_file() {
828+
return (
829+
StatusCode::NOT_FOUND,
830+
[(header::CONTENT_TYPE, "text/plain")],
831+
Vec::new(),
832+
)
833+
.into_response();
834+
}
835+
836+
let content_type = match path.extension().and_then(|e| e.to_str()) {
837+
Some("png") => "image/png",
838+
Some("jpg") | Some("jpeg") => "image/jpeg",
839+
Some("gif") => "image/gif",
840+
Some("webp") => "image/webp",
841+
Some("svg") => "image/svg+xml",
842+
_ => "application/octet-stream",
843+
};
844+
845+
match tokio::fs::read(path).await {
846+
Ok(bytes) => (
847+
StatusCode::OK,
848+
[(header::CONTENT_TYPE, content_type)],
849+
bytes,
850+
)
851+
.into_response(),
852+
Err(e) => {
853+
tracing::error!("media_handler: failed to read {:?}: {e}", path);
854+
(
855+
StatusCode::INTERNAL_SERVER_ERROR,
856+
[(header::CONTENT_TYPE, "text/plain")],
857+
Vec::new(),
858+
)
859+
.into_response()
860+
}
861+
}
862+
}
863+
809864
// ---------------------------------------------------------------------------
810865
// POST /halt
811866
// ---------------------------------------------------------------------------
@@ -1109,6 +1164,7 @@ async fn main() {
11091164
.route("/confirm", post(confirm_handler))
11101165
.route("/workspace", get(get_workspace_handler).post(set_workspace_handler))
11111166
.route("/resolve/folder", get(resolve_folder_handler))
1167+
.route("/media", get(media_handler))
11121168
.route("/health", get(health_handler))
11131169
.layer(cors)
11141170
.with_state(app_state.clone());

0 commit comments

Comments
 (0)