Skip to content

feat(ai-skills): add @tanstack/ai-skills — portable Agent Skills middleware - #1236

Draft
jherr wants to merge 7 commits into
mainfrom
skills-support
Draft

feat(ai-skills): add @tanstack/ai-skills — portable Agent Skills middleware#1236
jherr wants to merge 7 commits into
mainfrom
skills-support

Conversation

@jherr

@jherr jherr commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

withSkills(...) now gives any tool-calling model a skill catalog and a load_skill tool. The model loads a SKILL.md only when it needs it. No hosted sandbox.

TanStack AI DevTools now has a Skills tab. It shows the catalog from a skills:state stream chunk, and it marks a skill loaded when the model calls load_skill. Click the Skills useChat row and the detail pane opens. Before this PR that click did nothing, because the hook registered with an empty id.

This also fixes review bugs: skillDirectory can load on Windows and by frontmatter name, filter() now hides load/readResource, filesystem resources return utf8 for references/, and catalog overflow throws SkillLimitError.

Changes

  • New package @tanstack/ai-skills with withSkills, inlineSkill, skillDirectory (/node), staticSkills, and combinators.
  • createResourceTool exposes bundled files as read_skill_resource.
  • Portable and hosted (native) skills refuse to combine in one call.
  • Core exports SkillLimitError. Native factories throw it for hosted caps. Portable catalog overflow throws it too.
  • DevTools Skills tab: catalog + loaded badges. Hook identity stays live after mount, so a useChat with no threadId is selectable.

Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.
  • Docs: I updated docs/ for this change, or this change is not user-facing.
  • Changeset: I added a changeset (pnpm changeset), or this PR does not change a published package.

Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Testing

Commands run. Package tests passed:

  1. pnpm --filter @tanstack/ai-skills test:lib (61 tests)
  2. pnpm --filter @tanstack/ai-skills test:types
  3. pnpm --filter @tanstack/ai-client test:lib -- tests/devtools.test.ts (38 tests)
  4. pnpm --filter @tanstack/ai-devtools-core test:lib (50 tests)

I did not run pnpm test:pr.

Manual test.

  1. Start testing/panel (pnpm dev on a free port, for example 3020).
  2. Open /skills.
  3. Open TanStack DevTools.
  4. Click the Skills useChat row. The right pane must show the hook header and tabs (Conversation, Tools, State, Memory, Skills).
  5. Open the Skills tab. The catalog lists pirate-speak, haiku, emoji-storyteller.
  6. Send a message that needs a skill (ask for a pirate-speak reply).
  7. Watch load_skill in the conversation. The Skills tab marks that skill loaded.

How this PR makes testing easy.

  • Unit tests in packages/ai-skills/tests/ (parse, walk, combinators, withSkills, skillDirectory, load-skill).
  • DevTools: packages/ai-client/tests/devtools.test.ts (skills:state CUSTOM chunk, plus a useChat with no threadId must register a generated hookId) and packages/ai-devtools/tests/skills-registry.test.ts.
  • E2E: testing/e2e/tests/portable-skills-wire.spec.ts.
  • Example: /skills in testing/panel.

Risk / rollback

Low risk. The package is opt-in. Nothing loads skills unless you add withSkills(...). The DevTools tab is empty when no skills:state chunk arrives. To roll back, revert the PR.

Public API change

New export withSkills from @tanstack/ai-skills, plus skillDirectory from @tanstack/ai-skills/node. Caller usage:

Before

chat({ adapter, model, messages })

After

import { chat } from '@tanstack/ai'
import { withSkills } from '@tanstack/ai-skills'
import { skillDirectory } from '@tanstack/ai-skills/node'

const skills = skillDirectory('./skills')

chat({
  adapter,
  model,
  messages,
  middleware: [withSkills(skills)],
})

Core @tanstack/ai also adds SkillLimitError. Portable catalog overflow throws it with path: 'portable'. DevTools users need no new calls: the Skills tab appears when withSkills is on the server.

jherr and others added 2 commits August 24, 2026 16:58
Introduce `@tanstack/ai-skills`: portable `SKILL.md` skills as a first-class
`chat()` middleware. `withSkills(sources, options?)` renders a per-model-family
catalog and a `load_skill` tool so any tool-calling model loads skills on
demand, on any provider, with no server sandbox.

- Sources: `inlineSkill`, `skillDirectory` (/node), build-time `staticSkills`
  (/static) with a Vite plugin; combinators `aggregate`/`dedupe`/`filter`/`cache`.
- Tools: `load_skill` (enum-constrained names, activation dedupe, frozen result
  shape) and `createResourceTool` (`read_skill_resource`, path-traversal guard).
