import { describe, it, expect, vi } from 'vitest'; import { OpenAIClient } from './openai.js'; // Shared mock function so we can override per-test const mockCreate = vi.fn().mockResolvedValue({ choices: [{ message: { content: 'Hello from GPT!' }, finish_reason: 'stop' }], usage: { prompt_tokens: 10, completion_tokens: 5 }, }); vi.mock('openai', () => ({ default: vi.fn().mockImplementation(() => ({ chat: { completions: { create: mockCreate, }, }, })), })); describe('OpenAIClient', () => { it('sends messages and returns response', async () => { const client = new OpenAIClient({ apiKey: 'test-key', model: 'gpt-4o', }); const response = await client.chat({ messages: [{ role: 'user', content: 'Hello' }], }); expect(response.content).toBe('Hello from GPT!'); expect(response.stopReason).toBe('stop'); expect(response.usage.inputTokens).toBe(10); expect(response.usage.outputTokens).toBe(5); }); }); describe('OpenAIClient tool use', () => { it('passes tools to API and parses tool_calls response', async () => { mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: null, tool_calls: [{ id: 'call_1', type: 'function', function: { name: 'shell.exec', arguments: '{"command":"ls"}' }, }], }, finish_reason: 'tool_calls', }], usage: { prompt_tokens: 20, completion_tokens: 15 }, }); const client = new OpenAIClient({ apiKey: 'test-key', model: 'gpt-4o', }); const response = await client.chat({ messages: [{ role: 'user', content: 'list files' }], tools: [{ name: 'shell.exec', description: 'Run shell command', input_schema: { type: 'object', properties: { command: { type: 'string' } }, required: ['command'] }, }], }); expect(response.stopReason).toBe('tool_calls'); expect(response.toolCalls).toHaveLength(1); expect(response.toolCalls![0]).toEqual({ id: 'call_1', name: 'shell.exec', args: { command: 'ls' }, }); }); });