Test capability của bạn
@agentic-ui/testing cho phép bạn test một capability như một hàm thường — không LLM, không Vue, không transport. Mỗi call vẫn chạy đủ pipeline của registry (resolve → stale check → validate → permission → confirmation → execute), nên cái gì pass ở đây sẽ hành xử y hệt trong app.
pnpm add -D @agentic-ui/testingCác ví dụ dưới đây dùng vitest, nhưng không có gì phụ thuộc vitest.
1. Gọi capability như một hàm — callCapability
import { describe, expect, it } from 'vitest';
import { callCapability } from '@agentic-ui/testing';
import type { AgentCapability } from '@agentic-ui/core';
const greet: AgentCapability<{ name: string }> = {
name: 'demo.greet',
description: 'Greet a person by name.',
inputSchema: {
type: 'object',
properties: { name: { type: 'string', minLength: 1 } },
required: ['name'],
additionalProperties: false,
},
risk: 'read',
execute: ({ name }) => ({ message: `Hello, ${name}!` }),
};
it('greets', async () => {
const result = await callCapability(greet, { name: 'An' });
expect(result.status).toBe('ok');
expect(result.structuredContent).toEqual({ message: 'Hello, An!' });
});
it('rejects bad input BEFORE execute runs', async () => {
const result = await callCapability(greet, { name: '' });
expect(result.status).toBe('invalid'); // schema said minLength 1
});callCapability đăng ký definition vào một registry dùng một lần rồi trả về ToolResult. Nó không bao giờ throw cho các kết quả pipeline — hãy assert trên result.status (ok | invalid | denied | unconfirmed | unavailable | ambiguous | stale | error).
2. Test tool mutating — stub xác nhận
Một capability có risk: 'mutating' | 'destructive' bình thường sẽ chặn lại chờ con người duyệt. Trong test, stub câu trả lời:
// approve → the tool runs
const ok = await callCapability(saveCap, {}, { confirm: 'approve' });
expect(ok.status).toBe('ok');
// deny → the tool NEVER executes
const denied = await callCapability(saveCap, {}, { confirm: 'deny' });
expect(denied.status).toBe('unconfirmed');
// or script it — `confirm` accepts a full ConfirmationHook
await callCapability(saveCap, {}, {
confirm: async (req) => (req.digest === expected ? { approved: true } : { approved: false }),
});Có một stub permission tương ứng cho permission hook (permission: false → result.status === 'denied', hoặc truyền vào một PermissionHook đầy đủ).
3. Snapshot digest duyệt — capabilityDigest
Việc duyệt được ràng buộc với sha256(canonicalJson({ name, targetId, input })). Để assert rằng UI duyệt của bạn hiển thị đúng digest — hoặc rằng pipeline hỏi với đúng payload bạn mong đợi — hãy tính digest bằng chính thuật toán đó và quan sát request:
import { callCapability, capabilityDigest } from '@agentic-ui/testing';
import type { ConfirmationRequest } from '@agentic-ui/core';
it('asks approval for exactly this payload', async () => {
const seen: ConfirmationRequest[] = [];
await callCapability(saveCap, {}, {
confirm: 'approve',
onConfirmation: (req) => seen.push(req),
});
expect(seen[0].digest).toBe(await capabilityDigest(saveCap, {}));
});capabilityDigest(cap, input) dùng chính target.id của capability (truyền tham số thứ ba để override). Digest ổn định — canonical JSON sắp xếp key — nên lưu vào snapshot file là an toàn. computeDigest / canonicalJson / sha256hex được re-export cho các nhu cầu ở tầng thấp hơn.
4. Lint chính definition — expectCapabilityContract
Bắt các lỗi contract mà test happy-path bỏ sót — hãy chạy nó trên mọi capability bạn ship:
import { expectCapabilityContract } from '@agentic-ui/testing';
it('honours the capability contract', () => {
expectCapabilityContract(greet, {
examples: [{ name: 'An' }], // each example must PASS the inputSchema
});
});Nó throw một error liệt kê mọi vi phạm:
inputSchemaphải là schema mà validator thực sự enforce được: roottype: 'object'(các LLM tool API yêu cầu điều này), keywordtypehợp lệ, pattern compile được, operand của keyword đúng kiểu, không có tổ hợprequired+additionalProperties: falsebất khả thi.descriptionkhông rỗng và trông giống tiếng Anh — đây là tài liệu cho model.riskđược khai báo (read|mutating|destructive) — nó quyết định confirmation policy, đừng bao giờ để ngầm định.- mọi example input phải pass
inputSchema(example cũng có thể nằm ngay trên definition dưới dạng mảngexamples).
5. Kịch bản multi-instance / lifecycle — createTestRegistry
Cho bất cứ thứ gì vượt quá một call đơn (hai target, suspend/dispose, revision bị stale), hãy tự dựng một registry và điều khiển trực tiếp. callCapability nhận { registry } để dùng lại nó:
import { callCapability, createTestRegistry } from '@agentic-ui/testing';
it('routes to the addressed instance', async () => {
const registry = createTestRegistry({ clock: () => 42 }); // deterministic clock
registry.register({ ...cap, target: { id: 'page' } });
registry.register({ ...cap, target: { id: 'drawer' } });
const result = await callCapability(cap, input, { registry, target: 'drawer' });
expect(result.status).toBe('ok');
});Ví dụ thực tế
Playground test năm form capability của nó (form.describe / read_values / set_values / validate / save) bằng đúng các helper này — xem playground/test/form-capabilities.test.ts.