- `validateSkill` for author-time native-constraint linting; conformance suite
  at /testing (`runSkillSourceConformance`).
- Catalog renders `<available_skills>` XML for Anthropic, markdown elsewhere.
  Portable and hosted (native) skills refuse to combine in one call.

Core `@tanstack/ai` now exports `SkillLimitError`; `codeExecutionTool`
(ai-anthropic) frames its 8-skill cap with it, and `shellTool` (openai-base)
now validates `skill_id` format. `ai-sandbox` reuses the shared skill-directory
walk. Adds e2e wire coverage (portable catalog per family + co-existence
refusal), docs (docs/skills/*), a per-package SKILL.md, and a `/skills` demo in
testing/panel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rry code

- Route the new `ai-skills` skill from `ai-core` (sub-skills table + a
  companion-packages entry) and list `@tanstack/ai-skills` in the
  getting-started "Skills Shipped" table, so coding agents can find it.
- Add a "Skills that come with code" section to the portable-skills guide and
  the ai-skills SKILL.md: withSkills composes with your own tools, so pass an
  execution tool (e.g. execute_shell) for skills that reference scripts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nx-cloud

nx-cloud Bot commented Aug 25, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit fa120d1

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 53s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-27 13:17:03 UTC

@pkg-pr-new

pkg-pr-new Bot commented Aug 25, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai@1236

@tanstack/ai-acp

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-acp@1236

@tanstack/ai-angular

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-angular@1236

@tanstack/ai-anthropic

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-anthropic@1236

@tanstack/ai-bedrock

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-bedrock@1236

@tanstack/ai-byteplus

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-byteplus@1236

@tanstack/ai-claude-code

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-claude-code@1236

@tanstack/ai-client

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-client@1236

@tanstack/ai-code-mode

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode@1236

@tanstack/ai-code-mode-snippets

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode-snippets@1236

@tanstack/ai-codex

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-codex@1236

@tanstack/ai-cohere

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-cohere@1236

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-devtools-core@1236

@tanstack/ai-durable-stream

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-durable-stream@1236

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-elevenlabs@1236

@tanstack/ai-event-client

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-event-client@1236

@tanstack/ai-fal

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-fal@1236

@tanstack/ai-gemini

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-gemini@1236

@tanstack/ai-grok

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-grok@1236

@tanstack/ai-grok-build

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-grok-build@1236

@tanstack/ai-groq

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-groq@1236

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-cloudflare@1236

@tanstack/ai-isolate-daytona

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-daytona@1236

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-node@1236

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-quickjs@1236

@tanstack/ai-isolate-quickjs-bun

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-quickjs-bun@1236

@tanstack/ai-llmgateway

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-llmgateway@1236

@tanstack/ai-lovable

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-lovable@1236

@tanstack/ai-mcp

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-mcp@1236

@tanstack/ai-memory

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-memory@1236

@tanstack/ai-mistral

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-mistral@1236

@tanstack/ai-octane

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-octane@1236

@tanstack/ai-ollama

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-ollama@1236

@tanstack/ai-openai

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-openai@1236

@tanstack/ai-opencode

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-opencode@1236

@tanstack/ai-openrouter

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-openrouter@1236

@tanstack/ai-perplexity

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-perplexity@1236

@tanstack/ai-persistence

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-persistence@1236

@tanstack/ai-preact

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-preact@1236

@tanstack/ai-react

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react@1236

@tanstack/ai-react-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react-ui@1236

@tanstack/ai-sandbox

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox@1236

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-cloudflare@1236

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-daytona@1236

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-docker@1236

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-local-process@1236

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-sprites@1236

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-vercel@1236

@tanstack/ai-skills

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-skills@1236

@tanstack/ai-solid

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid@1236

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid-ui@1236

@tanstack/ai-svelte

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-svelte@1236

@tanstack/ai-utils

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-utils@1236

@tanstack/ai-vercel-gateway

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vercel-gateway@1236

@tanstack/ai-vertex

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vertex@1236

@tanstack/ai-vue

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue@1236

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue-ui@1236

@tanstack/openai-base

npm i https://pkg.pr.new/TanStack/ai/@tanstack/openai-base@1236

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/preact-ai-devtools@1236

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/react-ai-devtools@1236

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/solid-ai-devtools@1236

@tanstack/svelte-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/svelte-ai-devtools@1236

commit: fa120d1

jherr and others added 4 commits August 24, 2026 18:59
Formatters (autofix-ci) add a trailing newline to the fixture
references/note.md, so a file-backed SkillSource reads 'hello\n' while
the inline source returns 'hello'. The shared conformance assertion
compared byte-exact and failed only for skillDirectory on CI. trimEnd
the read value — the payload is what the contract cares about.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants