180 lines
6.5 KiB
TypeScript
180 lines
6.5 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { transformClaudeRequestIn } from '../lib/geminiNexus/ClaudeRequestMapper';
|
|
import { ClaudeRequest } from '../lib/geminiNexus/types';
|
|
import { SignatureStore } from '../lib/geminiNexus/SignatureStore';
|
|
|
|
/**
|
|
* Gemini Nexus Core Features Verification
|
|
*
|
|
* This test suite verifies the critical "Gemini Nexus" enhancements ported from the Manager project.
|
|
* Primarily focuses on:
|
|
* 1. Identity Injection (Safety & Branding)
|
|
* 2. Gemini 3 Pro Compatibility (Stability Fixes)
|
|
* 3. Model Routing Logic (Business Rules)
|
|
*/
|
|
describe('Gemini Nexus Core Features (Business Logic)', () => {
|
|
beforeEach(() => {
|
|
// Clear global signature store before each test
|
|
SignatureStore.clear();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
const BASE_REQUEST: ClaudeRequest = {
|
|
model: 'claude-3-5-sonnet-20241022',
|
|
messages: [{ role: 'user', content: 'Hello' }],
|
|
max_tokens: 100,
|
|
stream: true,
|
|
};
|
|
|
|
describe('Feature: Identity Injection (Branding & Safety)', () => {
|
|
it('should inject Gemini Nexus identity when system prompt is missing', () => {
|
|
const result = transformClaudeRequestIn({ ...BASE_REQUEST }, 'test-project');
|
|
|
|
const parts = result.request.systemInstruction?.parts || [];
|
|
const identityPart = parts.find((p) => p.text.includes('You are Gemini Nexus'));
|
|
|
|
expect(identityPart).toBeDefined();
|
|
expect(identityPart?.text).toContain('[IDENTITY_PATCH]');
|
|
});
|
|
|
|
it('should NOT inject Gemini Nexus identity if user already provided it', () => {
|
|
// User explicitly providing the identity string
|
|
const result = transformClaudeRequestIn(
|
|
{
|
|
...BASE_REQUEST,
|
|
system: 'You are Gemini Nexus, the best AI.',
|
|
},
|
|
'test-project',
|
|
);
|
|
|
|
const parts = result.request.systemInstruction?.parts || [];
|
|
// Should verify we don't have TWO identity blocks
|
|
// The injected one starts with "--- [IDENTITY_PATCH] ---"
|
|
const injectedPart = parts.find((p) => p.text.includes('[IDENTITY_PATCH]'));
|
|
|
|
expect(injectedPart).toBeUndefined();
|
|
});
|
|
|
|
it('should inject before user system prompt if user prompt does NOT have identity', () => {
|
|
const result = transformClaudeRequestIn(
|
|
{
|
|
...BASE_REQUEST,
|
|
system: 'Be concise.',
|
|
},
|
|
'test-project',
|
|
);
|
|
|
|
const parts = result.request.systemInstruction?.parts || [];
|
|
expect(parts.length).toBeGreaterThanOrEqual(2);
|
|
expect(parts[0].text).toContain('Gemini Nexus');
|
|
expect(parts[parts.length - 1].text).toContain('Be concise');
|
|
});
|
|
});
|
|
|
|
describe('Feature: Gemini 3 Pro Stability Fix ', () => {
|
|
// Situation: User enables thinking, calls a tool, but NO thought signature exists.
|
|
// Result: Google API would return 400.
|
|
// Fix: We must silently disable thinking for this request.
|
|
it('should disable thinking mode if function calls exist but NO signature', () => {
|
|
const requestWithTools = {
|
|
...BASE_REQUEST,
|
|
// Maps to a gemini-3 model potentially
|
|
model: 'gemini-3.1-pro-preview',
|
|
tools: [{ name: 'get_weather' }], // Mock tool structure
|
|
thinking: { type: 'enabled', budget_tokens: 1000 },
|
|
} as any;
|
|
|
|
const result = transformClaudeRequestIn(requestWithTools, 'test-project');
|
|
|
|
// Should detect missing signature and remove thinking config
|
|
expect(result.request.generationConfig?.thinkingConfig).toBeUndefined();
|
|
});
|
|
|
|
it('should KEEP thinking mode if function calls exist AND valid signature exists in Store', () => {
|
|
// Pre-fill a valid signature
|
|
SignatureStore.store('valid_signature_string_longer_than_10_chars');
|
|
|
|
const requestWithTools = {
|
|
...BASE_REQUEST,
|
|
model: 'gemini-3.1-pro-preview',
|
|
tools: [{ name: 'get_weather' }],
|
|
thinking: { type: 'enabled', budget_tokens: 1000 },
|
|
} as any;
|
|
|
|
const result = transformClaudeRequestIn(requestWithTools, 'test-project');
|
|
|
|
// Should keep thinking config
|
|
expect(result.request.generationConfig?.thinkingConfig).toBeDefined();
|
|
});
|
|
|
|
it('should KEEP thinking mode if NO function calls exist (pure thinking)', () => {
|
|
const requestPureThinking = {
|
|
...BASE_REQUEST,
|
|
model: 'gemini-3.1-pro-preview',
|
|
// No tools
|
|
thinking: { type: 'enabled', budget_tokens: 1000 },
|
|
} as any;
|
|
|
|
const result = transformClaudeRequestIn(requestPureThinking, 'test-project');
|
|
|
|
// Should keep thinking config
|
|
expect(result.request.generationConfig?.thinkingConfig).toBeDefined();
|
|
});
|
|
|
|
it('should auto-enable thinking for gemini-3-flash even without explicit thinking config', () => {
|
|
const result = transformClaudeRequestIn(
|
|
{
|
|
...BASE_REQUEST,
|
|
model: 'gemini-3-flash',
|
|
thinking: undefined,
|
|
} as any,
|
|
'test-project',
|
|
);
|
|
|
|
expect(result.request.generationConfig?.thinkingConfig).toBeDefined();
|
|
expect(result.request.generationConfig?.thinkingConfig?.includeThoughts).toBe(true);
|
|
});
|
|
|
|
it('should inject flash sentinel thoughtSignature for tool_use without cached signature', () => {
|
|
const requestWithToolUse = {
|
|
...BASE_REQUEST,
|
|
model: 'gemini-3-flash',
|
|
thinking: undefined,
|
|
messages: [
|
|
{
|
|
role: 'assistant',
|
|
content: [
|
|
{
|
|
type: 'tool_use',
|
|
id: 'call_1',
|
|
name: 'get_weather',
|
|
input: { location: 'Beijing' },
|
|
},
|
|
],
|
|
},
|
|
],
|
|
} as any;
|
|
|
|
const result = transformClaudeRequestIn(requestWithToolUse, 'test-project');
|
|
const part = result.request.contents?.[0]?.parts?.[0] as any;
|
|
expect(part?.thoughtSignature).toBe('skip_thought_signature_validator');
|
|
});
|
|
|
|
it('should keep mixed tools for gemini 2.x+ when both function and web search are requested', () => {
|
|
const mixedToolsRequest = {
|
|
...BASE_REQUEST,
|
|
model: 'gemini-2.0-flash-online',
|
|
tools: [{ name: 'get_weather' }, { name: 'web_search' }],
|
|
} as any;
|
|
|
|
const result = transformClaudeRequestIn(mixedToolsRequest, 'test-project');
|
|
const tools = result.request.tools || [];
|
|
const hasFunctions = tools.some((tool: any) => Array.isArray(tool.functionDeclarations));
|
|
const hasGoogleSearch = tools.some((tool: any) => tool.googleSearch);
|
|
|
|
expect(hasFunctions).toBe(true);
|
|
expect(hasGoogleSearch).toBe(true);
|
|
});
|
|
});
|
|
});
|