Atlas / Skills / tanstack / Tool Calling

Tool CallingSAFE

skills/tanstack/tool-calling

🤖 Type-safe, provider-agnostic TypeScript AI SDK for streaming chat, tool calling, agents, and multimodal apps across OpenAI, Anthropic, Gemini, React, Vue, Svelte, and Solid.

Verdict
SAFE
Grade
B
Trust score
89 /100
Version
—
Hosts
—
License
MIT
Stars
3,172
01

Overview

🤖 Type-safe, provider-agnostic TypeScript AI SDK for streaming chat, tool calling, agents, and multimodal apps across OpenAI, Anthropic, Gemini, React, Vue, Svelte, and Solid.

Read from source at commit dafe8ab3bd88OBSERVED · 2026-10-08
02

What it tells the agent

The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.

---
name: ai-core/tool-calling
description: >
  Isomorphic tool system: toolDefinition() with Zod schemas,
  .server() and .client() implementations, passing tools to both
  chat() on server and useChat/clientTools on client, tool approval
  flows with needsApproval and bound interrupts (resolveInterrupt), generic
  middleware interrupts with defineInterrupt(), lazy tool
  discovery with lazy:true, rendering ToolCallPart and ToolResultPart
  in UI.
type: sub-skill
library: tanstack-ai
library_version: '0.42.0'
sources:
  - 'TanStack/ai:docs/tools/tools.md'
  - 'TanStack/ai:docs/tools/server-tools.md'
  - 'TanStack/ai:docs/tools/client-tools.md'
  - 'TanStack/ai:docs/tools/tool-approval.md'
  - 'TanStack/ai:docs/tools/lazy-tool-discovery.md'
---

# Tool Calling

This skill builds on ai-core. Read it first for critical rules.

## Setup

Complete end-to-end example: shared definition, server tool, client tool, server route, React client.
The four files below share one scope, so later files use the earlier exports directly.

```typescript group=product-catalog
// tools/definitions.ts
import { toolDefinition } from '@tanstack/ai'
import { z } from 'zod'

export const getProductsDef = toolDefinition({
  name: 'get_products',
  description: 'Search for products in the catalog',
  inputSchema: z.object({
    query: z.string().meta({ description: 'Search keyword' }),
    limit: z.number().optional().meta({ description: 'Max results' }),
  }),
  outputSchema: z.object({
    products: z.array(
      z.object({ id: z.string(), name: z.string(), price: z.number() }),
    ),
  }),
})

export const updateCartUIDef = toolDefinition({
  name: 'update_cart_ui',
  description: 'Update the shopping cart UI with item count',
  inputSchema: z.object({ itemCount: z.number(), message: z.string() }),
  outputSchema: z.object({ displayed: z.boolean() }),
})
```

```typescript group=product-catalog
// tools/server.ts (uses getProductsDef from tools/definitions.ts)
import { db } from './db'

export const getProducts = getProductsDef.server(async ({ query, limit }) => {
  const results: Array<{ id: string; name: string; price: number }> =
    await db.products.search(query, { limit: limit ?? 10 })
  return {
    products: results.map((p) => ({ id: p.id, name: p.name, price: p.price })),
  }
})
```

```typescript group=product-catalog
// api/chat/route.ts (uses getProducts and updateCartUIDef from tools/)
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

export async function POST(request: Request) {
  const { messages } = await request.json()
  const stream = chat({
    adapter: openaiText('gpt-5.5'),
    messages,
    tools: [getProducts, updateCartUIDef], // server tool + client definition
  })
  return toServerSentEventsResponse(stream)
}
```

