UNPKG

@tanstack/ai-skills

Version:

Portable Agent Skills (SKILL.md) as a first-class chat() middleware for TanStack AI.

67 lines (62 loc) • 1.99 kB
/** * `inlineSkill` — an edge-safe {@link SkillSource} for a single skill defined * next to app code, in a DB row, or per-session/per-tenant. Resource values may * be thunks, evaluated at read time. */ import { stableHash } from '../util' import { validateSkill } from '../validate' import type { SkillMetadata, SkillSource } from '../types' export interface InlineSkillConfig { name: string description: string instructions: string resources?: Record<string, string | (() => string | Promise<string>)> compatibility?: string } export function inlineSkill(config: InlineSkillConfig): SkillSource { const metadata: SkillMetadata = { name: config.name, description: config.description, ...(config.compatibility && { compatibility: config.compatibility }), } const lint = validateSkill(metadata) if (!lint.ok) { throw new Error( `inlineSkill "${config.name}": ${lint.issues.map((i) => i.message).join('; ')}`, ) } const resourcePaths = Object.keys(config.resources ?? {}) const revision = stableHash( JSON.stringify({ metadata, instructions: config.instructions, resources: resourcePaths, }), ) const assertName = (name: string) => { if (name !== config.name) { throw new Error(`inlineSkill has no skill named "${name}"`) } } return { revision: () => Promise.resolve(revision), list: () => Promise.resolve([metadata]), // Async so an unknown-name throw surfaces as a rejected promise. load: async (name) => { assertName(name) return config.instructions }, listResources: async (name) => { assertName(name) return resourcePaths }, readResource: async (name, path) => { assertName(name) const value = config.resources?.[path] if (value === undefined) { throw new Error(`skill "${name}" has no resource "${path}"`) } return typeof value === 'function' ? await value() : value }, } }