From d3a4398e78260023c0320bcfd6097c787dedb7b1 Mon Sep 17 00:00:00 2001 From: viettranx Date: Wed, 25 Mar 2026 09:48:39 +0700 Subject: [PATCH] fix(gateway): sign file URLs in run.completed events for real-time display OnEvent callback only handled map[string]string payloads, but run.completed uses map[string]any (contains usage map + media slice). File URLs in content were never signed, and media paths were sent as raw local paths without ?ft= hash tokens. Switch on payload type to handle both shapes: sign content URLs and convert media local paths to /v1/files/basename?ft=hash. --- cmd/gateway_managed.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/cmd/gateway_managed.go b/cmd/gateway_managed.go index 6f1adb3c..a1e09a3c 100644 --- a/cmd/gateway_managed.go +++ b/cmd/gateway_managed.go @@ -166,9 +166,26 @@ func wireExtras( OnEvent: func(event agent.AgentEvent) { // Sign /v1/files/ and /v1/media/ URLs in content before delivery. // Sessions store clean paths; signing happens only at delivery time. - if m, ok := event.Payload.(map[string]string); ok { + secret := httpapi.FileSigningKey() + switch m := event.Payload.(type) { + case map[string]string: if c, has := m["content"]; has && strings.Contains(c, "/v1/") { - m["content"] = httpapi.SignFileURLs(c, httpapi.FileSigningKey()) + m["content"] = httpapi.SignFileURLs(c, secret) + } + case map[string]any: + // Sign /v1/ URLs in content text (run.completed payload is map[string]any). + if c, ok := m["content"].(string); ok && strings.Contains(c, "/v1/") { + m["content"] = httpapi.SignFileURLs(c, secret) + } + // Convert media local paths → signed /v1/files/basename?ft=hash + if rawMedia, ok := m["media"].([]agent.MediaResult); ok { + for i := range rawMedia { + basename := filepath.Base(rawMedia[i].Path) + url := "/v1/files/" + basename + ft := httpapi.SignFileToken(url, secret, httpapi.FileTokenTTL) + rawMedia[i].Path = url + "?ft=" + ft + } + m["media"] = rawMedia } } msgBus.Broadcast(bus.Event{