Skip to content

Content Upload And Resolution

Binary content has two separate middleware steps:

  1. upload user-provided bytes into a durable or provider-native reference,
  2. resolve HPD's internal reference into content the current provider can read.

This split lets branch history keep stable references while each model turn still receives provider-facing content.

Upload User Content

ContentUploadMiddleware runs before the message turn. It scans the user message for DataContent with bytes, including typed content such as images, audio, documents, and video.

csharp
using HPD.Agent;
using Microsoft.Extensions.AI;

var agent = await new AgentBuilder()
    .WithChatClient(chatClient)
    .WithContentStore(new InMemoryContentStore())
    .BuildAsync();

var image = new DataContent(await File.ReadAllBytesAsync("diagram.png"), "image/png")
{
    Name = "diagram.png"
};

await agent.RunAsync(new UserMessagesInputEvent([
    new ChatMessage(ChatRole.User,
    [
        new TextContent("Describe this diagram."),
        image
    ])
])
{
    SessionId = "session-1",
    BranchId = "main"
});

The upload path depends on the run and provider configuration:

Upload pathResulting content
Hosted file clientHostedFileContent
Framework content storeUriContent(hpd-content://{contentId})
No configured path or failed required pathOriginal DataContent remains

AgentRunConfig.UploadStrategy controls which path is required or preferred:

StrategyBehavior
AutoPrefer hosted files when available; fall back to the content store when possible.
HostedRequire a hosted file client.
LocalRequire an IContentStore.

Hosted upload can come from a provider-created hosted-file client, AgentRunConfig.OverrideHostedFileClient, or the agent's resolved client set. Local upload requires WithContentStore(...).

Resolve Internal References

ContentReferenceResolverMiddleware runs before the model iteration. It looks for internal HPD content references:

text
hpd-content://{contentId}

Providers should not receive that internal URI directly. The resolver turns it into the best provider-facing shape available for the current run:

Resolution pathProvider sees
Temporary read URI from the content storeUriContent(directUri, mediaType)
Hosted file upload from the content streamHostedFileContent
Buffered fallbackDataContent

If resolution fails, the original internal reference is preserved and a failure event is emitted.

Why There Are Two Steps

Upload and resolution answer different questions:

StepQuestion
UploadWhere should these user bytes live after the initial message?
ResolutionWhat content shape can this provider consume right now?

This is especially useful for sessions and branches. A branch can persist hpd-content://... references, then later resolve them as direct URLs, hosted files, or buffered bytes depending on the provider and runtime environment.

Branch Scope

Local content-store upload and reference resolution are scoped to the active branch:

text
sessionId + branchId

Normal RunAsync(..., sessionId: ...) execution supplies that context through the active branch. When you construct UserMessagesInputEvent directly, include both SessionId and BranchId so upload and resolution use the same durable scope.

Sibling branches do not automatically resolve each other's local content references. Forking and replay should preserve the branch path that owns the content reference.

Events

Upload and resolution emit observability events:

Event familyMeaning
ContentUploadedEventUser bytes were stored in IContentStore.
HostedFileUploadedEventUser bytes were uploaded to a provider hosted-file client.
ContentUploadFailedEventLocal upload failed or a required local path was unavailable.
HostedFileUploadFailedEventHosted upload failed or a required hosted path was unavailable.
ContentReferenceResolvedEventAn internal HPD reference was resolved for provider use.
ContentReferenceResolutionFailedEventAn internal HPD reference could not be resolved.

Use these events for UI state, diagnostics, and tests. Do not infer upload or resolution success only from the final content type.

Audio Interaction

Audio input participates in the same content flow, but audio runtime detection happens before content upload. That lets the audio runtime preserve media identity, content index, name, media type, size, and transcript metadata even if later middleware turns the original AudioContent into a UriContent or hosted file reference.

For the audio-specific model behavior, see Speech To Text Input and Audio Runtime Attachment.

Built for production .NET agent applications.