Proofreader

Build a proofreader that reads a document on the server, generates corrections, and applies them on the server.

See the source code on GitHub.

Proofreader format

The Server AI Toolkit proofreader workflow currently uses format: 'shorthand'.

Reuse the same session across read and execute

The read step returns a sessionId. Send that same sessionId with the execute request so the server can reject stale edits when the document changed after the AI read it.

Before starting, set up authentication by following the authorization guide.

1. Read the document content from the AI Server

Start by reading the document in shorthand format. Call:

  • POST /v3/ai/toolkit/read/read-document
import { getAuthHeaders } from '@/lib/server-ai-toolkit/get-auth-headers'

const readResponse = await fetch(`${apiBaseUrl}/toolkit/read/read-document`, {
  method: 'POST',
  headers: getAuthHeaders(),
  body: JSON.stringify({
    schemaAwarenessData,
    sessionId,
    format: 'shorthand',
    reviewOptions: {
      mode: 'disabled',
    },
    experimental_documentOptions: {
      documentId,
      userId: 'ai-assistant',
    },
  }),
})

const readResult = await readResponse.json()

2. Generate proofreader operations

Get the workflow definition from the AI Server, combine it with the schema awareness prompt, and ask the model to return proofreader operations.

const workflowResponse = await fetch(`${apiBaseUrl}/toolkit/workflows/proofreader`, {
  method: 'POST',
  headers: getAuthHeaders(),
  body: JSON.stringify({
    format: 'shorthand',
  }),
})

const workflow = await workflowResponse.json()

const schemaResponse = await fetch(`${apiBaseUrl}/toolkit/schema-awareness-prompt`, {
  method: 'POST',
  headers: getAuthHeaders(),
  body: JSON.stringify({
    schemaAwarenessData,
  }),
})

const { prompt: schemaAwarenessPrompt } = await schemaResponse.json()

const result = streamText({
  model,
  system: `${workflow.systemPrompt}\n\n${schemaAwarenessPrompt}`,
  prompt: JSON.stringify({
    content: readResult.output.content,
    task: 'Correct all grammar and spelling mistakes',
  }),
  output: Output.object({ schema: z.fromJSONSchema(workflow.outputSchema) }),
})

const output = await result.output

3. Execute the workflow

Execute the workflow directly to apply the corrections to the document. Call:

  • POST /v3/ai/toolkit/execute-workflow/proofreader
const executeResponse = await fetch(`${apiBaseUrl}/toolkit/execute-workflow/proofreader`, {
  method: 'POST',
  headers: getAuthHeaders(),
  body: JSON.stringify({
    schemaAwarenessData,
    format: 'shorthand',
    input: output,
    sessionId: readResult.sessionId,
    reviewOptions: {
      mode: 'disabled',
    },
    experimental_documentOptions: {
      documentId,
      userId: 'ai-assistant',
    },
  }),
})

const executeResult = await executeResponse.json()

4. Trigger the workflow from the editor UI

The client sends the document ID and current session ID, then stores the updated session ID from the response.

const runProofreader = async () => {
  const response = await fetch('/api/server-proofreader-workflow', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      documentId,
      schemaAwarenessData: getSchemaAwarenessData(editor),
      sessionId,
    }),
  })

  const result: { sessionId: string } = await response.json()
  setSessionId(result.sessionId)
}

Tracked changes

Integrate this workflow with the Tracked Changes extension to show a review UI after the AI edits the document, and allow users to accept and reject changes.

In the request to POST /v3/ai/toolkit/execute-workflow/proofreader, configure the reviewOptions parameter:

reviewOptions: {
  mode: 'trackedChanges',
  trackedChangesOptions: {
    userId: 'ai-assistant',
  },
}

See the AI Toolkit demos for examples on how to integrate Server AI Toolkit workflows with Tracked Changes.

Tracked changes with comments

See the source code on GitHub.

You can make the AI provide a justification for each correction. Each justification becomes a comment thread linked to its tracked change, using the Comments extension.

Workflow definition with operationMeta

When fetching the proofreader workflow definition, pass operationMeta to require each operation to include a meta field. The string you provide describes what should go in the meta field.

const workflowResponse = await fetch(`${apiBaseUrl}/toolkit/workflows/proofreader`, {
  method: 'POST',
  headers: getAuthHeaders(),
  body: JSON.stringify({
    format: 'shorthand',
    operationMeta:
      'Specific reason for this correction (e.g., "spelling: excelent → excellent" or "subject-verb agreement").',
  }),
})

The returned outputSchema now requires a meta field on every operation, and the systemPrompt instructs the AI to fill it.

Execute the workflow with comments

Pass experimental_commentsOptions alongside reviewOptions when executing the workflow:

const executeResponse = await fetch(`${apiBaseUrl}/toolkit/execute-workflow/proofreader`, {
  method: 'POST',
  headers: getAuthHeaders(),
  body: JSON.stringify({
    schemaAwarenessData,
    format: 'shorthand',
    input: output,
    sessionId: readResult.sessionId,
    reviewOptions: {
      mode: 'trackedChanges',
      trackedChangesOptions: {
        userId: 'ai-assistant',
      },
    },
    experimental_commentsOptions: {
      threadData: { userName: 'Tiptap AI' },
      commentData: { userName: 'Tiptap AI' },
    },
    experimental_documentOptions: {
      documentId,
      userId: 'ai-assistant',
    },
  }),
})

Each non-empty meta field in the operations becomes a comment thread linked to its tracked change. The justification is stored as the thread's first comment content and as suggestionReason in the thread data.

Client-side setup

On the client, add the CommentsKit extension with a TiptapCollabProvider:

import { CommentsKit } from '@tiptap-pro/extension-comments'
import { TrackedChanges } from '@tiptap-pro/extension-tracked-changes'
import { ServerAiToolkit } from '@tiptap-pro/server-ai-toolkit'

const editor = useEditor({
  extensions: [
    StarterKit.configure({ undoRedo: false }),
    Collaboration.configure({ document: doc }),
    TrackedChanges.configure({ enabled: false }),
    ServerAiToolkit,
    CommentsKit.configure({
      provider, // Your TiptapCollabProvider instance
    }),
  ],
})

Users can review each tracked change and read the AI's justification in the comments sidebar.

End result

The result is a collaborative proofreader workflow for server-side grammar and spelling fixes:

See the source code on GitHub.

Next steps