Skip to content

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

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

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

Conversation

@jherr

@jherr jherr commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Add @tanstack/ai-skills, a new published package. It brings portable Agent Skills (SKILL.md) to chat() as middleware. Wrap a skill source with withSkills(...) and any tool-calling model can load skills on demand, on any provider, with no server sandbox.

🎯 Changes

The model gets an <available_skills> catalog and a load_skill tool. It loads a skill only when it needs one. Skills come from three sources:

  • inlineSkill — define a skill in code.
  • skillDirectory (/node subpath) — read a folder of SKILL.md files.
  • staticSkills (Vite plugin) — bundle skills at build time.

Sources compose with aggregate, dedupe, filter, and cache. createResourceTool exposes a skill's bundled files through a read_skill_resource tool. runSkillSourceConformance (/testing subpath) validates custom SkillSource adapters. The catalog renders as XML for Anthropic models and markdown for others.

Core @tanstack/ai now exports SkillLimitError. The native (hosted) skill factories throw it: codeExecutionTool (@tanstack/ai-anthropic) frames its 8-skill cap, and shellTool (@tanstack/openai-base) now validates skill_id format. Portable and native skills refuse to combine in one call. @tanstack/ai-sandbox reuses the shared skill-directory walk.

✅ 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. I did not run pnpm test:pr yet. This is a draft PR, opened right after a clean rebase onto main. Run the full gate before you mark it ready.

Manual test.

  1. Start the panel: cd testing/panel && pnpm dev.
  2. Open the /skills route.
  3. Send a message that needs a skill (for example, ask for a pirate-speak reply).
  4. Watch the inspector. It shows the model call load_skill, then answer with the loaded skill.

How this PR makes testing easy.

  • Unit tests: packages/ai-skills/tests/ (parse, walk, validate, load-skill).
  • E2E test: testing/e2e/tests/portable-skills-wire.spec.ts with its API route.
  • Example: the /skills demo route in testing/panel (UI, chat endpoint, inspector).

Risk / rollback

Low risk. The package is new and opt-in. Nothing loads skills unless you add withSkills(...) to a chat() call. The touch on core is one new export (SkillLimitError) plus validation in two native factories. 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

// No portable skills. You wire tools by hand, or use a hosted sandbox.
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 one export: SkillLimitError.

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 55b5322

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 3m 13s View ↗
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 21s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-25 02:04:22 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: 57f268b

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.

1 participant