@tanstack/ai-skills
Version:
Portable Agent Skills (SKILL.md) as a first-class chat() middleware for TanStack AI.
93 lines (84 loc) • 3.69 kB
text/typescript
/**
* `runSkillSourceConformance` — the real deliverable for third-party adapters.
* Since adapter code is frequently LLM-generated, this suite (not prose) is what
* makes a new `SkillSource` safe to ship.
*
* The factory must return a source seeded with this fixed fixture contract:
*
* - skill `alpha`: description non-empty; resource `references/note.md` whose
* contents are exactly `hello`; script `scripts/run.py` whose bytes decode
* to `print(1)` (only if the source supports scripts).
* - skill `beta`: description non-empty; no resources required.
*
* Sources that cannot represent a tier (resources/scripts) simply omit the
* corresponding methods — those cases are skipped, not failed.
*/
import { describe, expect, it } from 'vitest'
import type { SkillSource } from '../types'
const dec = (v: string | Uint8Array) =>
typeof v === 'string' ? v : new TextDecoder().decode(v)
export function runSkillSourceConformance(
factory: () => SkillSource | Promise<SkillSource>,
label = 'SkillSource',
): void {
describe(`conformance: ${label}`, () => {
it('lists skills with a name and description', async () => {
const source = await factory()
const skills = await source.list()
const names = skills.map((s) => s.name)
expect(names).toContain('alpha')
expect(names).toContain('beta')
for (const s of skills) {
expect(s.name).toBeTruthy()
expect(s.description).toBeTruthy()
}
})
it('loads a known skill body', async () => {
const source = await factory()
const body = await source.load('alpha')
expect(typeof body).toBe('string')
expect(body.length).toBeGreaterThan(0)
})
it('throws (not returns empty) for a missing skill name', async () => {
const source = await factory()
await expect(source.load('does-not-exist')).rejects.toThrow()
})
it('has a stable revision across identical content', async () => {
const source = await factory()
const rev = source.revision
if (!rev) return
const a = await rev()
const b = await rev()
expect(a).toBe(b)
const other = await factory()
if (other.revision) expect(await other.revision()).toBe(a)
})
it('serves concurrent list() consistently', async () => {
const source = await factory()
const [a, b] = await Promise.all([source.list(), source.list()])
expect(a.map((s) => s.name).sort()).toEqual(b.map((s) => s.name).sort())
})
it('reads a bundled resource and rejects path traversal', async () => {
const source = await factory()
const { listResources, readResource } = source
if (!listResources || !readResource) return
const resources = await listResources('alpha')
expect(resources).toContain('references/note.md')
const value = await readResource('alpha', 'references/note.md')
// trimEnd: a file-backed source keeps the fixture's trailing newline (formatters add one); the payload is what matters.
expect(dec(value).trimEnd()).toBe('hello')
await expect(readResource('alpha', '../../etc/passwd')).rejects.toThrow()
})
it('returns script bytes correctly', async () => {
const source = await factory()
if (!source.listScripts || !source.readScript) return
const scripts = await source.listScripts('alpha')
const ref = scripts.find((s) => s.path === 'scripts/run.py')
if (!ref) return
expect(ref.executable).toBe(false)
const bytes = await source.readScript('alpha', 'scripts/run.py')
expect(bytes).toBeInstanceOf(Uint8Array)
expect(dec(bytes)).toContain('print(1)')
})
})
}