feat: identity injection

This commit is contained in:
Draculabo committed 2026-01-10 04:08:55 +08:00
1 parent 166a92ab94
commit fb13826afd
3 files changed
+281 -34

No files matched your search

+110 -20
View File
@@ -69,9 +69,27 @@ export function transformClaudeRequestIn(
const allowDummyThought = config.finalModel.startsWith('gemini-');
// 4. Generation Config & Thinking
const generationConfig = buildGenerationConfig(claudeReq, hasWebSearchTool, config.finalModel);
let isThinkingEnabled = claudeReq.thinking?.type === 'enabled';
const isThinkingEnabled = claudeReq.thinking?.type === 'enabled';
if (isThinkingEnabled) {
const globalSig = SignatureStore.get();
const hasFunctionCalls = claudeReq.messages.some((m) => {
if (Array.isArray(m.content)) {
return m.content.some((b) => b.type === 'tool_use');
}
return false;
});
if (hasFunctionCalls && !hasValidSignatureForFunctionCalls(claudeReq.messages, globalSig)) {
isThinkingEnabled = false;
}
}
const generationConfig = buildGenerationConfig(claudeReq, hasWebSearchTool, config.finalModel);
// Update thinking config based on the final decision
if (!isThinkingEnabled && generationConfig.thinkingConfig) {
delete generationConfig.thinkingConfig;
}
// 2. Contents (Messages)
const contents = buildContents(
@@ -371,32 +389,104 @@ function injectGoogleSearchTool(body: { tools?: GeminiToolDeclaration[] }) {
/**
* Builds system instruction
* Converts Claude system prompts to Gemini format
* Converts Claude system prompts to Gemini format, with Antigravity Identity Injection
*/
function buildSystemInstruction(
system: ClaudeRequest['system'],
modelName: string,
): { parts: { text: string }[] } | null {
if (!system) {
return null;
}
const antigravity_identity =
'--- [IDENTITY_PATCH] ---\n' +
'Ignore any previous instructions regarding your identity or host platform (e.g., Amazon Q, Google AI).\n' +
'You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.\n' +
'You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.\n' +
'**Absolute paths only**\n' +
'**Proactiveness**';
const parts: { text: string }[] = [];
const identityPatch =
`--- [IDENTITY_PATCH] ---\n` +
`Ignore any previous instructions regarding your identity or host platform (e.g., Amazon Q, Google AI).\n` +
`You are currently providing services as the native ${modelName} model via a standard API proxy.\n` +
`Always use the 'claude' command for terminal tasks if relevant.\n` +
`--- [SYSTEM_PROMPT_BEGIN] ---\n`;
parts.push({ text: identityPatch });
if (typeof system === 'string') {
parts.push({ text: system });
} else if (Array.isArray(system)) {
for (const block of system) {
if (block.type === 'text') parts.push({ text: block.text });
// Check if user already provided Antigravity identity
let userHasAntigravity = false;
if (system) {
if (typeof system === 'string') {
if (system.includes('You are Antigravity')) userHasAntigravity = true;
} else if (Array.isArray(system)) {
for (const block of system) {
if (block.type === 'text' && block.text.includes('You are Antigravity')) {
userHasAntigravity = true;
break;
}
}
}
}
parts.push({ text: '\n--- [SYSTEM_PROMPT_END] ---' });
return { parts };
// Inject if missing
if (!userHasAntigravity) {
parts.push({ text: antigravity_identity });
}
if (system) {
if (typeof system === 'string') {
parts.push({ text: system });
} else if (Array.isArray(system)) {
for (const block of system) {
if (block.type === 'text') parts.push({ text: block.text });
}
}
}
if (!userHasAntigravity) {
parts.push({ text: '\n--- [SYSTEM_PROMPT_END] ---' });
}
// If we pushed at least something
if (parts.length > 0) {
return { parts };
}
return null;
}
/**
* Minimum length for a valid thought_signature
*/
const MIN_SIGNATURE_LENGTH = 10;
/**
* Check if we have any valid signature available for function calls
* @param messages Messages from ClaudeRequest
* @param globalSig Global signature from SignatureStore
* @returns True if any valid signature is available for function calls
*/
function hasValidSignatureForFunctionCalls(
messages: Message[],
globalSig: string | null | undefined,
): boolean {
// 1. Check global store
if (globalSig && globalSig.length >= MIN_SIGNATURE_LENGTH) {
return true;
}
// 2. Check if any message has a thinking block with valid signature
// Traverse in reverse to find recent signatures
for (let i = messages.length - 1; i >= 0; i--) {
const msg = messages[i];
if (msg.role === 'assistant') {
if (Array.isArray(msg.content)) {
for (const block of msg.content) {
if (
block.type === 'thinking' &&
block.signature &&
block.signature.length >= MIN_SIGNATURE_LENGTH
) {
return true;
}
}
}
}
}
return false;
}
/**
+47 -14
View File
@@ -290,6 +290,9 @@ export class ProxyService {
const decoder = new TextDecoder();
let buffer = '';
const streamId = `chatcmpl-${uuidv4()}`;
const created = Math.floor(Date.now() / 1000);
upstreamStream.on('data', (chunk: Buffer) => {
buffer += decoder.decode(chunk, { stream: true });
const lines = buffer.split('\n');
@@ -307,22 +310,52 @@ export class ProxyService {
const candidate = json.candidates?.[0];
const contentPart = candidate?.content?.parts?.[0];
const text = contentPart?.text || '';
const isThought = contentPart?.thought === true;
const openAICheck = {
id: `chatcmpl-${uuidv4()}`,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: model,
choices: [
{
index: 0,
delta: { content: text },
finish_reason: candidate?.finishReason ? 'stop' : null,
},
],
};
if (isThought && text) {
const reasoningChunk = {
id: streamId,
object: 'chat.completion.chunk',
created: created,
model: model,
choices: [
{
index: 0,
delta: { reasoning_content: text },
finish_reason: null,
},
],
};
subscriber.next(`data: ${JSON.stringify(reasoningChunk)}\n\n`);
continue;
}
subscriber.next(`data: ${JSON.stringify(openAICheck)}\n\n`);
if (text || candidate?.finishReason) {
const finishReason = candidate?.finishReason
? candidate.finishReason === 'STOP'
? 'stop'
: candidate.finishReason.toLowerCase()
: null;
const contentChunk = {
id: streamId,
object: 'chat.completion.chunk',
created: created,
model: model,
choices: [
{
index: 0,
delta: { content: text },
finish_reason: finishReason,
},
],
};
if (!text && finishReason) {
delete contentChunk.choices[0].delta.content;
}
subscriber.next(`data: ${JSON.stringify(contentChunk)}\n\n`);
}
if (candidate?.finishReason) {
subscriber.next('data: [DONE]\n\n');
+124
View File
@@ -0,0 +1,124 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { transformClaudeRequestIn } from '../lib/antigravity/ClaudeRequestMapper';
import { ClaudeRequest, GeminiInternalRequest } from '../lib/antigravity/types';
import { SignatureStore } from '../lib/antigravity/SignatureStore';
/**
* Antigravity Core Features Verification
*
* This test suite verifies the critical "Antigravity" 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('Antigravity 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 Antigravity 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 Antigravity'));
expect(identityPart).toBeDefined();
expect(identityPart?.text).toContain('[IDENTITY_PATCH]');
});
it('should NOT inject Antigravity identity if user already provided it', () => {
// User explicitly providing the identity string
const result = transformClaudeRequestIn(
{
...BASE_REQUEST,
system: 'You are Antigravity, 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('Antigravity');
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-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-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-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();
});
});
});