ai-functions
Version:
Core AI primitives for building intelligent applications
158 lines (157 loc) • 6.07 kB
JavaScript
/**
* Tests for the ai proxy and AI() schema functions
*
* These tests use real AI calls via the Cloudflare AI Gateway.
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { ai, AI, functions, withTemplate } from '../src/index.js';
// Skip tests if no gateway configured
const hasGateway = !!process.env.AI_GATEWAY_URL || !!process.env.ANTHROPIC_API_KEY;
describe('ai proxy', () => {
beforeEach(() => {
functions.clear();
});
it('exposes functions registry', () => {
expect(ai.functions).toBeDefined();
expect(typeof ai.functions.list).toBe('function');
expect(typeof ai.functions.get).toBe('function');
expect(typeof ai.functions.set).toBe('function');
expect(typeof ai.functions.has).toBe('function');
expect(typeof ai.functions.clear).toBe('function');
expect(typeof ai.functions.delete).toBe('function');
});
it('exposes define helpers', () => {
expect(ai.define).toBeDefined();
expect(typeof ai.define).toBe('function');
expect(typeof ai.define.generative).toBe('function');
expect(typeof ai.define.agentic).toBe('function');
expect(typeof ai.define.human).toBe('function');
expect(typeof ai.define.code).toBe('function');
});
it('exposes defineFunction', () => {
expect(typeof ai.defineFunction).toBe('function');
});
});
describe.skipIf(!hasGateway)('ai proxy auto-define', () => {
beforeEach(() => {
functions.clear();
});
it('auto-defines a function on first call', async () => {
expect(functions.has('greetPerson')).toBe(false);
const result = await ai.greetPerson({
name: 'Alice',
style: 'friendly',
});
expect(result).toBeDefined();
expect(functions.has('greetPerson')).toBe(true);
});
it('uses cached definition on second call', async () => {
// First call - defines the function
await ai.capitalizeText({
text: 'hello',
});
const fn1 = functions.get('capitalizeText');
expect(fn1).toBeDefined();
// Second call - uses cached definition
await ai.capitalizeText({
text: 'world',
});
const fn2 = functions.get('capitalizeText');
expect(fn1).toBe(fn2); // Same cached function
});
});
describe.skipIf(!hasGateway)('AI() schema functions', () => {
it('creates schema-based functions', async () => {
const client = AI({
sentiment: {
sentiment: 'positive | negative | neutral',
score: 'Confidence score 0-1 (number)',
explanation: 'Brief explanation',
},
});
expect(client.sentiment).toBeDefined();
expect(typeof client.sentiment).toBe('function');
});
it('generates structured output from schema', async () => {
const client = AI({
person: {
name: 'Full name',
age: 'Age (number)',
occupation: 'Job title',
},
});
const result = await client.person('A software engineer named Alice who is 30');
expect(result).toBeDefined();
expect(typeof result.name).toBe('string');
expect(typeof result.age).toBe('number');
expect(typeof result.occupation).toBe('string');
});
it('generates nested objects', async () => {
const client = AI({
profile: {
user: {
name: 'Name',
email: 'Email address',
},
preferences: {
theme: 'light | dark',
notifications: 'Enabled? (boolean)',
},
},
});
const result = await client.profile('User Alice who prefers dark mode and has notifications on');
expect(result).toBeDefined();
expect(result.user).toBeDefined();
expect(result.preferences).toBeDefined();
expect(['light', 'dark']).toContain(result.preferences.theme);
expect(typeof result.preferences.notifications).toBe('boolean');
});
it('generates arrays', async () => {
const client = AI({
todoList: {
title: 'List title',
items: ['Todo items'],
priority: 'high | medium | low',
},
});
const result = await client.todoList('A high priority shopping list with 3 items');
expect(result).toBeDefined();
expect(typeof result.title).toBe('string');
expect(Array.isArray(result.items)).toBe(true);
expect(result.items.length).toBeGreaterThan(0);
expect(['high', 'medium', 'low']).toContain(result.priority);
});
});
describe('withTemplate helper', () => {
it('handles regular function calls', () => {
const fn = withTemplate((prompt) => prompt.toUpperCase());
const result = fn('hello world');
expect(result).toBe('HELLO WORLD');
});
it('handles tagged template literals', () => {
const fn = withTemplate((prompt) => prompt.toUpperCase());
const result = fn `hello world`;
expect(result).toBe('HELLO WORLD');
});
it('handles tagged template literals with interpolation', () => {
const fn = withTemplate((prompt) => prompt.toUpperCase());
const name = 'Alice';
const result = fn `hello ${name}!`;
expect(result).toBe('HELLO ALICE!');
});
it('handles multiple interpolations', () => {
const fn = withTemplate((prompt) => prompt);
const a = 'one';
const b = 'two';
const c = 'three';
const result = fn `${a}, ${b}, ${c}`;
expect(result).toBe('one, two, three');
});
it('works with async functions', async () => {
const fn = withTemplate(async (prompt) => {
return `Result: ${prompt}`;
});
const result = await fn `async test`;
expect(result).toBe('Result: async test');
});
});