Persistence (Advanced)
- Build your first harness
- Host options
- Send and show media
- Connect clients
- Write a client in any language
- Access and tenants
- Run in the terminal
- Deploy a harness
- Send inputs safely
- Run a tool without a model
- Durable sessions
- Run background work on workers
- List, rename, and fork sessions
- Lifecycle and retention
- Run side effects once
- Wait for a person
- Build on the session log
- Control how a turn ends
- Store settings per thread
- Compact a long session
- Fork and reset a thread
- One log for a session tree
- Write a plugin
- Track usage and cost
- Ask before risky tool calls
- Give your agent coding tools
- Sandbox and extend the coding tools
- Run the coding tools for many threads
- Undo a turn
- Build a coding agent
- Delegate to coding agents
- Switch between agent profiles
- Auth and connectors
- Share a thread between people
- Connect model providers
- Build your own UI
- Code mode in a harness
- Work until a goal is met
- Run agents from a harness
- Hand off a conversation
- Add skills
- Use MCP servers
- Give your agent a computer
- Use from any MCP client
- Use a harness from an editor (ACP)
- Overview
- Quick Start
- Providers
- Harnesses
- Harness Auth
- Workspace
- Tools
- Policy
- Lifecycle & Snapshots
- Portable Snapshots
- Keep Files After Reload
- Save a Named Version
- Branch From a Version
- Send a Frozen File
- Let the Agent Save and Fork
- Pick Which Files to Keep
- What a Snapshot Stores
- Instance Durability
- Durable Runs
- Events
Sandboxes (Advanced)
- Runtime Context
- Bring Your Own Key
- Keep Keys on the Server
- Debug Logging
- Multimodal Content
- Files API
- Per-Model Type Safety
- Runtime Adapter Switching
- Tree-Shaking
- Extend Adapter
- Extend Adapter: Advanced
- Typed Pre-Configured Options
- Test with a Fake Model
- Prompt Caching
- Mid-Conversation Changes
- Approval Flow Processing
Highlight markdown code
Pass a TanStack Highlight highlighter to TextPart. Fenced code then renders with token colors.
TextPart is in the React, Vue, and Solid UI packages. If your app still passes remarkPlugins or rehypePlugins, start with TextPart markdown.
Quick setup#
These steps use React. For Vue and Solid, steps 1 to 3 are the same. Step 4 is different. See Vue and Solid.
1. Install TanStack Highlight#
npm i @tanstack/highlightpnpm add @tanstack/highlightyarn add @tanstack/highlightbun add @tanstack/highlightUse @tanstack/highlight 0.0.6 or newer. The markdown adapter is in that package.
2. Create the highlighter#
Create src/markdown-highlighter.ts. Register only the languages that your models write:
import { createHighlighter } from '@tanstack/highlight/core'
import { js } from '@tanstack/highlight/languages/js'
import { json } from '@tanstack/highlight/languages/json'
import { plaintext } from '@tanstack/highlight/languages/plaintext'
import { ts } from '@tanstack/highlight/languages/ts'
import { tsx } from '@tanstack/highlight/languages/tsx'
import { createTanStackMarkdownHighlighter } from '@tanstack/highlight/markdown'
const highlighter = createHighlighter({
languages: [plaintext, js, json, ts, tsx],
})
export const highlightMarkdownCode =
createTanStackMarkdownHighlighter(highlighter)A fence with an unknown language renders as escaped plain text.
Warning
Use createTanStackMarkdownHighlighter. TextPart inserts the highlighter output as trusted HTML. A highlighter that returns unescaped source text lets model output inject HTML.
3. Create the theme CSS#
Create src/markdown-highlight-theme.ts. Point the selectors at a .markdown-renderer wrapper:
import { createThemeCss } from '@tanstack/highlight/theme'
import { githubDarkTheme } from '@tanstack/highlight/themes/github-dark'
import { githubLightTheme } from '@tanstack/highlight/themes/github-light'
export const markdownHighlightCss = createThemeCss({
light: githubLightTheme,
dark: githubDarkTheme,
lightSelector: '.markdown-renderer',
darkSelector: '.dark .markdown-renderer',
codeBlockSelector: '.markdown-renderer pre.tm-code',
lineNumbersSelector: '.markdown-renderer .tm-code--line-numbers',
})4. Use it in the chat#
Register TextPart as partsComponents.text. Pass the highlighter, and put markdown-renderer on the wrapper so that the theme CSS matches. Inject the CSS once in layout:
import { fetchServerSentEvents } from '@tanstack/ai-react'
import { createChatHook, TextPart } from '@tanstack/ai-react/ui'
import { highlightMarkdownCode } from './markdown-highlighter'
import { markdownHighlightCss } from './markdown-highlight-theme'
const chatOptions = {
connection: fetchServerSentEvents('/api/chat'),
}
const { useAppChat, useChatContext } = createChatHook({
options: chatOptions,
components: {
input: () => {
const chat = useChatContext()
return (
<form
onSubmit={(event) => {
event.preventDefault()
const field = event.currentTarget.elements.namedItem('message')
if (!(field instanceof HTMLInputElement)) return
const text = field.value.trim()
if (!text) return
field.value = ''
void chat.sendMessage(text)
}}
>
<input name="message" />
<button type="submit">Send</button>
</form>
)
},
layout: ({ Messages, Input }) => (
<main>
<style>{markdownHighlightCss}</style>
<Messages />
<Input />
</main>
),
message: ({ message, Parts }) => (
<article data-role={message.role}>
<Parts />
</article>
),
},
partsComponents: {
text: ({ part }) => (
<TextPart
content={part.content}
highlighter={highlightMarkdownCode}
className="markdown-renderer"
/>
),
fallback: () => null,
},
})
export function ChatScreen() {
const chat = useAppChat({ threadId: 'support-chat' })
return <chat.AppChat />
}Send a prompt that returns a fenced ts block. The fence now shows token colors.
How it works#
- createHighlighter tokenizes code for the languages that you register.
- createTanStackMarkdownHighlighter turns it into the CodeHighlighter that TanStack Markdown calls for each fence. It escapes the source text.
- createThemeCss writes CSS for the token classes, one theme for light mode and one for dark mode.
Vue and Solid#
Use the same highlighter file and the same theme file. Inject markdownHighlightCss once, at the app root. Then pass the highlighter to TextPart from your UI package.
Vue:
import { defineComponent, h } from 'vue'
import { TextPart } from '@tanstack/ai-vue/ui'
import { highlightMarkdownCode } from './markdown-highlighter'
export default defineComponent({
props: { content: { type: String, required: true } },
setup(props) {
return () =>
h(TextPart, {
content: props.content,
highlighter: highlightMarkdownCode,
class: 'markdown-renderer',
})
},
})Solid:
import { TextPart } from '@tanstack/ai-solid/ui'
import { highlightMarkdownCode } from './markdown-highlighter'
export function Reply(props: { content: string }) {
return (
<TextPart
content={props.content}
highlighter={highlightMarkdownCode}
class="markdown-renderer"
/>
)
}You now have colored code in model replies.
Advanced#
- More languages: import them from @tanstack/highlight/languages/* and add them to createHighlighter.
- Fence metadata and line numbers: read the TanStack Markdown highlighting guide.
- Tag swaps in React (a, img): pass components to TextPart. That map uses normal HTML props.
- Tag swaps in Vue and Solid: these TextPart components have no components map. Use a TanStack Markdown extension.