---
title: "TanStack AI"
description: "Run the GitHub tools inside TanStack AI's own chat loop, with approval through TanStack's approval UI and interrupts."
canonical_url: "https://github-tools.com/integrations/tanstack-ai"
---
# TanStack AI

> Run the GitHub tools inside TanStack AI's own chat loop, with approval through TanStack's approval UI and interrupts.

## Overview

`@github-tools/sdk/tanstack` builds the 97 GitHub tools as [TanStack AI](https://tanstack.com/ai) server tools, so TanStack's own loop runs them. It also exports a prebuilt agent, and an approval middleware for `'auto'`. Presets, [context](https://github-tools.com/context) and [commit identity](https://github-tools.com/commits) take the same options as the AI SDK entry point.

Pick it when your app runs on TanStack AI's `chat()`. This entry point is in beta.

## Install

<code-group>

```bash [pnpm]
pnpm add @github-tools/sdk @tanstack/ai@0.64 @tanstack/ai-vercel-gateway@0.3 ai zod
```

```bash [npm]
npm install @github-tools/sdk @tanstack/ai@0.64 @tanstack/ai-vercel-gateway@0.3 ai zod
```

```bash [yarn]
yarn add @github-tools/sdk @tanstack/ai@0.64 @tanstack/ai-vercel-gateway@0.3 ai zod
```

```bash [bun]
bun add @github-tools/sdk @tanstack/ai@0.64 @tanstack/ai-vercel-gateway@0.3 ai zod
```

</code-group>

Keep `GITHUB_TOKEN` and the Gateway credentials (`AI_GATEWAY_API_KEY` or Vercel OIDC) on the server. Then create the prebuilt agent:

```ts [agent.ts]
import { createGithubAgent } from '@github-tools/sdk/tanstack'
import { vercelGatewayText } from '@tanstack/ai-vercel-gateway'

export const agent = createGithubAgent({
  adapter: vercelGatewayText('anthropic/claude-opus-5'),
  preset: 'repo-explorer',
  context: { owner: 'vercel', repo: 'ai' },
})

const text = await agent.generate({ prompt: 'Summarize the open pull requests.' })
```

`generate()` returns the final text, and throws when the run stops for an approval. `instructions` replaces the preset prompt and `additionalInstructions` appends to it. Other `chat()` settings, such as `modelOptions`, `agentLoopStrategy` and `abortController`, pass through.

## Stream to a chat UI

`stream()` returns TanStack's event stream, which you pass to its transport helpers:

```ts [app/api/chat/route.ts]
import { chatParamsFromRequestBody, toServerSentEventsResponse } from '@tanstack/ai'
import { agent } from './agent'

export async function POST(request: Request) {
  const { messages, threadId, runId, parentRunId, resume } =
    await chatParamsFromRequestBody(await request.json())
  return toServerSentEventsResponse(agent.stream({ messages, threadId, runId, parentRunId, resume }))
}
```

The agent keeps no state between calls, so each request passes the history and TanStack's continuation fields.

## Add the tools to your own chat

`createGithubTools()` returns an array. Combine it with your own tools as `[...tools, yourTool]`:

```ts [chat.ts]
import { chat } from '@tanstack/ai'
import { vercelGatewayText } from '@tanstack/ai-vercel-gateway'
import { createGithubTools } from '@github-tools/sdk/tanstack'

const tools = createGithubTools({
  preset: ['repo-explorer', 'code-review'],
  context: { owner: 'vercel', repo: 'ai' },
})

const stream = chat({
  adapter: vercelGatewayText('anthropic/claude-opus-5'),
  tools,
  messages: [{ role: 'user', content: 'Review pull request #42.' }],
})
```

Each tool is also a named factory, such as `getRepository(token)` or `addIssueComment(token, { needsApproval: true })`. `overrides` changes a tool's `description` or `needsApproval`, but not its schema or execution.

## Approval

Write tools ask by default. With `true` and `false`, globally or per tool, approval uses [TanStack's native tool approval](https://tanstack.com/ai/latest/docs/tools/tool-approval) and its client UI.

`'auto'` goes through TanStack's interrupts instead, because the decision runs before a tool executes. The prebuilt agent installs the middleware for you. With `chat()`, install it and register the interrupt yourself:

```ts [chat.ts]
import { chat } from '@tanstack/ai'
import { vercelGatewayText } from '@tanstack/ai-vercel-gateway'
import { createGithubApprovalMiddleware, createGithubTools } from '@github-tools/sdk/tanstack'
import { githubToolApproval } from '@github-tools/sdk/tanstack/interrupts'

const tools = createGithubTools({ preset: 'issue-triage', requireApproval: 'auto' })

const stream = chat({
  adapter: vercelGatewayText('anthropic/claude-opus-5'),
  tools,
  middleware: [createGithubApprovalMiddleware({ tools })],
  interrupts: [githubToolApproval],
  messages: [{ role: 'user', content: 'Add the bug label to vercel/ai#42.' }],
})
```

### Answer an interrupt

When the decision model wants a person, the run ends on a `githubToolApproval` interrupt. Its payload holds `toolCallId`, `toolName` and the resolved `input`, so you can show exactly what will happen.

1. Register the same interrupt in your client's `interrupts`. Import it from `@github-tools/sdk/tanstack/interrupts`, which is safe to bundle for the browser.
2. Show the action, and answer with TanStack's `resolveInterrupt` and `{ approved: true }` or `{ approved: false }`.
3. Send the resume data back with the history, as in the [streaming route](#stream-to-a-chat-ui).

TanStack's [interrupt client flow](https://tanstack.com/ai/latest/docs/interrupts/generic) covers the UI side. A denied call returns a denial without running.

`guardrails` and `dryRun` work here too. A write that [`guardrails`](https://github-tools.com/approval#guardrails) block still asks for approval, then fails with the reason. Under [`dryRun`](https://github-tools.com/approval#shadow-mode), a write never reaches the approval middleware, so its result has no `approval` field with `'auto'`.

## Use the `'auto'` modes

`preset: 'auto'` lets the decision model pick the tools, and `requireApproval: 'auto'` lets it approve routine writes:

```ts [agent.ts]
const agent = createGithubAgent({
  adapter: vercelGatewayText('anthropic/claude-opus-5'),
  preset: 'auto',
  requireApproval: 'auto',
})
```

Both use TanStack's `decide()` with `vercelGatewayDecider('typesafe-ai/jev')`. Here `preset: 'auto'` routes each call's latest user message to presets, not to single tools.

`evaluation` takes `adapter` for another TanStack evaluator, plus `maxRisk` (default `1`), `minIntent` (`0.6`), `minPresetProbability` (`0.7`) and `maxPresets` (`2`).

## FAQ

<accordion>
<accordion-item label="Which versions do I need?">

`@tanstack/ai` 0.64.x and `@tanstack/ai-vercel-gateway` 0.3.x.

</accordion-item>

<accordion-item label="Do I need the Gateway adapter?">

Not if you bring another chat adapter and, for the `'auto'` modes, your own `evaluation.adapter`.

</accordion-item>

<accordion-item label="Why install ai and zod?">

`ai` and `zod` are the SDK's peer dependencies, so they stay installed even though TanStack runs the loop.

</accordion-item>

<accordion-item label="Do the 'auto' modes need ai 7?">

No, the `'auto'` modes use TanStack's `decide()`, not the AI SDK's.

</accordion-item>

<accordion-item label="What if I use 'auto' with chat() and no middleware?">

Without the middleware, the `'auto'` tools ask on every call.

</accordion-item>

<accordion-item label="Does one answer cover a retry?">

No, an answer covers one call with those exact arguments. A new call or changed input needs a new answer.

</accordion-item>

<accordion-item label="Which tools does a resumed call get with preset: 'auto'?">

A call that resumes an interrupt skips routing, and gets `repo-explorer` plus the tools its pending calls name.

</accordion-item>

<accordion-item label="What if the decision call fails?">

If the decision call fails, writes ask for approval and routing falls back to `repo-explorer`.

</accordion-item>
</accordion>


## Sitemap

See the full [sitemap](https://github-tools.com/sitemap.md) for all pages.
