Skip to content
Computer Use

Computer Use Safety Checks

A safety check runs before a computer action. It blocks an action the user did not ask for, or an action that sends data.

OpenAI attaches safety checks to these actions. TanStack AI pauses the run, and a person approves or declines the action. This page adds the approval card to your computer use app.

1. Add the server route

Give tools, middleware, and interrupt to chat(). Also give parentRunId and resume, so that the approval can continue the run.

ts
// app/api/chat/route.ts
import { chat, chatParamsFromRequest, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { createComputerUse } from '@tanstack/ai-computer-use'
import { playwrightComputer } from '@tanstack/ai-computer-playwright'

const computerUse = createComputerUse({
  driver: playwrightComputer({ startUrl: 'https://tanstack.com' }),
  // A person has up to one hour to answer before the page closes.
  idleTimeout: 60 * 60_000,
})

export async function POST(request: Request) {
  const params = await chatParamsFromRequest(request)
  const stream = chat({
    adapter: openaiText('gpt-6.1-sol'),
    messages: params.messages,
    threadId: params.threadId,
    runId: params.runId,
    ...(params.parentRunId ? { parentRunId: params.parentRunId } : {}),
    ...(params.resume ? { resume: params.resume } : {}),
    tools: computerUse.tools,
    middleware: [computerUse.middleware],
    interrupts: [computerUse.interrupt],
  })
  return toServerSentEventsResponse(stream)
}

2. Show the approval card

Register computerSafetyCheck with useChat. Then show a card for each paused action. Each interrupt has these fields:

  • payload.checks: the safety checks. Each check has an id, and can have a code and a message.
  • payload.action: the action that waits. For a batch, this is the list of actions.
tsx
// app/chat.tsx
import { fetchServerSentEvents, useChat } from '@tanstack/ai-react'
import type { GenericInterrupt } from '@tanstack/ai-react'
import { computerSafetyCheck } from '@tanstack/ai-computer-use'

function SafetyCheckCard({
  interrupt,
}: {
  interrupt: GenericInterrupt<typeof computerSafetyCheck>
}) {
  const checks = interrupt.payload?.checks ?? []
  return (
    <article>
      <h2>Approve this computer action?</h2>
      <ul>
        {checks.map((check) => (
          <li key={check.id}>{check.message ?? check.code ?? check.id}</li>
        ))}
      </ul>
      <pre>{JSON.stringify(interrupt.payload?.action, null, 2)}</pre>
      <button onClick={() => interrupt.resolveInterrupt({ approved: true })}>
        Approve
      </button>
      <button onClick={() => interrupt.resolveInterrupt({ approved: false })}>
        Decline
      </button>
    </article>
  )
}

export function Chat() {
  const { interrupts } = useChat({
    connection: fetchServerSentEvents('/api/chat'),
    threadId: 'support-chat',
    interrupts: [computerSafetyCheck],
  })

  return (
    <>
      {interrupts.map((interrupt) => {
        if (interrupt.kind !== 'generic') return null
        if (!('definitionId' in interrupt)) return null
        if (interrupt.definitionId !== computerSafetyCheck.id) return null
        return <SafetyCheckCard key={interrupt.id} interrupt={interrupt} />
      })}
    </>
  )
}

interrupt.cancel() also declines the action. When every interrupt of the batch has an answer, the client sends the answers. For more about generic interrupts, see Generic Interrupts.

3. Try it

Ask the model to do a task that sends data, for example a form submit. When OpenAI adds a safety check, the card shows.

  • Click Approve, and the action runs.
  • Click Decline, and the page stays as it was.

How it works

  1. The model sends a computer call with pending_safety_checks.
  2. The middleware pauses the run with one interrupt for each call that has checks.
  3. A person approves or declines on the client.

After the answer:

  • If the person approves all calls, the calls run. The checks go back to OpenAI as acknowledged.
  • If the person declines one call, no call of that turn runs.
  • A call with checks that nobody approved does not run. The tool refuses it.

Safety checks are an OpenAI feature. Anthropic models do not send them, so this code does nothing on Anthropic.

Advanced

Keep the approval until the resume request

computerUse keeps the approval for the thread until the resume request comes. To keep it there:

  • Create computerUse one time, at module scope. Every request uses the same one.
  • Give params.threadId to chat(). The resume request has the same thread id as the request that paused.
  • Set idleTimeout above the time that a person takes to answer. If the time ends first, the page closes, and the approved action runs on a new page.
  • Send the resume request to the same server process. The approvals live in the memory of that process.