Artifacts
Stream agent-generated files (reports, charts, code) to the UI
Artifacts are files produced by the agent during a task that can be streamed to the UI. The SDK surfaces them via useSandAgentChat and useArtifacts hooks.
Quick UI Example
import { useSandAgentChat } from "@sandagent/sdk/react";
export default function ChatPage() {
const {
messages,
sendMessage,
artifacts,
selectedArtifact,
setSelectedArtifact,
} = useSandAgentChat({ apiEndpoint: "/api/ai" });
return (
<div className="flex">
<div className="flex-1">{/* chat UI */}</div>
{artifacts.length > 0 && (
<div className="w-96 border-l">
<div className="flex gap-2 p-2 border-b">
{artifacts.map((artifact) => (
<button
key={artifact.artifactId}
onClick={() => setSelectedArtifact(artifact)}
>
{artifact.artifactId}
</button>
))}
</div>
{selectedArtifact && (
<div className="p-4">
<pre>{selectedArtifact.content}</pre>
</div>
)}
</div>
)}
</div>
);
}useArtifacts (Standalone)
If you have a custom chat UI, extract artifacts directly:
import { useArtifacts } from "@sandagent/sdk/react";
function MyArtifactPanel({ messages }) {
const {
artifacts,
selectedArtifact,
setSelectedArtifact,
hasArtifacts,
copyContent,
downloadArtifact,
} = useArtifacts({ messages });
if (!hasArtifacts) return null;
return (
<div>
{artifacts.map((a) => (
<button key={a.artifactId} onClick={() => setSelectedArtifact(a)}>
{a.artifactId}
</button>
))}
{selectedArtifact && <pre>{selectedArtifact.content}</pre>}
</div>
);
}Backend Setup
Register an ArtifactProcessor with the provider:
import { createSandAgent, LocalSandbox } from "@sandagent/sdk";
import { createUIMessageStream, createUIMessageStreamResponse, streamText } from "ai";
export async function POST(request: Request) {
const { messages } = await request.json();
const sandbox = new LocalSandbox({ workdir: process.cwd() });
const stream = createUIMessageStream({
execute: async ({ writer }) => {
const artifactProcessor = new TaskDrivenArtifactProcessor({
sandbox,
workdir: sandbox.getWorkdir?.() || "/sandagent",
writer,
});
const sandagent = createSandAgent({
sandbox,
artifactProcessors: [artifactProcessor],
});
const result = streamText({ model: sandagent("sonnet"), messages });
writer.merge(result.toUIMessageStream());
await result.response;
},
});
return createUIMessageStreamResponse({ stream });
}Artifact Manifest
The agent writes a manifest at tasks/{sessionId}/artifact.json:
{
"artifacts": [
{
"id": "report",
"path": "tasks/{sessionId}/reports/report.md",
"mimeType": "text/markdown",
"description": "Main report"
}
]
}| Field | Required | Description |
|---|---|---|
id | No | Unique identifier for UI de-duplication |
path | Yes | Relative or absolute file path |
mimeType | No | e.g. text/markdown, text/html |
description | No | Short description |
Artifact Skill (Recommended)
Add an artifact skill to your template so the agent reliably writes the manifest:
templates/your-template/
.claude/
skills/
artifact/
SKILL.mdThe skill should instruct the agent to:
- Create
tasks/${CLAUDE_SESSION_ID}/artifact.json - Add entries whenever a new output file is created
- Keep paths consistent with the workdir
A reference implementation exists at templates/researcher/.claude/skills/artifact/SKILL.md.
Rendering Tips
- Use Markdown rendering for
text/markdown - Render
text/htmlin a sandboxed iframe - For large text, show a scrollable
<pre>
Troubleshooting
- No artifacts showing → confirm
artifact.jsonexists and paths are valid - Duplicate artifacts → ensure
idis unique and stable - Infinite render loop → memoize extracted artifacts when using custom hooks