feat: add xAI Grok adapters

This commit is contained in:
2026-05-11 17:16:48 +09:00
parent 3ee6b233ea
commit 5be64756ac
5 changed files with 428 additions and 3 deletions

147
tests/grok.test.ts Normal file
View File

@@ -0,0 +1,147 @@
import { describe, expect, it } from 'vitest';
import {
createGrokAdapters,
createGrokImageModelAdapter,
createGrokStructuredModelAdapter,
createGrokTextModelAdapter,
} from '../src';
describe('Grok adapters', () => {
it('creates a text adapter that calls xAI chat completions', async () => {
const calls: Array<{ url: string; init?: RequestInit }> = [];
const adapter = createGrokTextModelAdapter({
apiKey: 'test-key',
model: 'grok-4.3-mini',
fetch: async (url, init) => {
calls.push({ url: String(url), ...(init ? { init } : {}) });
return new Response(JSON.stringify({
choices: [
{
message: {
content: 'hello from grok',
},
},
],
}), {
status: 200,
headers: { 'content-type': 'application/json' },
});
},
});
await expect(adapter.generateText({
system: 'You are a helpful persona engine.',
prompt: 'Say hi.',
temperature: 0.3,
})).resolves.toBe('hello from grok');
expect(calls).toHaveLength(1);
expect(calls[0]?.url).toBe('https://api.x.ai/v1/chat/completions');
expect(calls[0]?.init?.method).toBe('POST');
expect(calls[0]?.init?.headers).toMatchObject({
authorization: 'Bearer test-key',
'content-type': 'application/json',
});
const body = JSON.parse(String(calls[0]?.init?.body));
expect(body.model).toBe('grok-4.3-mini');
expect(body.temperature).toBe(0.3);
expect(body.messages).toEqual([
{ role: 'system', content: 'You are a helpful persona engine.' },
{ role: 'user', content: 'Say hi.' },
]);
});
it('creates a structured adapter that sends json_schema response_format and parses JSON', async () => {
const calls: Array<{ url: string; init?: RequestInit }> = [];
const adapter = createGrokStructuredModelAdapter({
apiKey: 'test-key',
model: 'grok-4.3',
fetch: async (url, init) => {
calls.push({ url: String(url), ...(init ? { init } : {}) });
return new Response(JSON.stringify({
choices: [
{
message: {
content: JSON.stringify({ biography: 'Born in Busan.' }),
},
},
],
}), {
status: 200,
headers: { 'content-type': 'application/json' },
});
},
});
const schema = {
type: 'object',
required: ['biography'],
properties: { biography: { type: 'string' } },
};
await expect(adapter.generateObject<{ biography: string }>({
prompt: 'Write a biography.',
system: 'Return only JSON.',
schema,
})).resolves.toEqual({ biography: 'Born in Busan.' });
const body = JSON.parse(String(calls[0]?.init?.body));
expect(body.response_format).toEqual({
type: 'json_schema',
json_schema: {
name: 'boxbrain_structured_output',
schema,
},
});
});
it('creates an image adapter that calls xAI image generation with mapped aspect ratios', async () => {
const calls: Array<{ url: string; init?: RequestInit }> = [];
const adapter = createGrokImageModelAdapter({
apiKey: 'test-key',
model: 'grok-imagine-image-quality',
fetch: async (url, init) => {
calls.push({ url: String(url), ...(init ? { init } : {}) });
return new Response(JSON.stringify({
data: [{ url: 'https://cdn.x.ai/generated.png', revised_prompt: 'revised prompt' }],
}), {
status: 200,
headers: { 'content-type': 'application/json' },
});
},
});
await expect(adapter.generateImage({
prompt: 'A quiet portrait photo.',
aspectRatio: 'portrait',
})).resolves.toEqual({
url: 'https://cdn.x.ai/generated.png',
revisedPrompt: 'revised prompt',
});
expect(calls[0]?.url).toBe('https://api.x.ai/v1/images/generations');
const body = JSON.parse(String(calls[0]?.init?.body));
expect(body.aspect_ratio).toBe('3:4');
expect(body.model).toBe('grok-imagine-image-quality');
});
it('can create a bundled Grok adapter set with shared defaults', async () => {
const adapters = createGrokAdapters({
apiKey: 'bundle-key',
textModel: 'grok-4.3-mini',
structuredModel: 'grok-4.3',
imageModel: 'grok-imagine-image-quality',
fetch: async (_url, _init) => new Response(JSON.stringify({
choices: [{ message: { content: 'ok' } }],
}), {
status: 200,
headers: { 'content-type': 'application/json' },
}),
});
expect(adapters.text.provider).toBe('xai-grok');
expect(adapters.structured.model).toBe('grok-4.3');
expect(adapters.image.model).toBe('grok-imagine-image-quality');
});
});