Assets
Assets are files that workflows can reference, such as text, images, and other media.
Rather than embedding file contents directly into context (which can bloat the database), store assets separately and reference them by AssetRef.
Depending on their scope, assets can be reused across workflows, reused across conversation turns, or kept private to a single run.
AssetRef
Instead of passing file contents into context, pass an AssetRef. It contains the unique identifier of the asset along with helpful metadata such as:
- Name
- Media type
- Size
Asset Scopes
Assets are scoped in three ways:
- Library assets are independent of workflows.
- Run assets are tied to a specific run.
- Conversation assets are tied to a specific conversation across multiple turns.
Library Assets
Library assets allow for reuse and are not tied to a specific workflow or workflow run. They are typically added via the editor where you can list, add, rename, move, and delete them. Library assets can be organized into folders, and those folders are preserved during transfer export/import. Renaming is available when one asset is selected and edits the complete name, including its extension. Renaming refuses a name already used by another asset in the same folder; leaving the name unchanged is a no-op. Uploading an asset with the same name in the same folder, matched case-insensitively, overwrites that existing asset so repeat uploads are idempotent. Asset and asset-folder names cannot contain commas because commas separate fallback names in asset templates. The editor asset list shows created and modified dates and allows sorting by either date. New assets initially use their created date as their modified date; editing an asset's contents, renaming it, moving it, or overwriting it through upload updates the modified date. Assets created before modified-date tracking was added display and sort using their created date until they are changed. Programmatic changes are also possible.
Run Assets
Run assets are scoped to a specific workflow run; when that run is deleted, the linked assets are deleted as well. You cannot view run assets in the editor asset library; instead, navigate to the workflow run. These assets are typically used when you want to provide a one-off asset for a single run or for images created as part of the run.
Conversation Assets
Conversation assets are scoped to a specific conversation and remain available across later turns in that same conversation. When the conversation is deleted, the linked conversation assets are deleted as well. They do not appear in the editor asset library because they are not shared assets. Instead, they are shown on the workflow trace panel separately from per-turn run assets.
Conversation assets are useful when a conversation-enabled workflow needs to accumulate artifacts across turns. For example, you might store a generated transcript, an uploaded reference file, or a draft document that later turns keep reusing.
Only library assets support folders. Run and conversation assets cannot be assigned to folders.
Programmatic Examples
You can create and resolve AssetRef instances in code, then place them into a context for workflow input. These examples assume you have resolved IAssetService and IRepositoryService from dependency injection.
Use library asset
// Load the named asset
var asset = await repositoryService.GetLibraryAssetByName("document.png");
if (asset is null)
throw new InvalidOperationException("Asset 'document.png' was not found in the library.");
// Create an AssetRef from the asset
var assetRef = new AssetRef(asset);
// Only an AssetRef or AssetRefList can be added into Context
var context = new ContextObject();
context.Set("input.image", assetRef);
Add library asset
// File helper loads bytes from a named file on your local system
var data = await File.ReadAllBytesAsync("document.png");
// Create new library AssetRef from loaded the bytes
var libraryAssetRef = await assetService.CreateFromBytesAsync(
data,
"document.png",
"image/png",
AssetScope.Library);
// Only an AssetRef or AssetRefList can be added into Context
var context = new ContextObject();
context.Set("input.image", libraryAssetRef);
Add run-scoped asset
// File helper loads bytes from a named file on your local system
var data = await File.ReadAllBytesAsync("document.png");
// Specify Run scope and provide the run identifier
var runAssetRef = await assetService.CreateFromBytesAsync(
data,
"document.png",
"image/png",
AssetScope.Run,
runId);
// Only an AssetRef or AssetRefList can be added into Context
var context = new ContextObject();
context.Set("input.image", runAssetRef);
Add conversation-scoped asset
// File helper loads bytes from a named file on your local system
var data = await File.ReadAllBytesAsync("transcript.txt");
// Specify Conversation scope and provide the conversation identifier
var conversationAssetRef = await assetService.CreateFromBytesAsync(
data,
"transcript.txt",
"text/plain",
AssetScope.Conversation,
conversationId: conversationId);
// Only an AssetRef or AssetRefList can be added into Context
var context = new ContextObject();
context.Set("input.transcript", conversationAssetRef);
Viewing assets in the editor
Asset content is always served by the editor API at api/assets/{id}/content, whichever store is configured. No store ever hands the browser a direct link to the underlying file or blob, so a store reachable only by the server works exactly like a local one.
The editor reads that endpoint through its HTTP client rather than pointing an image tag at it, and renders the bytes it receives. This matters for hosts that require authentication, because only requests made through the HTTP client carry the bearer token supplied by window.sharpomaticAuth; an image tag pointing at the API would make an unauthenticated browser request and fail to display.
Storage
During application configuration, you need to specify how assets will be stored.
FileSystemAssetStore
This is the simplest implementation and uses your local file system. It is great for getting up and running quickly, and you can override the default location using appsettings. It is not recommended for production systems.
To use this store, you need the following in your startup code.
builder.Services.AddSingleton<IAssetStore, FileSystemAssetStore>();
builder.Services.Configure<FileSystemAssetStoreOptions>(
builder.Configuration.GetSection("AssetStorage:FileSystem"));
By default it will store assets in a subdirectory of your user profile directory.
C:\Users\<username>\AppData\Local\SharpOMatic\Assets
To override this, set the RootPath in appsettings.
"AssetStorage": {
"FileSystem": {
"RootPath": "C:\\MyStorageDir"
}
}
AzureBlobStorageAssetStore
This connects to an Azure-hosted Blob Storage service. For production systems, it provides a more secure and reliable storage mechanism. Set appropriate connection settings in your appsettings.
To use this store, you need the following in your startup code.
builder.Services.AddSingleton<IAssetStore, AzureBlobStorageAssetStore>();
builder.Services.Configure<AzureBlobStorageAssetStoreOptions>(
builder.Configuration.GetSection("AssetStorage:AzureBlobStorage"));
You have two choices for setting the blob storage in appsettings. Either provide the ConnectionString for the blob storage container.
"AssetStorage": {
"AzureBlobStorage": {
"ContainerName": "assets",
"ConnectionString": ""
}
}
Or you can specify the ServiceUri along with the ContainerName.
"AssetStorage": {
"AzureBlobStorage": {
"ContainerName": "assets",
"ServiceUri": ""
}
}
When using ServiceUri, the store uses DefaultAzureCredential by default.
If your application handles Azure credentials differently, register the credential you want SharpOMatic to use.
builder.Services.AddSingleton<TokenCredential>(_ =>
new ManagedIdentityCredential(clientId: "..."));
builder.Services.AddSingleton<IAssetStore, AzureBlobStorageAssetStore>();
builder.Services.Configure<AzureBlobStorageAssetStoreOptions>(
builder.Configuration.GetSection("AssetStorage:AzureBlobStorage"));