You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add a new HTTP endpoint that streams a single persisted image DataContent's bytes for use as an <img src="..."> target in the agent-chat HTML view. The endpoint is registered inside AgentPersistenceEndpointRouteBuilderExtensions.MapAgentPersistenceEndpoints, so BOTH WorkspacesWebHost (in-app) and Phantom.Workspaces.Web.Server (external host) expose it automatically — same seam introduced by #1209.
GET /agent/persistence/{sessionId}/message/{sequence:long}/content/{contentIndex:int}/image
Persisted image bytes live inside ChatMessage.Payload on MongoDbPersistedMessageDocument. The HTML view (sub-item C) needs a URL that returns those bytes with a proper Content-Type so <img> can render them without inlining a multi-megabyte base64 data URI into the HTML fragment stream.
Web host & routing today.features/Phantom.Workspaces/Services/WorkspacesWebHost.cs:84-90 and features/Phantom.Workspaces.Web.Server/Program.cs:53-59 both compose the same route surface via extension methods:
MapWebDataAccessEndpoints
MapAgentEndpoints
MapTransportReverseEndpoints
MapWorkspaceToolEndpoints
MapAgentPersistenceEndpoints
The last currently exposes POST /agent/persistence/store, .../restore, .../messages, .../sub-agent-links/add, .../sub-agent-links/read — see features/Phantom.Workspaces.Web.Server/AgentPersistenceEndpointRouteBuilderExtensions.cs:106-132. Adding the new endpoint inside the same MapAgentPersistenceEndpoints method means both hosts pick it up with no wiring changes — see #1209.
Retrieval source. Reads MUST go through IAgentPersistenceStore (Mongo), NOT AgentChatSessionCache. AgentChatSessionCache (Phantom.Workspaces.Llm.Core/AgentChatSessionCache.cs:20-35, registered as a singleton at Phantom.Workspaces.Web.Server/Program.cs:38) only holds liveAgentChat objects for currently-open sessions; a reload, restart, or opening a historical session from a different host process would break the image tags. The persistence store is the durable source of truth and is already reachable from web endpoints — see AgentPersistenceEndpointRouteBuilderExtensions.cs:110-113 (RequestServices.GetService<IAgentPersistenceStore>()).
No new call needed — the extension is already chained around :84-90; endpoint appears automatically. Cited for coverage.
features/Phantom.Workspaces.Web.Server/Program.cs
No new call needed — endpoint appears via existing app.MapAgentPersistenceEndpoints() (:59). Cited for coverage.
Design / Fix
Endpoint contract
GET /agent/persistence/{sessionId}/message/{sequence:long}/content/{contentIndex:int}/image
Success (200): raw image bytes as body; Content-Type set from DataContent.MediaType (verbatim); Cache-Control: private, max-age=3600; Content-Length from bytes.
400 if sessionId is missing or whitespace after route decoding.
404 if the message at that sequence does not exist for sessionId, if contentIndex is out of range, or if the content at that index is not a DataContent with an image/* media type.
503 if IAgentPersistenceStore is not registered in the request-service container.
Non-image DataContent (e.g. application/pdf) intentionally 404s at this endpoint: this URL is image-only. A future non-image endpoint can be added separately.
Expected Tests
Naming style Subject_Scenario_ExpectedOutcome; class name matches the existing endpoint suite (AgentPersistenceEndpointTests in features/Phantom.Workspaces.Web.Server.Tests).
After storing a message with DataContent(image/png, bytes), GET /agent/persistence/{sid}/message/{seq}/content/{idx}/image returns 200, body bytes equal the persisted Data, Content-Type equals image/png.
Coverage that MapAgentPersistenceEndpoints (used by both WorkspacesWebHost and Phantom.Workspaces.Web.Server) exposes the new route — same technique as existing endpoint-registration assertions in this suite (per #1209 seam).
Part of #1216
Summary
Add a new HTTP endpoint that streams a single persisted image
DataContent's bytes for use as an<img src="...">target in the agent-chat HTML view. The endpoint is registered insideAgentPersistenceEndpointRouteBuilderExtensions.MapAgentPersistenceEndpoints, so BOTHWorkspacesWebHost(in-app) andPhantom.Workspaces.Web.Server(external host) expose it automatically — same seam introduced by #1209.Dependencies
IAgentPersistenceStore.ReadMessageContentAsyncread-by-index method that Persistence: read-by-index for image DataContent by (AgentSessionId, Sequence, ContentIndex) #1217 adds; the endpoint is a thin HTTP wrapper on top of it).Root Cause / Current Behavior
Persisted image bytes live inside
ChatMessage.PayloadonMongoDbPersistedMessageDocument. The HTML view (sub-item C) needs a URL that returns those bytes with a properContent-Typeso<img>can render them without inlining a multi-megabyte base64 data URI into the HTML fragment stream.Web host & routing today.
features/Phantom.Workspaces/Services/WorkspacesWebHost.cs:84-90andfeatures/Phantom.Workspaces.Web.Server/Program.cs:53-59both compose the same route surface via extension methods:MapWebDataAccessEndpointsMapAgentEndpointsMapTransportReverseEndpointsMapWorkspaceToolEndpointsMapAgentPersistenceEndpointsThe last currently exposes
POST /agent/persistence/store,.../restore,.../messages,.../sub-agent-links/add,.../sub-agent-links/read— seefeatures/Phantom.Workspaces.Web.Server/AgentPersistenceEndpointRouteBuilderExtensions.cs:106-132. Adding the new endpoint inside the sameMapAgentPersistenceEndpointsmethod means both hosts pick it up with no wiring changes — see #1209.Retrieval source. Reads MUST go through
IAgentPersistenceStore(Mongo), NOTAgentChatSessionCache.AgentChatSessionCache(Phantom.Workspaces.Llm.Core/AgentChatSessionCache.cs:20-35, registered as a singleton atPhantom.Workspaces.Web.Server/Program.cs:38) only holds liveAgentChatobjects for currently-open sessions; a reload, restart, or opening a historical session from a different host process would break the image tags. The persistence store is the durable source of truth and is already reachable from web endpoints — seeAgentPersistenceEndpointRouteBuilderExtensions.cs:110-113(RequestServices.GetService<IAgentPersistenceStore>()).Affected Files
features/Phantom.Workspaces.Web.Server/AgentPersistenceEndpointRouteBuilderExtensions.csGET /agent/persistence/{sessionId}/message/{sequence:long}/content/{contentIndex:int}/imageinsideMapAgentPersistenceEndpoints; callIAgentPersistenceStore.ReadMessageContentAsyncfrom #1217; map result → 200 with bytes / 404 / 400 / 503.features/Phantom.Workspaces/Services/WorkspacesWebHost.cs:84-90; endpoint appears automatically. Cited for coverage.features/Phantom.Workspaces.Web.Server/Program.csapp.MapAgentPersistenceEndpoints()(:59). Cited for coverage.Design / Fix
Endpoint contract
GET /agent/persistence/{sessionId}/message/{sequence:long}/content/{contentIndex:int}/imageContent-Typeset fromDataContent.MediaType(verbatim);Cache-Control: private, max-age=3600;Content-Lengthfrom bytes.sessionIdis missing or whitespace after route decoding.sequencedoes not exist forsessionId, ifcontentIndexis out of range, or if the content at that index is not aDataContentwith animage/*media type.IAgentPersistenceStoreis not registered in the request-service container.?v={sha256(Data)[..16]}for cache-busting; endpoint MAY validate the fingerprint and 404 on mismatch (see Persistence: read-by-index for image DataContent by (AgentSessionId, Sequence, ContentIndex) #1217 identity scheme). MVP: accept and ignore.Sketch
Notes:
MapAgentPersistenceEndpointsmeans bothWorkspacesWebHostandPhantom.Workspaces.Web.Serverexpose the route automatically.ReadMessageContentAsync(from Persistence: read-by-index for image DataContent by (AgentSessionId, Sequence, ContentIndex) #1217).DataContent(e.g.application/pdf) intentionally 404s at this endpoint: this URL is image-only. A future non-image endpoint can be added separately.Expected Tests
Naming style
Subject_Scenario_ExpectedOutcome; class name matches the existing endpoint suite (AgentPersistenceEndpointTestsinfeatures/Phantom.Workspaces.Web.Server.Tests).AgentImageEndpoint_WhenRequestedByMessageAndIndex_ReturnsImageBytesWithMediaTypeAgentPersistenceEndpointTestsDataContent(image/png, bytes),GET /agent/persistence/{sid}/message/{seq}/content/{idx}/imagereturns 200, body bytes equal the persistedData,Content-Typeequalsimage/png.AgentImageEndpoint_WhenSequenceOutOfRange_Returns404AgentPersistenceEndpointTestssequencefor the session → 404.AgentImageEndpoint_WhenContentIndexOutOfRange_Returns404AgentPersistenceEndpointTestsAgentImageEndpoint_WhenContentIsNotImage_Returns404AgentPersistenceEndpointTestsDataContentwith non-image/*media type orTextContentat index → 404.AgentImageEndpoint_WhenPersistenceStoreUnavailable_Returns503AgentPersistenceEndpointTestsIAgentPersistenceStoreis not registered → 503 (companion to existingStoreAsync_WhenServerReturns503_ThrowsHttpRequestExceptionshape).AgentImageEndpoint_WhenSessionIdMissing_Returns400AgentPersistenceEndpointTestssessionIdroute param → 400.AgentImageEndpoint_SetsCacheControlPrivateMaxAgeAgentPersistenceEndpointTestsCache-Control: private, max-age=3600.AgentImageEndpoint_IsRegisteredOnWorkspacesWebHostAgentPersistenceEndpointTestsMapAgentPersistenceEndpoints(used by bothWorkspacesWebHostandPhantom.Workspaces.Web.Server) exposes the new route — same technique as existing endpoint-registration assertions in this suite (per #1209 seam).