@tanstack/ai-skills
Version:
Portable Agent Skills (SKILL.md) as a first-class chat() middleware for TanStack AI.
116 lines (85 loc) • 5.48 kB
Markdown
<div align="center">
<picture>
<source
media="(prefers-color-scheme: dark)"
srcset="https://tanstack.com/api/readme/ai.png?theme=dark"
/>
<source
media="(prefers-color-scheme: light)"
srcset="https://tanstack.com/api/readme/ai.png"
/>
<img
src="https://tanstack.com/api/readme/ai.png"
alt="TanStack AI"
width="900"
/>
</picture>
</div>
<br />
# /ai-skills
Portable Agent Skills (SKILL.md) as a first-class `chat()` middleware for TanStack AI
`withSkills` renders a short catalog of the skills you offer into the system prompt and gives the model a `load_skill` tool. The model reads the catalog, picks a skill, and pulls the full instructions only when it needs them — so a large skill library does not tax every request. This is the _portable_ path: it runs on any tool-calling model, with no provider sandbox. For skills that run in a provider's own sandbox, see [Provider Skills](https://tanstack.com/ai/latest/docs/tools/provider-skills); the two do not mix in one call.
## Installation
```bash
npm install /ai-skills
# or
pnpm add /ai-skills
# or
yarn add /ai-skills
```
## Usage
Define a skill and pass it to `withSkills` in the `middleware` array. The middleware handles the catalog and the `load_skill` tool for you.
```typescript
import { chat, toServerSentEventsResponse } from '/ai'
import { anthropicText } from '/ai-anthropic'
import { inlineSkill, withSkills } from '/ai-skills'
const pptx = inlineSkill({
name: 'pptx-builder',
description: 'Build and edit PowerPoint decks with python-pptx.',
instructions: '# Building a deck\nUse python-pptx. Edit slides, then save.',
})
export async function POST(request: Request) {
const { messages } = await request.json()
const stream = chat({
adapter: anthropicText('claude-sonnet-4-5'),
messages,
middleware: [withSkills(pptx)],
})
return toServerSentEventsResponse(stream)
}
```
Pass an array to offer several; they are sorted by name and deduped. Loading the same skill twice in one conversation returns a short "already loaded" marker instead of repeating the body.
### Tuning the catalog
```typescript
withSkills(sources, {
// Cap the catalog so a big library doesn't tax every request.
// Default 4000 tokens; throws if exceeded unless you supply a reducer.
maxCatalogTokens: 4000,
// Require a human approval before load_skill runs. Default false.
requireApproval: true,
})
```
The catalog is rendered per model family — Anthropic models get the `<available_skills>` XML they are tuned for, everything else a plain markdown list — and you can override that with `renderCatalog` or an `instructionTemplate`.
## Where skills come from
Inline skills are the quickest start, but skills usually live somewhere else. Each source is on its own entry point:
| Source | Import | Use when |
| ----------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `inlineSkill(...)` | `/ai-skills` | Defining a skill in code |
| `skillDirectory(...)` | `/ai-skills/node` | Walking a folder of `SKILL.md` files — reads the filesystem, so server only |
| `staticSkills(catalog)` | `/ai-skills/static` | A build-time bundle, no filesystem at runtime (edge-safe). Pair with `skillsCatalogPlugin` from `/node` in your Vite config |
| Your own `SkillSource` | — | Skills in S3, a database, a registry |
`aggregate`, `dedupe`, `filter`, `cache`, and `combineSources` compose these.
### Writing your own source
Implement the `SkillSource` contract, then prove it with the conformance suite the package ships:
```typescript
import { runSkillSourceConformance } from '/ai-skills/testing'
import { s3Skills } from './s3-skills'
runSkillSourceConformance(() => s3Skills(makeTestBucket()), 's3')
```
It checks the things that break in production: a missing skill throws rather than returning empty, resources load while a path like `../../etc/passwd` is rejected, `revision()` is stable across identical content, and concurrent `list()` calls stay consistent. The optional parts of the contract are only checked when you implement them — the resource cases need both `listResources` and `readResource`, and the revision case needs `revision` — so a source that cannot represent them is skipped rather than failed. `vitest` is an optional peer dependency for this entry point.
## Documentation
- [Portable Agent Skills](https://tanstack.com/ai/latest/docs/skills/agent-skills): what the model sees, catalog options, reading a skill's bundled files
- [Skill sources](https://tanstack.com/ai/latest/docs/skills/skill-sources): folder, build-time bundle, your own store, and combining them
- [Writing a skill source](https://tanstack.com/ai/latest/docs/skills/writing-adapters): the `SkillSource` contract and the conformance suite
## License
MIT