```tsx group=product-catalog
// app/chat.tsx (uses updateCartUIDef from tools/definitions.ts)
import {
  useChat,
  fetchServerSentEvents,
  createChatClientOptions,
  type InferChatMessages,
} from '@tanstack/ai-react'
import { clientTools } from '@tanstack/ai-client'
import { useState } from 'react'

function ChatPage() {
  const [cartCount, setCartCount] = useState(0)

  const updateCartUI = updateCartUIDef.client((input) => {
    setCartCount(input.itemCount)
    return { displayed: true }
  })

  const tools = clientTools(updateCartUI)
  const chatOptions = createChatClientOptions({
    connection: fetchServerSentEvents('/api/chat'),
    tools,
  })
  const { messages, sendMessage } = useChat(chatOptions)
  // InferChatMessages ties part types to the configured tools when needed:
  // type Messages = InferChatMessages<typeof chatOptions>

  return (
    <div>
      <span>Cart: {cartCount}</span>
      {messages.map((msg) => (
        <div key={msg.id}>
          {msg.parts.map((part) => {
            if (part.type === 'text') return <p>{part.content}</p>
            if (part.type === 'tool-call') {
              return (
                <div key={part.id}>
                  Tool: {part.name} ({part.state})
                </div>
              )
            }
            return null
          })}
        </div>
      ))}
    </div>
  )
}
```

## Core Patterns

### Generic middleware interrupts

Use `defineInterrupt()` when middleware needs typed data from the client. This
does not replace `needsApproval`. Tool approval asks whether a tool can run.
Generic interrupts ask for application data at a chat lifecycle boundary.

Define the interrupt once. Register it with both `chat({ interrupts })` and
`useChat({ interrupts })`. Emit it only from `onInterruptBoundary`, then read
the typed result in `onInterruptResolution`.

```typescript
import { defineInterrupt, type ChatMiddleware } from '@tanstack/ai'
import { z } from 'zod'

const reviewPlan = defineInterrupt({
  id: 'review-plan',
  payloadSchema: z.object({ title: z.string() }),
  responseSchema: z.object({ approved: z.boolean() }),
})

const reviewMiddleware: ChatMiddleware<unknown, typeof reviewPlan> = {
  onInterruptBoundary(ctx) {
    if (ctx.phase !== 'beforeTools') return
    return {
      interrupts: [
        reviewPlan.interrupt({
          key: 'release-plan',
          reason: 'review-required',
          message: 'Approve this plan?',
          payload: { title: 'Release plan' },
        }),
      ],
    }
  },
  onInterruptResolution(_ctx, resumedInterrupts) {
    for (const result of resumedInterrupts.for(reviewPlan)) {
      if (result.status === 'resolved' && !result.response.approved) {
        return { toolResume: 'stop' }
      }
    }
  },
}
```

Several middleware can request generic interrupts at one boundary. They share
one AG-UI interrupt batch with tool approvals. A continuation starts only after
the client resolves or cancels every bound item. `stop` is more restrictive than
`cancel`, which is more restrictive than `continue`.

Do not emit raw AG-UI interrupt events from middleware. Use the boundary hook
so the engine creates one t
03

Trust audit

SAFEgrade B · trust 89/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.

LayerWhat it checksResult
L0Provenance & inventoryPASS
L1Static analysis of the codeNA
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfacePASS
L4Behavioural (sandbox)SKIPPED

What the source does

Filesystem
none-observed
Network
none-observed
Shell
none-observed
Dependencies
pinned
Secrets in source
none-found

Findings (0)

No findings outside the package's declared scope.

Gates applied: no_behavioural_pass.

Audited 2026-10-08 · audit v0.4.1 · source sha dafe8ab3bd88full audit observations/trust-audit/skill/tanstack__tool-calling.json · Report an issue / request a re-scan
04

Audit history

Every audit this skill has had.

DateSourceVerdictGradeScoreChange
2026-10-08dafe8ab3bd88SAFEB89first audit
05

Questions

What does the Tool Calling skill do?

🤖 Type-safe, provider-agnostic TypeScript AI SDK for streaming chat, tool calling, agents, and multimodal apps across OpenAI, Anthropic, Gemini, React, Vue, Svelte, and Solid.

Is Tool Calling safe to install?

The audit found nothing in the source that contradicts what it says it does, and graded it B (89/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean skill reads B.

What can Tool Calling access on my machine?

The audit observed no filesystem, network or shell use at all in its source.

How current is this page?

The grade is for one exact copy of the source (dafe8ab3bd88), read on 2026-10-08. The repository is watched, and a new audit runs when it changes — this is the first audit.

Advertisement