Files
Steven 7e48713b4e feat: make Gemini 3.6 Flash a default pxpipe reader (#134)
Add native Google AI Studio transformation, measured token accounting, dashboard support, and a production 312×728 profile for Gemini 3.6 Flash.

Gemini uses 26% fewer vision tokens than Fable 5 per full page and matches or exceeds Fable across the production-profile quality suite.
2026-07-21 16:41:40 -04:00

73 lines
3.3 KiB
TypeScript

/**
* Unit tests for the Google AI Studio / Gemini API transformer.
*/
import { describe, expect, it } from 'vitest';
import {
transformGoogleGenerateContent,
parseGoogleModelFromPath,
} from '../src/core/google.js';
describe('parseGoogleModelFromPath', () => {
it('extracts model name from Google AI Studio URL path', () => {
expect(parseGoogleModelFromPath('/google-ai-studio/v1beta/models/gemini-3.6-flash:generateContent')).toBe('gemini-3.6-flash');
expect(parseGoogleModelFromPath('/google-ai-studio/v1/models/gemini-3.6-flash:streamGenerateContent')).toBe('gemini-3.6-flash');
expect(parseGoogleModelFromPath('/v1beta/models/gemini-3.6-flash:streamGenerateContent')).toBeNull();
expect(parseGoogleModelFromPath('/foo/google-ai-studio/v1beta/models/gemini-3.6-flash:generateContent')).toBeNull();
expect(parseGoogleModelFromPath('/google-ai-studio/v1beta/models/gemini-3.6-flash:countTokens')).toBeNull();
expect(parseGoogleModelFromPath('/v1/messages')).toBeNull();
});
});
describe('transformGoogleGenerateContent', () => {
it('compresses system instruction when above profitability threshold', async () => {
const sampleBody = {
systemInstruction: {
parts: [{ text: 'System instruction text for testing Google transformer. '.repeat(300) }],
},
contents: [{ role: 'user', parts: [{ text: 'User question' }] }],
};
const bodyBytes = new TextEncoder().encode(JSON.stringify(sampleBody));
const result = await transformGoogleGenerateContent(bodyBytes, 'gemini-3.6-flash', { compress: true });
expect(result.info.compressed).toBe(true);
expect(result.info.imageCount).toBeGreaterThan(0);
expect(result.info.imageTokens).toBe(1078 * result.info.imageCount);
expect(result.info.baselineImagedTokens).toBeGreaterThan(1078);
expect(result.info.nativeInjectedTokens).toBeGreaterThan(0);
const outReq = JSON.parse(new TextDecoder().decode(result.body));
expect(outReq.systemInstruction.parts[0].text).toContain('same authority and priority');
expect(outReq.contents[0].parts[0].inlineData).toBeDefined();
expect(outReq.contents[0].parts[0].inlineData.mimeType).toBe('image/png');
});
it('defers the profitability decision to the upstream countTokens probes', async () => {
const sampleBody = {
systemInstruction: {
parts: [{ text: 'Short system prompt.' }],
},
contents: [{ role: 'user', parts: [{ text: 'User question' }] }],
};
const bodyBytes = new TextEncoder().encode(JSON.stringify(sampleBody));
const result = await transformGoogleGenerateContent(bodyBytes, 'gemini-3.6-flash', { compress: true });
expect(result.info.compressed).toBe(true);
expect(result.info.gateEval?.profitable).toBe(true);
});
it.each([
'null',
'[]',
'42',
JSON.stringify({ systemInstruction: { parts: 'bad' } }),
JSON.stringify({ systemInstruction: { parts: [null] } }),
JSON.stringify({ systemInstruction: { parts: [{ text: 'x'.repeat(10000) }] }, contents: 'bad' }),
])('passes unsupported request shape through unchanged: %s', async (raw) => {
const bytes = new TextEncoder().encode(raw);
const result = await transformGoogleGenerateContent(bytes, 'gemini-3.6-flash', { compress: true });
expect(new TextDecoder().decode(result.body)).toBe(raw);
expect(result.info.compressed).toBe(false);
});
});