---
title: "AI SDK"
description: "Give the GitHub tools to generateText, streamText or a reusable agent, and answer approvals from a chat UI."
canonical_url: "https://github-tools.com/integrations/ai-sdk"
---
# AI SDK

> Give the GitHub tools to generateText, streamText or a reusable agent, and answer approvals from a chat UI.

## Overview

`createGithubTools()` returns the GitHub tools as an AI SDK `ToolSet`, so they go wherever `tools` goes: `generateText`, `streamText`, or your own agent. `createGithubAgent()` wraps the same tools in a [`ToolLoopAgent`](https://ai-sdk.dev/docs/agents) with the preset's system prompt and the approval policy already set up.

Pick this entry point when you already call the AI SDK, or want the smallest setup. Vercel Workflow and Chat SDK build on it, so what you learn here carries over.

## Install

<code-group>

```bash [pnpm]
pnpm add @github-tools/sdk ai zod
```

```bash [npm]
npm install @github-tools/sdk ai zod
```

```bash [yarn]
yarn add @github-tools/sdk ai zod
```

```bash [bun]
bun add @github-tools/sdk ai zod
```

</code-group>

Set `GITHUB_TOKEN` as in [Installation](https://github-tools.com/start/installation). For a script, a CI job or a webhook, where one call produces one answer, pass the tools to `generateText`:

```ts [summarize.ts]
import { createGithubTools } from '@github-tools/sdk'
import { generateText, stepCountIs } from 'ai'

const { text } = await generateText({
  model: 'anthropic/claude-opus-5.5',
  tools: createGithubTools({ preset: 'repo-explorer' }),
  stopWhen: stepCountIs(10),
  prompt: 'Summarize the last five merged pull requests on vercel/ai.',
})
```

The model needs several steps to call tools and then answer. `generateText` stops after the first step unless you pass `stopWhen`.

## Stream to a chat UI

The same tools work in a route that streams to a chat UI:

```ts [app/api/chat/route.ts]
import { createGithubTools } from '@github-tools/sdk'
import { convertToModelMessages, stepCountIs, streamText, type UIMessage } from 'ai'

export async function POST(request: Request) {
  const { messages }: { messages: UIMessage[] } = await request.json()

  const result = streamText({
    model: 'anthropic/claude-opus-5.5',
    tools: createGithubTools({ preset: 'code-review' }),
    stopWhen: stepCountIs(20),
    messages: await convertToModelMessages(messages),
  })

  return result.toUIMessageStreamResponse()
}
```

## `createGithubAgent`

When the same configuration serves several routes or jobs, define an agent once and call `.generate()` or `.stream()`:

```ts [lib/reviewer.ts]
import { createGithubAgent } from '@github-tools/sdk'

export const reviewer = createGithubAgent({
  model: 'anthropic/claude-opus-5.5',
  preset: 'code-review',
  context: { owner: 'vercel', repo: 'ai' },
  additionalInstructions: 'Cite file paths and line numbers.',
})

const { text } = await reviewer.generate({ prompt: 'Review pull request #42.' })
```

Each preset comes with a system prompt. `additionalInstructions` appends to it, and `instructions` replaces it. Every other [`ToolLoopAgent`](https://ai-sdk.dev/docs/agents) setting, such as `stopWhen`, `providerOptions` or `prepareCall`, passes through. [API](https://github-tools.com/reference/api) lists every option.

Only the agent takes `preset: 'auto'`, because picking tools for each request needs its `prepareCall` hook. [Presets](https://github-tools.com/presets) explains the routing.

## Approval in a chat UI

Every write tool asks before it runs. In a chat, the request reaches the client as a tool part in the `approval-requested` state, the person clicks a button, and the answer goes back to the server. The server streams the agent:

```ts [app/api/chat/route.ts]
import { createGithubAgent } from '@github-tools/sdk'
import { createAgentUIStreamResponse } from 'ai'

const agent = createGithubAgent({
  model: 'anthropic/claude-opus-5.5',
  preset: 'issue-triage',
  experimental_toolApprovalSecret: process.env.TOOL_APPROVAL_SECRET,
})

export async function POST(request: Request) {
  const { messages } = await request.json()
  return createAgentUIStreamResponse({ agent, uiMessages: messages })
}
```

The client renders the request and sends the answer back:

```tsx [app/chat.tsx]
'use client'

import { useChat } from '@ai-sdk/react'
import { lastAssistantMessageIsCompleteWithApprovalResponses } from 'ai'

export function Chat() {
  const { messages, addToolApprovalResponse } = useChat({
    sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
  })
  const answer = (id: string, approved: boolean) => addToolApprovalResponse({ id, approved })

  return messages.flatMap(message => message.parts.map((part) => {
    if (part.type !== 'tool-addLabels' || part.state !== 'approval-requested') return null
    return (
      <div key={part.toolCallId}>
        <p>Add {part.input.labels.join(', ')} to #{part.input.issueNumber}?</p>
        <button onClick={() => answer(part.approval.id, true)}>Approve</button>
        <button onClick={() => answer(part.approval.id, false)}>Deny</button>
      </div>
    )
  }))
}
```

`sendAutomaticallyWhen` sends the conversation back once every pending request has an answer. The approved tool then runs on the server, and a denied one returns a denial the model can read.

The client sends the whole history back, approvals included, so sign them: the [approval secret](https://github-tools.com/approval#sign-approvals) rejects an `approved: true` the server didn't issue. [Approval](https://github-tools.com/approval) covers the policy itself.

### Show why the agent asks

With `requireApproval: 'auto'` on `ai` 7, a request carries the decision model's reason in `part.approval.requestReason`. Parts with `part.approval.isAutomatic` were decided without a person, so render them without buttons.

## FAQ

<accordion>
<accordion-item label="Which ai versions are supported?">

`ai` 6.0.97 and later, and `ai` 7. Everything on this page runs on both, except signing, which needs `ai` 7.0.36 or later, and `requireApproval: 'auto'` and `preset: 'auto'`, which need `ai` 7.0.105 or later.

</accordion-item>

<accordion-item label="What if ai is too old for 'auto'?">

Creating the tools or the agent throws `github_tools.EVALUATION_UNAVAILABLE`.

</accordion-item>

<accordion-item label="Do approval requests carry reasons on ai 6?">

No, approval requests carry reasons only on `ai` 7. `createGithubAgent` sets the AI SDK 7 call-level `toolApproval`, which carries the reasons. On `ai` 6, approval runs through each tool's `needsApproval`, without reasons.

</accordion-item>

<accordion-item label="Why does ai 7 mark needsApproval as deprecated?">

AI SDK 7 marks `needsApproval` deprecated in favor of `toolApproval` on `generateText` and `streamText`, but still honors it. `createGithubTools` always sets the tool-level `needsApproval`.

</accordion-item>
</accordion>


## Sitemap

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