mirror of
https://github.com/teamchong/pxpipe.git
synced 2026-07-22 02:02:51 +02:00
7e48713b4e
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.
73 lines
3.3 KiB
TypeScript
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);
|
|
});
|
|
});
|