Artifacts

Parse and render rich content delivered inline
View as Markdown

Artifacts are rich content objects embedded in chat messages. Learn how the WebSocket delivers them JIT and how to parse and render artifacts like doctor profiles and custom data structures.

What are artifacts?

Artifacts are structured data objects that provide additional context or information beyond plain text messages. Common examples include:

  • Doctor profiles: Healthcare provider information with NPI, specialty, ratings, etc.
  • Provider search results: Lists of providers from search queries with locations and distances
  • Structured data: Custom data objects specific to your use case
  • Rich content: Any JSON-serializable object

How artifacts are delivered

Artifacts are not fetched via REST API. The WebSocket backend expands artifact tags just-in-time (JIT) during streaming. When the AI references an artifact, the backend replaces the tag with the full artifact JSON and embeds it directly in message.text.

You receive artifact content inline—no separate fetch is needed. The message text contains embedded JSON objects with item_type, item_content, and created_at.

Parsing embedded artifacts

Parse message.text for JSON objects with item_type and item_content:

1function parseEmbeddedArtifacts(text: string): Array<{ item_type: string; item_content: unknown }> {
2 const artifacts: Array<{ item_type: string; item_content: unknown }> = [];
3 let i = 0;
4
5 while (i < text.length) {
6 const start = text.indexOf('{', i);
7 if (start === -1) break;
8
9 let braceCount = 0;
10 let end = start;
11 let inString = false;
12 let escapeNext = false;
13
14 for (let j = start; j < text.length; j++) {
15 const char = text[j];
16 if (escapeNext) { escapeNext = false; continue; }
17 if (char === '\\') { escapeNext = true; continue; }
18 if (char === '"') { inString = !inString; continue; }
19 if (inString) continue;
20 if (char === '{') braceCount++;
21 else if (char === '}') {
22 braceCount--;
23 if (braceCount === 0) { end = j + 1; break; }
24 }
25 }
26
27 if (braceCount === 0 && end > start) {
28 try {
29 const obj = JSON.parse(text.slice(start, end));
30 if (obj && typeof obj === 'object' && obj.item_type && obj.item_content) {
31 artifacts.push({ item_type: obj.item_type, item_content: obj.item_content });
32 }
33 } catch { /* skip invalid JSON */ }
34 i = end;
35 } else {
36 i = start + 1;
37 }
38 }
39 return artifacts;
40}

Artifact structure

The inline JSON format embedded in message text:

1interface InlineArtifact<T = unknown> {
2 item_type: string;
3 item_content: T;
4 created_at?: string;
5}

Doctor profile artifacts

Doctor profiles are a common artifact type:

1interface DoctorProfileArtifact {
2 npi: string;
3 first_name?: string;
4 last_name?: string;
5 title?: string;
6 specialty?: string;
7 languages_spoken?: string[];
8 gender?: string;
9 rating?: number;
10 review_count?: number;
11 last_updated_at?: string;
12 locations?: unknown;
13 rank_score?: number;
14 mrf_rates?: Array<Record<string, unknown>>;
15 out_of_pocket_costs?: Array<{
16 procedure_code: string;
17 procedure_code_type?: string;
18 procedure_name?: string;
19 rate?: number;
20 out_of_pocket: number;
21 }>;
22}

Parsing doctor profiles from message text

1const artifacts = parseEmbeddedArtifacts(message.text);
2
3artifacts.forEach(({ item_type, item_content }) => {
4 if (item_type === "doctor_profile") {
5 const profile = item_content as DoctorProfileArtifact;
6 console.log("Name:", `${profile.first_name} ${profile.last_name}`);
7 console.log("Specialty:", profile.specialty);
8 console.log("Rating:", profile.rating);
9 console.log("NPI:", profile.npi);
10 }
11});

Provider search results artifacts

The provider_search_results artifact type contains search results from the provider search tool:

1interface ProviderSearchResultsArtifact {
2 providers: ProviderResult[];
3 query: string;
4 location: string;
5 taxonomy_codes: string[];
6 plan_name: string;
7 filter_gender: string | null;
8 filter_languages: string[] | null;
9}
10
11interface ProviderResult {
12 npi: string;
13 name: string | null;
14 specialties: string[];
15 degrees: string[];
16 languages: string[];
17 locations: LocationResult[];
18}
19
20interface LocationResult {
21 name: string | null;
22 address_line_1: string | null;
23 address_line_2: string | null;
24 city: string | null;
25 state: string | null;
26 zip: string | null;
27 distance_miles: number | null;
28}

Parsing provider search results

1const artifacts = parseEmbeddedArtifacts(message.text);
2
3artifacts.forEach(({ item_type, item_content }) => {
4 if (item_type === "provider_search_results") {
5 const results = item_content as ProviderSearchResultsArtifact;
6 console.log("Query:", results.query);
7 results.providers.forEach((provider) => {
8 console.log(provider.name, provider.npi, provider.specialties);
9 });
10 }
11});

Rendering artifacts

The createSunnyChat widget automatically parses and renders doctor profiles and provider search results from message text:

1import { createSunnyChat } from "@sunnyhealthai/agents-sdk";
2
3const chat = createSunnyChat({
4 container: document.getElementById("chat"),
5 partnerIdentifier: "acme-health",
6 publicKey: "pk-sunnyagents_abc_xyz",
7 authType: "passwordless",
8});

Doctor profiles and provider search results are automatically rendered when embedded in messages.

Embedded artifact format

Message text contains JSON blobs—no {art_tag} tags are visible to the client. The backend expands {art_tag}uuid{/art_tag} server-side before streaming. What you receive looks like:

Here's a doctor who might work for you: {"item_type":"doctor_profile","item_content":{"npi":"1234567890","first_name":"Jane","last_name":"Smith","specialty":"Cardiology","rating":4.8,"review_count":150},"created_at":"2025-01-15T12:00:00Z"}

Error handling

Malformed JSON

When parsing, handle invalid JSON gracefully:

1try {
2 const obj = JSON.parse(jsonStr);
3 if (obj?.item_type && obj?.item_content) {
4 // Valid artifact
5 }
6} catch {
7 // Skip or show fallback for malformed JSON
8}

Best practices

  1. Parse defensively: Wrap JSON.parse in try/catch; skip invalid objects
  2. Type safety: Use TypeScript assertions or guards for item_content by item_type
  3. Fallback UI: Provide fallback when an artifact fails to parse
  4. No loading states: Artifacts arrive with the message—no fetch loading needed

Next steps