liuw15 commited on
Commit
31da410
·
1 Parent(s): 5778ed2

额外创建openai、claude、gemini的转化器

Browse files
src/utils/converters/claude.js ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Claude 格式转换工具
2
+ import config from '../../config/config.js';
3
+ import { generateRequestId } from '../idGenerator.js';
4
+ import { getReasoningSignature } from '../thoughtSignatureCache.js';
5
+ import { setToolNameMapping } from '../toolNameCache.js';
6
+ import { getThoughtSignatureForModel, sanitizeToolName, cleanParameters, modelMapping, isEnableThinking, generateGenerationConfig } from '../utils.js';
7
+
8
+ function extractImagesFromClaudeContent(content) {
9
+ const result = { text: '', images: [] };
10
+ if (typeof content === 'string') {
11
+ result.text = content;
12
+ return result;
13
+ }
14
+ if (Array.isArray(content)) {
15
+ for (const item of content) {
16
+ if (item.type === 'text') {
17
+ result.text += item.text || '';
18
+ } else if (item.type === 'image') {
19
+ const source = item.source;
20
+ if (source && source.type === 'base64' && source.data) {
21
+ result.images.push({
22
+ inlineData: {
23
+ mimeType: source.media_type || 'image/png',
24
+ data: source.data
25
+ }
26
+ });
27
+ }
28
+ }
29
+ }
30
+ }
31
+ return result;
32
+ }
33
+
34
+ function handleClaudeUserMessage(extracted, antigravityMessages) {
35
+ antigravityMessages.push({
36
+ role: 'user',
37
+ parts: [{ text: extracted.text }, ...extracted.images]
38
+ });
39
+ }
40
+
41
+ function handleClaudeAssistantMessage(message, antigravityMessages, enableThinking, actualModelName, sessionId) {
42
+ const lastMessage = antigravityMessages[antigravityMessages.length - 1];
43
+ const content = message.content;
44
+
45
+ let textContent = '';
46
+ const toolCalls = [];
47
+
48
+ if (typeof content === 'string') {
49
+ textContent = content;
50
+ } else if (Array.isArray(content)) {
51
+ for (const item of content) {
52
+ if (item.type === 'text') {
53
+ textContent += item.text || '';
54
+ } else if (item.type === 'tool_use') {
55
+ const originalName = item.name;
56
+ const safeName = sanitizeToolName(originalName);
57
+ const part = {
58
+ functionCall: {
59
+ id: item.id,
60
+ name: safeName,
61
+ args: { query: JSON.stringify(item.input || {}) }
62
+ }
63
+ };
64
+ if (sessionId && actualModelName && safeName !== originalName) {
65
+ setToolNameMapping(sessionId, actualModelName, safeName, originalName);
66
+ }
67
+ toolCalls.push(part);
68
+ }
69
+ }
70
+ }
71
+
72
+ const hasToolCalls = toolCalls.length > 0;
73
+ const hasContent = textContent && textContent.trim() !== '';
74
+
75
+ if (lastMessage?.role === 'model' && hasToolCalls && !hasContent) {
76
+ lastMessage.parts.push(...toolCalls);
77
+ } else {
78
+ const parts = [];
79
+ if (enableThinking) {
80
+ const cachedSig = getReasoningSignature(sessionId, actualModelName);
81
+ const thoughtSignature = cachedSig || getThoughtSignatureForModel(actualModelName);
82
+ parts.push({ text: ' ', thought: true });
83
+ parts.push({ text: ' ', thoughtSignature });
84
+ }
85
+ if (hasContent) parts.push({ text: textContent.trimEnd() });
86
+ parts.push(...toolCalls);
87
+ antigravityMessages.push({ role: 'model', parts });
88
+ }
89
+ }
90
+
91
+ function handleClaudeToolResult(message, antigravityMessages) {
92
+ const content = message.content;
93
+ if (!Array.isArray(content)) return;
94
+
95
+ for (const item of content) {
96
+ if (item.type !== 'tool_result') continue;
97
+
98
+ const toolUseId = item.tool_use_id;
99
+ let functionName = '';
100
+ for (let i = antigravityMessages.length - 1; i >= 0; i--) {
101
+ if (antigravityMessages[i].role === 'model') {
102
+ const parts = antigravityMessages[i].parts;
103
+ for (const part of parts) {
104
+ if (part.functionCall && part.functionCall.id === toolUseId) {
105
+ functionName = part.functionCall.name;
106
+ break;
107
+ }
108
+ }
109
+ if (functionName) break;
110
+ }
111
+ }
112
+
113
+ const lastMessage = antigravityMessages[antigravityMessages.length - 1];
114
+ let resultContent = '';
115
+ if (typeof item.content === 'string') {
116
+ resultContent = item.content;
117
+ } else if (Array.isArray(item.content)) {
118
+ resultContent = item.content.filter(c => c.type === 'text').map(c => c.text).join('');
119
+ }
120
+
121
+ const functionResponse = {
122
+ functionResponse: {
123
+ id: toolUseId,
124
+ name: functionName,
125
+ response: { output: resultContent }
126
+ }
127
+ };
128
+
129
+ if (lastMessage?.role === 'user' && lastMessage.parts.some(p => p.functionResponse)) {
130
+ lastMessage.parts.push(functionResponse);
131
+ } else {
132
+ antigravityMessages.push({ role: 'user', parts: [functionResponse] });
133
+ }
134
+ }
135
+ }
136
+
137
+ function claudeMessageToAntigravity(claudeMessages, enableThinking, actualModelName, sessionId) {
138
+ const antigravityMessages = [];
139
+ for (const message of claudeMessages) {
140
+ if (message.role === 'user') {
141
+ const content = message.content;
142
+ if (Array.isArray(content) && content.some(item => item.type === 'tool_result')) {
143
+ handleClaudeToolResult(message, antigravityMessages);
144
+ } else {
145
+ const extracted = extractImagesFromClaudeContent(content);
146
+ handleClaudeUserMessage(extracted, antigravityMessages);
147
+ }
148
+ } else if (message.role === 'assistant') {
149
+ handleClaudeAssistantMessage(message, antigravityMessages, enableThinking, actualModelName, sessionId);
150
+ }
151
+ }
152
+ return antigravityMessages;
153
+ }
154
+
155
+ function convertClaudeToolsToAntigravity(claudeTools, sessionId, actualModelName) {
156
+ if (!claudeTools || claudeTools.length === 0) return [];
157
+ return claudeTools.map((tool) => {
158
+ const rawParams = tool.input_schema || {};
159
+ const cleanedParams = cleanParameters(rawParams) || {};
160
+ if (cleanedParams.type === undefined) cleanedParams.type = 'object';
161
+ if (cleanedParams.type === 'object' && cleanedParams.properties === undefined) cleanedParams.properties = {};
162
+
163
+ const originalName = tool.name;
164
+ const safeName = sanitizeToolName(originalName);
165
+ if (sessionId && actualModelName && safeName !== originalName) {
166
+ setToolNameMapping(sessionId, actualModelName, safeName, originalName);
167
+ }
168
+
169
+ return {
170
+ functionDeclarations: [{
171
+ name: safeName,
172
+ description: tool.description || '',
173
+ parameters: cleanedParams
174
+ }]
175
+ };
176
+ });
177
+ }
178
+
179
+ export function generateClaudeRequestBody(claudeMessages, modelName, parameters, claudeTools, systemPrompt, token) {
180
+ const enableThinking = isEnableThinking(modelName);
181
+ const actualModelName = modelMapping(modelName);
182
+
183
+ const baseSystem = config.systemInstruction || '';
184
+ let mergedSystem = '';
185
+ if (config.useContextSystemPrompt && systemPrompt) {
186
+ const parts = [];
187
+ if (baseSystem.trim()) parts.push(baseSystem.trim());
188
+ if (systemPrompt.trim()) parts.push(systemPrompt.trim());
189
+ mergedSystem = parts.join('\n\n');
190
+ } else {
191
+ mergedSystem = baseSystem;
192
+ }
193
+
194
+ const requestBody = {
195
+ project: token.projectId,
196
+ requestId: generateRequestId(),
197
+ request: {
198
+ contents: claudeMessageToAntigravity(claudeMessages, enableThinking, actualModelName, token.sessionId),
199
+ tools: convertClaudeToolsToAntigravity(claudeTools, token.sessionId, actualModelName),
200
+ toolConfig: { functionCallingConfig: { mode: 'VALIDATED' } },
201
+ generationConfig: generateGenerationConfig(parameters, enableThinking, actualModelName),
202
+ sessionId: token.sessionId
203
+ },
204
+ model: actualModelName,
205
+ userAgent: 'antigravity'
206
+ };
207
+
208
+ if (mergedSystem) {
209
+ requestBody.request.systemInstruction = {
210
+ role: 'user',
211
+ parts: [{ text: mergedSystem }]
212
+ };
213
+ }
214
+
215
+ return requestBody;
216
+ }
src/utils/converters/gemini.js ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Gemini 格式转换工具
2
+ import config from '../../config/config.js';
3
+ import { generateRequestId } from '../idGenerator.js';
4
+ import { getReasoningSignature } from '../thoughtSignatureCache.js';
5
+ import { getThoughtSignatureForModel, modelMapping, isEnableThinking } from '../utils.js';
6
+
7
+ export function generateGeminiRequestBody(geminiBody, modelName, token) {
8
+ const enableThinking = isEnableThinking(modelName);
9
+ const actualModelName = modelMapping(modelName);
10
+
11
+ const request = JSON.parse(JSON.stringify(geminiBody));
12
+
13
+ if (request.contents && Array.isArray(request.contents)) {
14
+ const functionCallIds = [];
15
+ request.contents.forEach(content => {
16
+ if (content.role === 'model' && content.parts && Array.isArray(content.parts)) {
17
+ content.parts.forEach(part => {
18
+ if (part.functionCall) {
19
+ if (!part.functionCall.id) {
20
+ part.functionCall.id = `call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
21
+ }
22
+ functionCallIds.push(part.functionCall.id);
23
+ }
24
+ });
25
+ }
26
+ });
27
+
28
+ let responseIndex = 0;
29
+ request.contents.forEach(content => {
30
+ if (content.role === 'user' && content.parts && Array.isArray(content.parts)) {
31
+ content.parts.forEach(part => {
32
+ if (part.functionResponse) {
33
+ if (!part.functionResponse.id && responseIndex < functionCallIds.length) {
34
+ part.functionResponse.id = functionCallIds[responseIndex];
35
+ responseIndex++;
36
+ }
37
+ }
38
+ });
39
+ }
40
+ });
41
+
42
+ if (enableThinking) {
43
+ const cachedSig = getReasoningSignature(token.sessionId, actualModelName);
44
+ const thoughtSignature = cachedSig || getThoughtSignatureForModel(actualModelName);
45
+
46
+ request.contents.forEach(content => {
47
+ if (content.role === 'model' && content.parts && Array.isArray(content.parts)) {
48
+ const hasThought = content.parts.some(p => p.thought === true);
49
+ if (!hasThought) {
50
+ content.parts.unshift(
51
+ { text: ' ', thought: true },
52
+ { text: ' ', thoughtSignature }
53
+ );
54
+ }
55
+ }
56
+ });
57
+ }
58
+ }
59
+
60
+ if (!request.generationConfig) {
61
+ request.generationConfig = {};
62
+ }
63
+
64
+ if (enableThinking) {
65
+ const defaultThinkingBudget = config.defaults.thinking_budget ?? 1024;
66
+ if (!request.generationConfig.thinkingConfig) {
67
+ request.generationConfig.thinkingConfig = {
68
+ includeThoughts: true,
69
+ thinkingBudget: defaultThinkingBudget
70
+ };
71
+ }
72
+ }
73
+
74
+ request.generationConfig.candidateCount = 1;
75
+ request.sessionId = token.sessionId;
76
+ delete request.safetySettings;
77
+
78
+ const existingText = request.systemInstruction?.parts?.[0]?.text || '';
79
+ const mergedText = existingText ? `${config.systemInstruction}\n\n${existingText}` : config.systemInstruction ?? "";
80
+ request.systemInstruction = {
81
+ role: 'user',
82
+ parts: [{ text: mergedText }]
83
+ };
84
+
85
+ //console.log(JSON.stringify(request, null, 2))
86
+
87
+ const requestBody = {
88
+ project: token.projectId,
89
+ requestId: generateRequestId(),
90
+ request: request,
91
+ model: actualModelName,
92
+ userAgent: 'antigravity'
93
+ };
94
+
95
+ return requestBody;
96
+ }
src/utils/{openai_messages.js → converters/openai.js} RENAMED
@@ -1,6 +1,9 @@
1
- import { getReasoningSignature, getToolSignature } from './thoughtSignatureCache.js';
2
- import { setToolNameMapping } from './toolNameCache.js';
3
- import { getThoughtSignatureForModel, getToolSignatureForModel } from './openai_signatures.js';
 
 
 
4
 
5
  function extractImagesFromContent(content) {
6
  const result = { text: '', images: [] };
@@ -16,12 +19,10 @@ function extractImagesFromContent(content) {
16
  const imageUrl = item.image_url?.url || '';
17
  const match = imageUrl.match(/^data:image\/(\w+);base64,(.+)$/);
18
  if (match) {
19
- const format = match[1];
20
- const base64Data = match[2];
21
  result.images.push({
22
  inlineData: {
23
- mimeType: `image/${format}`,
24
- data: base64Data
25
  }
26
  });
27
  }
@@ -34,28 +35,10 @@ function extractImagesFromContent(content) {
34
  function handleUserMessage(extracted, antigravityMessages) {
35
  antigravityMessages.push({
36
  role: 'user',
37
- parts: [
38
- { text: extracted.text },
39
- ...extracted.images
40
- ]
41
  });
42
  }
43
 
44
- function sanitizeToolName(name) {
45
- if (!name || typeof name !== 'string') {
46
- return 'tool';
47
- }
48
- let cleaned = name.replace(/[^a-zA-Z0-9_-]/g, '_');
49
- cleaned = cleaned.replace(/^_+|_+$/g, '');
50
- if (!cleaned) {
51
- cleaned = 'tool';
52
- }
53
- if (cleaned.length > 128) {
54
- cleaned = cleaned.slice(0, 128);
55
- }
56
- return cleaned;
57
- }
58
-
59
  function handleAssistantMessage(message, antigravityMessages, enableThinking, actualModelName, sessionId) {
60
  const lastMessage = antigravityMessages[antigravityMessages.length - 1];
61
  const hasToolCalls = message.tool_calls && message.tool_calls.length > 0;
@@ -65,26 +48,20 @@ function handleAssistantMessage(message, antigravityMessages, enableThinking, ac
65
  ? message.tool_calls.map(toolCall => {
66
  const originalName = toolCall.function.name;
67
  const safeName = sanitizeToolName(originalName);
68
-
69
  const part = {
70
  functionCall: {
71
  id: toolCall.id,
72
  name: safeName,
73
- args: {
74
- query: toolCall.function.arguments
75
- }
76
  }
77
  };
78
-
79
  if (sessionId && actualModelName && safeName !== originalName) {
80
  setToolNameMapping(sessionId, actualModelName, safeName, originalName);
81
  }
82
-
83
  if (enableThinking) {
84
  const cachedToolSig = getToolSignature(sessionId, actualModelName);
85
  part.thoughtSignature = toolCall.thoughtSignature || cachedToolSig || getToolSignatureForModel(actualModelName);
86
  }
87
-
88
  return part;
89
  })
90
  : [];
@@ -93,27 +70,16 @@ function handleAssistantMessage(message, antigravityMessages, enableThinking, ac
93
  lastMessage.parts.push(...antigravityTools);
94
  } else {
95
  const parts = [];
96
-
97
  if (enableThinking) {
98
  const cachedSig = getReasoningSignature(sessionId, actualModelName);
99
  const thoughtSignature = message.thoughtSignature || cachedSig || getThoughtSignatureForModel(actualModelName);
100
- let reasoningText = '';
101
- if (typeof message.reasoning_content === 'string' && message.reasoning_content.length > 0) {
102
- reasoningText = message.reasoning_content;
103
- } else {
104
- reasoningText = ' ';
105
- }
106
  parts.push({ text: reasoningText, thought: true });
107
  parts.push({ text: ' ', thoughtSignature });
108
  }
109
-
110
  if (hasContent) parts.push({ text: message.content.trimEnd() });
111
  parts.push(...antigravityTools);
112
-
113
- antigravityMessages.push({
114
- role: 'model',
115
- parts
116
- });
117
  }
118
  }
119
 
@@ -137,19 +103,14 @@ function handleToolCall(message, antigravityMessages) {
137
  functionResponse: {
138
  id: message.tool_call_id,
139
  name: functionName,
140
- response: {
141
- output: message.content
142
- }
143
  }
144
  };
145
 
146
  if (lastMessage?.role === 'user' && lastMessage.parts.some(p => p.functionResponse)) {
147
  lastMessage.parts.push(functionResponse);
148
  } else {
149
- antigravityMessages.push({
150
- role: 'user',
151
- parts: [functionResponse]
152
- });
153
  }
154
  }
155
 
@@ -168,11 +129,67 @@ function openaiMessageToAntigravity(openaiMessages, enableThinking, actualModelN
168
  return antigravityMessages;
169
  }
170
 
171
- export {
172
- extractImagesFromContent,
173
- handleUserMessage,
174
- sanitizeToolName,
175
- handleAssistantMessage,
176
- handleToolCall,
177
- openaiMessageToAntigravity
178
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // OpenAI 格式转换工具
2
+ import config from '../../config/config.js';
3
+ import { generateRequestId } from '../idGenerator.js';
4
+ import { getReasoningSignature, getToolSignature } from '../thoughtSignatureCache.js';
5
+ import { setToolNameMapping } from '../toolNameCache.js';
6
+ import { getThoughtSignatureForModel, getToolSignatureForModel, sanitizeToolName, cleanParameters, modelMapping, isEnableThinking, generateGenerationConfig, extractSystemInstruction } from '../utils.js';
7
 
8
  function extractImagesFromContent(content) {
9
  const result = { text: '', images: [] };
 
19
  const imageUrl = item.image_url?.url || '';
20
  const match = imageUrl.match(/^data:image\/(\w+);base64,(.+)$/);
21
  if (match) {
 
 
22
  result.images.push({
23
  inlineData: {
24
+ mimeType: `image/${match[1]}`,
25
+ data: match[2]
26
  }
27
  });
28
  }
 
35
  function handleUserMessage(extracted, antigravityMessages) {
36
  antigravityMessages.push({
37
  role: 'user',
38
+ parts: [{ text: extracted.text }, ...extracted.images]
 
 
 
39
  });
40
  }
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  function handleAssistantMessage(message, antigravityMessages, enableThinking, actualModelName, sessionId) {
43
  const lastMessage = antigravityMessages[antigravityMessages.length - 1];
44
  const hasToolCalls = message.tool_calls && message.tool_calls.length > 0;
 
48
  ? message.tool_calls.map(toolCall => {
49
  const originalName = toolCall.function.name;
50
  const safeName = sanitizeToolName(originalName);
 
51
  const part = {
52
  functionCall: {
53
  id: toolCall.id,
54
  name: safeName,
55
+ args: { query: toolCall.function.arguments }
 
 
56
  }
57
  };
 
58
  if (sessionId && actualModelName && safeName !== originalName) {
59
  setToolNameMapping(sessionId, actualModelName, safeName, originalName);
60
  }
 
61
  if (enableThinking) {
62
  const cachedToolSig = getToolSignature(sessionId, actualModelName);
63
  part.thoughtSignature = toolCall.thoughtSignature || cachedToolSig || getToolSignatureForModel(actualModelName);
64
  }
 
65
  return part;
66
  })
67
  : [];
 
70
  lastMessage.parts.push(...antigravityTools);
71
  } else {
72
  const parts = [];
 
73
  if (enableThinking) {
74
  const cachedSig = getReasoningSignature(sessionId, actualModelName);
75
  const thoughtSignature = message.thoughtSignature || cachedSig || getThoughtSignatureForModel(actualModelName);
76
+ const reasoningText = (typeof message.reasoning_content === 'string' && message.reasoning_content.length > 0) ? message.reasoning_content : ' ';
 
 
 
 
 
77
  parts.push({ text: reasoningText, thought: true });
78
  parts.push({ text: ' ', thoughtSignature });
79
  }
 
80
  if (hasContent) parts.push({ text: message.content.trimEnd() });
81
  parts.push(...antigravityTools);
82
+ antigravityMessages.push({ role: 'model', parts });
 
 
 
 
83
  }
84
  }
85
 
 
103
  functionResponse: {
104
  id: message.tool_call_id,
105
  name: functionName,
106
+ response: { output: message.content }
 
 
107
  }
108
  };
109
 
110
  if (lastMessage?.role === 'user' && lastMessage.parts.some(p => p.functionResponse)) {
111
  lastMessage.parts.push(functionResponse);
112
  } else {
113
+ antigravityMessages.push({ role: 'user', parts: [functionResponse] });
 
 
 
114
  }
115
  }
116
 
 
129
  return antigravityMessages;
130
  }
131
 
132
+ function convertOpenAIToolsToAntigravity(openaiTools, sessionId, actualModelName) {
133
+ if (!openaiTools || openaiTools.length === 0) return [];
134
+ return openaiTools.map((tool) => {
135
+ const rawParams = tool.function?.parameters || {};
136
+ const cleanedParams = cleanParameters(rawParams) || {};
137
+ if (cleanedParams.type === undefined) cleanedParams.type = 'object';
138
+ if (cleanedParams.type === 'object' && cleanedParams.properties === undefined) cleanedParams.properties = {};
139
+
140
+ const originalName = tool.function?.name;
141
+ const safeName = sanitizeToolName(originalName);
142
+ if (sessionId && actualModelName && safeName !== originalName) {
143
+ setToolNameMapping(sessionId, actualModelName, safeName, originalName);
144
+ }
145
+
146
+ return {
147
+ functionDeclarations: [{
148
+ name: safeName,
149
+ description: tool.function.description,
150
+ parameters: cleanedParams
151
+ }]
152
+ };
153
+ });
154
+ }
155
+
156
+ export function generateRequestBody(openaiMessages, modelName, parameters, openaiTools, token) {
157
+ const enableThinking = isEnableThinking(modelName);
158
+ const actualModelName = modelMapping(modelName);
159
+ const mergedSystemInstruction = extractSystemInstruction(openaiMessages);
160
+ let filteredMessages = openaiMessages;
161
+ let startIndex = 0;
162
+ if (config.useContextSystemPrompt) {
163
+ for (let i = 0; i < openaiMessages.length; i++) {
164
+ if (openaiMessages[i].role === 'system') {
165
+ startIndex = i + 1;
166
+ } else {
167
+ filteredMessages = openaiMessages.slice(startIndex);
168
+ break;
169
+ }
170
+ }
171
+ }
172
+
173
+ const requestBody = {
174
+ project: token.projectId,
175
+ requestId: generateRequestId(),
176
+ request: {
177
+ contents: openaiMessageToAntigravity(filteredMessages, enableThinking, actualModelName, token.sessionId),
178
+ tools: convertOpenAIToolsToAntigravity(openaiTools, token.sessionId, actualModelName),
179
+ toolConfig: { functionCallingConfig: { mode: 'VALIDATED' } },
180
+ generationConfig: generateGenerationConfig(parameters, enableThinking, actualModelName),
181
+ sessionId: token.sessionId
182
+ },
183
+ model: actualModelName,
184
+ userAgent: 'antigravity'
185
+ };
186
+
187
+ if (mergedSystemInstruction) {
188
+ requestBody.request.systemInstruction = {
189
+ role: 'user',
190
+ parts: [{ text: mergedSystemInstruction }]
191
+ };
192
+ }
193
+
194
+ return requestBody;
195
+ }
src/utils/openai_generation.js DELETED
@@ -1,58 +0,0 @@
1
- import config from '../config/config.js';
2
- import { REASONING_EFFORT_MAP, DEFAULT_STOP_SEQUENCES } from '../constants/index.js';
3
-
4
- function modelMapping(modelName) {
5
- if (modelName === 'claude-sonnet-4-5-thinking') {
6
- return 'claude-sonnet-4-5';
7
- } else if (modelName === 'claude-opus-4-5') {
8
- return 'claude-opus-4-5-thinking';
9
- } else if (modelName === 'gemini-2.5-flash-thinking') {
10
- return 'gemini-2.5-flash';
11
- }
12
- return modelName;
13
- }
14
-
15
- function isEnableThinking(modelName) {
16
- return modelName.includes('-thinking') ||
17
- modelName === 'gemini-2.5-pro' ||
18
- modelName.startsWith('gemini-3-pro-') ||
19
- modelName === 'rev19-uic3-1p' ||
20
- modelName === 'gpt-oss-120b-medium';
21
- }
22
-
23
- function generateGenerationConfig(parameters, enableThinking, actualModelName) {
24
- const defaultThinkingBudget = config.defaults.thinking_budget ?? 1024;
25
- let thinkingBudget = 0;
26
- if (enableThinking) {
27
- if (parameters.thinking_budget !== undefined) {
28
- thinkingBudget = parameters.thinking_budget;
29
- } else if (parameters.reasoning_effort !== undefined) {
30
- thinkingBudget = REASONING_EFFORT_MAP[parameters.reasoning_effort] ?? defaultThinkingBudget;
31
- } else {
32
- thinkingBudget = defaultThinkingBudget;
33
- }
34
- }
35
-
36
- const generationConfig = {
37
- topP: parameters.top_p ?? config.defaults.top_p,
38
- topK: parameters.top_k ?? config.defaults.top_k,
39
- temperature: parameters.temperature ?? config.defaults.temperature,
40
- candidateCount: 1,
41
- maxOutputTokens: parameters.max_tokens ?? config.defaults.max_tokens,
42
- stopSequences: DEFAULT_STOP_SEQUENCES,
43
- thinkingConfig: {
44
- includeThoughts: enableThinking,
45
- thinkingBudget: thinkingBudget
46
- }
47
- };
48
- if (enableThinking && actualModelName.includes('claude')) {
49
- delete generationConfig.topP;
50
- }
51
- return generationConfig;
52
- }
53
-
54
- export {
55
- modelMapping,
56
- isEnableThinking,
57
- generateGenerationConfig
58
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils/openai_mapping.js DELETED
@@ -1,81 +0,0 @@
1
- import config from '../config/config.js';
2
- import { generateRequestId } from './idGenerator.js';
3
- import { openaiMessageToAntigravity } from './openai_messages.js';
4
- import { extractSystemInstruction } from './openai_system.js';
5
- import { convertOpenAIToolsToAntigravity } from './openai_tools.js';
6
- import { modelMapping, isEnableThinking, generateGenerationConfig } from './openai_generation.js';
7
- import os from 'os';
8
-
9
- function generateRequestBody(openaiMessages, modelName, parameters, openaiTools, token) {
10
- const enableThinking = isEnableThinking(modelName);
11
- const actualModelName = modelMapping(modelName);
12
- const mergedSystemInstruction = extractSystemInstruction(openaiMessages);
13
-
14
- let startIndex = 0;
15
- if (config.useContextSystemPrompt) {
16
- for (let i = 0; i < openaiMessages.length; i++) {
17
- if (openaiMessages[i].role === 'system') {
18
- startIndex = i + 1;
19
- } else {
20
- break;
21
- }
22
- }
23
- }
24
- const filteredMessages = openaiMessages.slice(startIndex);
25
-
26
- const requestBody = {
27
- project: token.projectId,
28
- requestId: generateRequestId(),
29
- request: {
30
- contents: openaiMessageToAntigravity(filteredMessages, enableThinking, actualModelName, token.sessionId),
31
- tools: convertOpenAIToolsToAntigravity(openaiTools, token.sessionId, actualModelName),
32
- toolConfig: {
33
- functionCallingConfig: {
34
- mode: 'VALIDATED'
35
- }
36
- },
37
- generationConfig: generateGenerationConfig(parameters, enableThinking, actualModelName),
38
- sessionId: token.sessionId
39
- },
40
- model: actualModelName,
41
- userAgent: 'antigravity'
42
- };
43
-
44
- if (mergedSystemInstruction) {
45
- requestBody.request.systemInstruction = {
46
- role: 'user',
47
- parts: [{ text: mergedSystemInstruction }]
48
- };
49
- }
50
-
51
- return requestBody;
52
- }
53
-
54
- function prepareImageRequest(requestBody) {
55
- if (!requestBody || !requestBody.request) return requestBody;
56
- requestBody.request.generationConfig = { candidateCount: 1 };
57
- requestBody.requestType = 'image_gen';
58
- delete requestBody.request.systemInstruction;
59
- delete requestBody.request.tools;
60
- delete requestBody.request.toolConfig;
61
- return requestBody;
62
- }
63
-
64
- function getDefaultIp() {
65
- const interfaces = os.networkInterfaces();
66
- for (const iface of Object.values(interfaces)) {
67
- for (const inter of iface) {
68
- if (inter.family === 'IPv4' && !inter.internal) {
69
- return inter.address;
70
- }
71
- }
72
- }
73
- return '127.0.0.1';
74
- }
75
-
76
- export {
77
- generateRequestId,
78
- generateRequestBody,
79
- prepareImageRequest,
80
- getDefaultIp
81
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils/openai_signatures.js DELETED
@@ -1,28 +0,0 @@
1
- const CLAUDE_THOUGHT_SIGNATURE = 'RXFRRENrZ0lDaEFDR0FJcVFKV1Bvcy9GV20wSmtMV2FmWkFEbGF1ZTZzQTdRcFlTc1NvbklmemtSNFo4c1dqeitIRHBOYW9hS2NYTE1TeTF3bjh2T1RHdE1KVjVuYUNQclZ5cm9DMFNETHk4M0hOSWsrTG1aRUhNZ3hvTTl0ZEpXUDl6UUMzOExxc2ZJakI0UkkxWE1mdWJ1VDQrZnY0Znp0VEoyTlhtMjZKL2daYi9HL1gwcmR4b2x0VE54empLemtLcEp0ZXRia2plb3NBcWlRSWlXUHloMGhVVTk1dHNha1dyNDVWNUo3MTJjZDNxdHQ5Z0dkbjdFaFk4dUllUC9CcThVY2VZZC9YbFpYbDc2bHpEbmdzL2lDZXlNY3NuZXdQMjZBTDRaQzJReXdibVQzbXlSZmpld3ZSaUxxOWR1TVNidHIxYXRtYTJ0U1JIRjI0Z0JwUnpadE1RTmoyMjR4bTZVNUdRNXlOSWVzUXNFNmJzRGNSV0RTMGFVOEZERExybmhVQWZQT2JYMG5lTGR1QnU1VGZOWW9NZglRbTgyUHVqVE1xaTlmN0t2QmJEUUdCeXdyVXR2eUNnTEFHNHNqeWluZDRCOEg3N2ZJamt5blI3Q3ZpQzlIOTVxSENVTCt3K3JzMmsvV0sxNlVsbGlTK0pET3UxWXpPMWRPOUp3V3hEMHd5ZVU0a0Y5MjIxaUE5Z2lUd2djZXhSU2c4TWJVMm1NSjJlaGdlY3g0YjJ3QloxR0FFPQ==';
2
- const GEMINI_THOUGHT_SIGNATURE = 'EqAHCp0HAXLI2nygRbdzD4Vgzxxi7tbM87zIRkNgPLqTj+Jxv9mY8Q0G87DzbTtvsIFhWB0RZMoEK6ntm5GmUe6ADtxHk4zgHUs/FKqTu8tzUdPRDrKn3KCAtFW4LJqijZoFxNKMyQRmlgPUX4tGYE7pllD77UK6SjCwKhKZoSVZLMiPXP9YFktbida1Q5upXMrzG1t8abPmpFo983T/rgWlNqJp+Fb+bsoH0zuSpmU4cPKO3LIGsxBhvRhM/xydahZD+VpEX7TEJAN58z1RomFyx9u0IR7ukwZr2UyoNA+uj8OChUDFupQsVwbm3XE1UAt22BGvfYIyyZ42fxgOgsFFY+AZ72AOufcmZb/8vIw3uEUgxHczdl+NGLuS4Hsy/AAntdcH9sojSMF3qTf+ZK1FMav23SPxUBtU5T9HCEkKqQWRnMsVGYV1pupFisWo85hRLDTUipxVy9ug1hN8JBYBNmGLf8KtWLhVp7Z11PIAZj3C6HzoVyiVeuiorwNrn0ZaaXNe+y5LHuDF0DNZhrIfnXByq6grLLSAv4fTLeCJvfGzTWWyZDMbVXNx1HgumKq8calP9wv33t0hfEaOlcmfGIyh1J/N+rOGR0WXcuZZP5/VsFR44S2ncpwTPT+MmR0PsjocDenRY5m/X4EXbGGkZ+cfPnWoA64bn3eLeJTwxl9W1ZbmYS6kjpRGUMxExgRNOzWoGISddHCLcQvN7o50K8SF5k97rxiS5q4rqDmqgRPXzQTQnZyoL3dCxScX9cvLSjNCZDcotonDBAWHfkXZ0/EmFiONQcLJdANtAjwoA44Mbn50gubrTsNd7d0Rm/hbNEh/ZceUalV5MMcl6tJtahCJoybQMsnjWuBXl7cXiKmqAvxTDxIaBgQBYAo4FrbV4zQv35zlol+O3YiyjJn/U0oBeO5pEcH1d0vnLgYP71jZVY2FjWRKnDR9aw4JhiuqAa+i0tupkBy+H4/SVwHADFQq6wcsL8qvXlwktJL9MIAoaXDkIssw6gKE9EuGd7bSO9f+sA8CZ0I8LfJ3jcHUsE/3qd4pFrn5RaET56+1p8ZHZDDUQ0p1okApUCCYsC2WuL6O9P4fcg3yitAA/AfUUNjHKANE+ANneQ0efMG7fx9bvI+iLbXgPupApoov24JRkmhHsrJiu9bp+G/pImd2PNv7ArunJ6upl0VAUWtRyLWyGfdl6etGuY8vVJ7JdWEQ8aWzRK3g6e+8YmDtP5DAfw==';
3
- const CLAUDE_TOOL_SIGNATURE = 'RXVNQkNrZ0lDaEFDR0FJcVFLZGsvMnlyR0VTbmNKMXEyTFIrcWwyY2ozeHhoZHRPb0VOYWJ2VjZMSnE2MlBhcEQrUWdIM3ZWeHBBUG9rbGN1aXhEbXprZTcvcGlkbWRDQWs5MWcrTVNERnRhbWJFOU1vZWZGc1pWSGhvTUxsMXVLUzRoT3BIaWwyeXBJakNYa05EVElMWS9talprdUxvRjFtMmw5dnkrbENhSDNNM3BYNTM0K1lRZ0NaWTQvSUNmOXo4SkhZVzU2Sm1WcTZBcVNRUURBRGVMV1BQRXk1Q0JsS0dCZXlNdHp2NGRJQVlGbDFSMDBXNGhqNHNiSWNKeGY0UGZVQTBIeE1mZjJEYU5BRXdrWUJ4MmNzRFMrZGM1N1hnUlVNblpkZ0hTVHVNaGdod1lBUT09';
4
- const GEMINI_TOOL_SIGNATURE = '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';
5
-
6
- const DEFAULT_THOUGHT_SIGNATURE = CLAUDE_THOUGHT_SIGNATURE;
7
- const DEFAULT_TOOL_SIGNATURE = CLAUDE_TOOL_SIGNATURE;
8
-
9
- function getThoughtSignatureForModel(actualModelName) {
10
- if (!actualModelName) return DEFAULT_THOUGHT_SIGNATURE;
11
- const lower = actualModelName.toLowerCase();
12
- if (lower.includes('claude')) return CLAUDE_THOUGHT_SIGNATURE;
13
- if (lower.includes('gemini')) return GEMINI_THOUGHT_SIGNATURE;
14
- return DEFAULT_THOUGHT_SIGNATURE;
15
- }
16
-
17
- function getToolSignatureForModel(actualModelName) {
18
- if (!actualModelName) return DEFAULT_TOOL_SIGNATURE;
19
- const lower = actualModelName.toLowerCase();
20
- if (lower.includes('claude')) return CLAUDE_TOOL_SIGNATURE;
21
- if (lower.includes('gemini')) return GEMINI_TOOL_SIGNATURE;
22
- return DEFAULT_TOOL_SIGNATURE;
23
- }
24
-
25
- export {
26
- getThoughtSignatureForModel,
27
- getToolSignatureForModel
28
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils/openai_system.js DELETED
@@ -1,33 +0,0 @@
1
- import config from '../config/config.js';
2
-
3
- function extractSystemInstruction(openaiMessages) {
4
- const baseSystem = config.systemInstruction || '';
5
- if (!config.useContextSystemPrompt) {
6
- return baseSystem;
7
- }
8
- const systemTexts = [];
9
- for (const message of openaiMessages) {
10
- if (message.role === 'system') {
11
- const content = typeof message.content === 'string'
12
- ? message.content
13
- : (Array.isArray(message.content)
14
- ? message.content.filter(item => item.type === 'text').map(item => item.text).join('')
15
- : '');
16
- if (content.trim()) {
17
- systemTexts.push(content.trim());
18
- }
19
- } else {
20
- break;
21
- }
22
- }
23
- const parts = [];
24
- if (baseSystem.trim()) {
25
- parts.push(baseSystem.trim());
26
- }
27
- if (systemTexts.length > 0) {
28
- parts.push(systemTexts.join('\n\n'));
29
- }
30
- return parts.join('\n\n');
31
- }
32
-
33
- export { extractSystemInstruction };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils/openai_tools.js DELETED
@@ -1,83 +0,0 @@
1
- import { setToolNameMapping } from './toolNameCache.js';
2
-
3
- const EXCLUDED_KEYS = new Set([
4
- '$schema',
5
- 'additionalProperties',
6
- 'minLength',
7
- 'maxLength',
8
- 'minItems',
9
- 'maxItems',
10
- 'uniqueItems',
11
- 'exclusiveMaximum',
12
- 'exclusiveMinimum',
13
- 'const',
14
- 'anyOf',
15
- 'oneOf',
16
- 'allOf',
17
- 'any_of',
18
- 'one_of',
19
- 'all_of'
20
- ]);
21
-
22
- function cleanParameters(obj) {
23
- if (!obj || typeof obj !== 'object') return obj;
24
- const cleaned = Array.isArray(obj) ? [] : {};
25
- for (const [key, value] of Object.entries(obj)) {
26
- if (EXCLUDED_KEYS.has(key)) continue;
27
- const cleanedValue = (value && typeof value === 'object') ? cleanParameters(value) : value;
28
- cleaned[key] = cleanedValue;
29
- }
30
- return cleaned;
31
- }
32
-
33
- function sanitizeToolName(name) {
34
- if (!name || typeof name !== 'string') {
35
- return 'tool';
36
- }
37
- let cleaned = name.replace(/[^a-zA-Z0-9_-]/g, '_');
38
- cleaned = cleaned.replace(/^_+|_+$/g, '');
39
- if (!cleaned) {
40
- cleaned = 'tool';
41
- }
42
- if (cleaned.length > 128) {
43
- cleaned = cleaned.slice(0, 128);
44
- }
45
- return cleaned;
46
- }
47
-
48
- function convertOpenAIToolsToAntigravity(openaiTools, sessionId, actualModelName) {
49
- if (!openaiTools || openaiTools.length === 0) return [];
50
- return openaiTools.map((tool) => {
51
- const rawParams = tool.function?.parameters || {};
52
- const cleanedParams = cleanParameters(rawParams) || {};
53
- if (cleanedParams.type === undefined) {
54
- cleanedParams.type = 'object';
55
- }
56
- if (cleanedParams.type === 'object' && cleanedParams.properties === undefined) {
57
- cleanedParams.properties = {};
58
- }
59
-
60
- const originalName = tool.function?.name;
61
- const safeName = sanitizeToolName(originalName);
62
-
63
- if (sessionId && actualModelName && safeName !== originalName) {
64
- setToolNameMapping(sessionId, actualModelName, safeName, originalName);
65
- }
66
-
67
- return {
68
- functionDeclarations: [
69
- {
70
- name: safeName,
71
- description: tool.function.description,
72
- parameters: cleanedParams
73
- }
74
- ]
75
- };
76
- });
77
- }
78
-
79
- export {
80
- cleanParameters,
81
- sanitizeToolName,
82
- convertOpenAIToolsToAntigravity
83
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils/utils.js CHANGED
@@ -1,283 +1,77 @@
 
1
  import config from '../config/config.js';
2
- import tokenManager from '../auth/token_manager.js';
3
- import { generateRequestId } from './idGenerator.js';
4
  import os from 'os';
5
- import { getReasoningSignature, getToolSignature } from './thoughtSignatureCache.js';
6
- import { setToolNameMapping } from './toolNameCache.js';
7
 
8
- // 思维链签名常量
9
- // Claude 模型签名
10
- const CLAUDE_THOUGHT_SIGNATURE = 'RXFRRENrZ0lDaEFDR0FJcVFKV1Bvcy9GV20wSmtMV2FmWkFEbGF1ZTZzQTdRcFlTc1NvbklmemtSNFo4c1dqeitIRHBOYW9hS2NYTE1TeTF3bjh2T1RHdE1KVjVuYUNQclZ5cm9DMFNETHk4M0hOSWsrTG1aRUhNZ3hvTTl0ZEpXUDl6UUMzOExxc2ZJakI0UkkxWE1mdWJ1VDQrZnY0Znp0VEoyTlhtMjZKL2daYi9HL1gwcmR4b2x0VE54empLemtLcEp0ZXRia2plb3NBcWlRSWlXUHloMGhVVTk1dHNha1dyNDVWNUo3MTJjZDNxdHQ5Z0dkbjdFaFk4dUllUC9CcThVY2VZZC9YbFpYbDc2bHpEbmdzL2lDZXlNY3NuZXdQMjZBTDRaQzJReXdibVQzbXlSZmpld3ZSaUxxOWR1TVNidHIxYXRtYTJ0U1JIRjI0Z0JwUnpadE1RTmoyMjR4bTZVNUdRNXlOSWVzUXNFNmJzRGNSV0RTMGFVOEZERExybmhVQWZQT2JYMG5lTGR1QnU1VGZOWW9NZGlRbTgyUHVqVE1xaTlmN0t2QmJEUUdCeXdyVXR2eUNnTEFHNHNqeWluZDRCOEg3N2ZJamt5blI3Q3ZpQzlIOTVxSENVTCt3K3JzMmsvV0sxNlVsbGlTK0pET3UxWXpPMWRPOUp3V3hEMHd5ZVU0a0Y5MjIxaUE5Z2lUd2djZXhSU2c4TWJVMm1NSjJlaGdlY3g0YjJ3QloxR0FFPQ==';
11
- // Gemini 思维链签名
12
  const GEMINI_THOUGHT_SIGNATURE = 'EqAHCp0HAXLI2nygRbdzD4Vgzxxi7tbM87zIRkNgPLqTj+Jxv9mY8Q0G87DzbTtvsIFhWB0RZMoEK6ntm5GmUe6ADtxHk4zgHUs/FKqTu8tzUdPRDrKn3KCAtFW4LJqijZoFxNKMyQRmlgPUX4tGYE7pllD77UK6SjCwKhKZoSVZLMiPXP9YFktbida1Q5upXMrzG1t8abPmpFo983T/rgWlNqJp+Fb+bsoH0zuSpmU4cPKO3LIGsxBhvRhM/xydahZD+VpEX7TEJAN58z1RomFyx9u0IR7ukwZr2UyoNA+uj8OChUDFupQsVwbm3XE1UAt22BGvfYIyyZ42fxgOgsFFY+AZ72AOufcmZb/8vIw3uEUgxHczdl+NGLuS4Hsy/AAntdcH9sojSMF3qTf+ZK1FMav23SPxUBtU5T9HCEkKqQWRnMsVGYV1pupFisWo85hRLDTUipxVy9ug1hN8JBYBNmGLf8KtWLhVp7Z11PIAZj3C6HzoVyiVeuiorwNrn0ZaaXNe+y5LHuDF0DNZhrIfnXByq6grLLSAv4fTLeCJvfGzTWWyZDMbVXNx1HgumKq8calP9wv33t0hfEaOlcmfGIyh1J/N+rOGR0WXcuZZP5/VsFR44S2ncpwTPT+MmR0PsjocDenRY5m/X4EXbGGkZ+cfPnWoA64bn3eLeJTwxl9W1ZbmYS6kjpRGUMxExgRNOzWoGISddHCLcQvN7o50K8SF5k97rxiS5q4rqDmqgRPXzQTQnZyoL3dCxScX9cvLSjNCZDcotonDBAWHfkXZ0/EmFiONQcLJdANtAjwoA44Mbn50gubrTsNd7d0Rm/hbNEh/ZceUalV5MMcl6tJtahCJoybQMsnjWuBXl7cXiKmqAvxTDxIaBgQBYAo4FrbV4zQv35zlol+O3YiyjJn/U0oBeO5pEcH1d0vnLgYP71jZVY2FjWRKnDR9aw4JhiuqAa+i0tupkBy+H4/SVwHADFQq6wcsL8qvXlwktJL9MIAoaXDkIssw6gKE9EuGd7bSO9f+sA8CZ0I8LfJ3jcHUsE/3qd4pFrn5RaET56+1p8ZHZDDUQ0p1okApUCCYsC2WuL6O9P4fcg3yitAA/AfUUNjHKANE+ANneQ0efMG7fx9bvI+iLbXgPupApoov24JRkmhHsrJiu9bp+G/pImd2PNv7ArunJ6upl0VAUWtRyLWyGfdl6etGuY8vVJ7JdWEQ8aWzRK3g6e+8YmDtP5DAfw==';
13
- // 工具调用思维链签名
14
- const TOOL_THOUGHT_SIGNATURE = 'EqoNCqcNAXLI2nwkidsFconk7xHt7x0zIOX7n/JR7DTKiPa/03uqJ9OmZaujaw0xNQxZ0wNCx8NguJ+sAfaIpek62+aBnciUTQd5UEmwM/V5o6EA2wPvv4IpkXyl6Eyvr8G+jD/U4c2Tu4M4WzVhcImt9Lf/ZH6zydhxgU9ZgBtMwck292wuThVNqCZh9akqy12+BPHs9zW8IrPGv3h3u64Q2Ye9Mzx+EtpV2Tiz8mcq4whdUu72N6LQVQ+xLLdzZ+CQ7WgEjkqOWQs2C09DlAsdu5vjLeF5ZgpL9seZIag9Dmhuk589l/I20jGgg7EnCgojzarBPHNOCHrxTbcp325tTLPa6Y7U4PgofJEkv0MX4O22mu/On6TxAlqYkVa6twdEHYb+zMFWQl7SVFwQTY9ub7zeSaW+p/yJ+5H43LzC95aEcrfTaX0P2cDWGrQ1IVtoaEWPi7JVOtDSqchVC1YLRbIUHaWGyAysx7BRoSBIr46aVbGNy2Xvt35Vqt0tDJRyBdRuKXTmf1px6mbDpsjldxE/YLzCkCtAp1Ji1X9XPFhZbj7HTNIjCRfIeHA/6IyOB0WgBiCw5e2p50frlixd+iWD3raPeS/VvCBvn/DPCsnH8lzgpDQqaYeN/y0K5UWeMwFUg+00YFoN9D34q6q3PV9yuj1OGT2l/DzCw8eR5D460S6nQtYOaEsostvCgJGipamf/dnUzHomoiqZegJzfW7uzIQl1HJXQJTnpTmk07LarQwxIPtId9JP+dXKLZMw5OAYWITfSXF5snb7F1jdN0NydJOVkeanMsxnbIyU7/iKLDWJAmcRru/GavbJGgB0vJgY52SkPi9+uhfF8u60gLqFpbhsal3oxSPJSzeg+TN/qktBGST2YvLHxilPKmLBhggTUZhDSzSjxPfseE41FHYniyn6O+b3tujCdvexnrIjmmX+KTQC3ovjfk/ArwImI/cGihFYOc+wDnri5iHofdLbFymE/xb1Q4Sn06gVq1sgmeeS/li0F6C0v9GqOQ4olqQrTT2PPDVMbDrXgjZMfHk9ciqQ5OB6r19uyIqb6lFplKsE/ZSacAGtw1K0HENMq9q576m0beUTtNRJMktXem/OJIDbpRE0cXfBt1J9VxYHBe6aEiIZmRzJnXtJmUCjqfLPg9n0FKUIjnnln7as+aiRpItb5ZfJjrMEu154ePgUa1JYv2MA8oj5rvzpxRSxycD2p8HTxshitnLFI8Q6Kl2gUqBI27uzYSPyBtrvWZaVtrXYMiyjOFBdjUFunBIW2UvoPSKYEaNrUO3tTSYO4GjgLsfCRQ2CMfclq/TbCALjvzjMaYLrn6OKQnSDI/Tt1J6V6pDXfSyLdCIDg77NTvdqTH2Cv3yT3fE3nOOW5mUPZtXAIxPkFGo9eL+YksEgLIeZor0pdb+BHs1kQ4z7EplCYVhpTbo6fMcarW35Qew9HPMTFQ03rQaDhlNnUUI3tacnDMQvKsfo4OPTQYG2zP4lHXSsf4IpGRJyTBuMGK6siiKBiL/u73HwKTDEu2RU/4ZmM6dQJkoh+6sXCCmoZuweYOeF2cAx2AJAHD72qmEPzLihm6bWeSRXDxJGm2RO85NgK5khNfV2Mm1etmQdDdbTLJV5FTvJQJ5zVDnYQkk7SKDio9rQMBucw5M6MyvFFDFdzJQlVKZm/GZ5T21GsmNHMJNd9G2qYAKwUV3Mb64Ipk681x8TFG+1AwkfzSWCHnbXMG2bOX+JUt/4rldyRypArvxhyNimEDc7HoqSHwTVfpd6XA0u8emcQR1t+xAR2BiT/elQHecAvhRtJt+ts44elcDIzTCBiJG4DEoV8X0pHb1oTLJFcD8aF29BWczl4kYDPtR9Dtlyuvmaljt0OEeLz9zS0MGvpflvMtUmFdGq7ZP+GztIdWup4kZZ59pzTuSR9itskMAnqYj+V9YBCSUUmsxW6Zj4Uvzw0nLYsjIgTjP3SU9WvwUhvJWzu5wZkdu3e03YoGxUjLWDXMKeSZ/g2Th5iNn3xlJwp5Z2p0jsU1rH4K/iMsYiLBJkGnsYuBqqFt2UIPYziqxOKV41oSKdEU+n4mD3WarU/kR4krTkmmEj2aebWgvHpsZSW0ULaeK3QxNBdx7waBUUkZ7nnDIRDi31T/sBYl+UADEFvm2INIsFuXPUyXbAthNWn5vIQNlKNLCwpGYqhuzO4hno8vyqbxKsrMtayk1U+0TQsBbQY1VuFF2bDBNFcPQOv/7KPJDL8hal0U6J0E6DVZVcH4Gel7pgsBeC+48=';
15
- // 兜底签名(非 Claude/Gemini 时)
16
- const DEFAULT_THOUGHT_SIGNATURE = CLAUDE_THOUGHT_SIGNATURE;
17
 
18
- function getThoughtSignatureForModel(actualModelName) {
19
- if (!actualModelName) return DEFAULT_THOUGHT_SIGNATURE;
20
  const lower = actualModelName.toLowerCase();
21
  if (lower.includes('claude')) return CLAUDE_THOUGHT_SIGNATURE;
22
  if (lower.includes('gemini')) return GEMINI_THOUGHT_SIGNATURE;
23
- return DEFAULT_THOUGHT_SIGNATURE;
24
  }
25
 
26
- function extractImagesFromContent(content) {
27
- const result = { text: '', images: [] };
28
-
29
- // 如果content是字符串,直接返回
30
- if (typeof content === 'string') {
31
- result.text = content;
32
- return result;
33
- }
34
-
35
- // 如果content是数组(multimodal格式)
36
- if (Array.isArray(content)) {
37
- for (const item of content) {
38
- if (item.type === 'text') {
39
- result.text += item.text;
40
- } else if (item.type === 'image_url') {
41
- // 提取base64图片数据
42
- const imageUrl = item.image_url?.url || '';
43
-
44
- // 匹配 data:image/{format};base64,{data} 格式
45
- const match = imageUrl.match(/^data:image\/(\w+);base64,(.+)$/);
46
- if (match) {
47
- const format = match[1]; // 例如 png, jpeg, jpg
48
- const base64Data = match[2];
49
- result.images.push({
50
- inlineData: {
51
- mimeType: `image/${format}`,
52
- data: base64Data
53
- }
54
- })
55
- }
56
- }
57
- }
58
- }
59
-
60
- return result;
61
- }
62
- function handleUserMessage(extracted, antigravityMessages){
63
- antigravityMessages.push({
64
- role: "user",
65
- parts: [
66
- {
67
- text: extracted.text
68
- },
69
- ...extracted.images
70
- ]
71
- })
72
  }
73
- // 将工具名称规范为 Vertex 要求的格式:^[a-zA-Z0-9_-]{1,128}$
74
- function sanitizeToolName(name) {
75
- if (!name || typeof name !== 'string') {
76
- return 'tool';
77
- }
78
- // 替换非法字符为下划线
79
  let cleaned = name.replace(/[^a-zA-Z0-9_-]/g, '_');
80
- // 去掉首尾多余下划线
81
  cleaned = cleaned.replace(/^_+|_+$/g, '');
82
- if (!cleaned) {
83
- cleaned = 'tool';
84
- }
85
- // 限制最大长度 128
86
- if (cleaned.length > 128) {
87
- cleaned = cleaned.slice(0, 128);
88
- }
89
  return cleaned;
90
  }
91
- function handleAssistantMessage(message, antigravityMessages, enableThinking, actualModelName, sessionId){
92
- const lastMessage = antigravityMessages[antigravityMessages.length - 1];
93
- const hasToolCalls = message.tool_calls && message.tool_calls.length > 0;
94
- const hasContent = message.content && message.content.trim() !== '';
95
-
96
- const antigravityTools = hasToolCalls ? message.tool_calls.map(toolCall => {
97
- const originalName = toolCall.function.name;
98
- const safeName = sanitizeToolName(originalName);
99
-
100
- const part = {
101
- functionCall: {
102
- id: toolCall.id,
103
- name: safeName,
104
- args: {
105
- query: toolCall.function.arguments
106
- }
107
- }
108
- };
109
-
110
- // 记录原始工具名到安全名的映射(仅当确实发生了变化时)
111
- if (sessionId && actualModelName && safeName !== originalName) {
112
- setToolNameMapping(sessionId, actualModelName, safeName, originalName);
113
- }
114
-
115
- // 启用思考模型时,工具调用优先使用实时签名(如果上游带了),否则兜底用常量
116
- if (enableThinking) {
117
- const cachedToolSig = getToolSignature(sessionId, actualModelName);
118
- part.thoughtSignature = toolCall.thoughtSignature || cachedToolSig || TOOL_THOUGHT_SIGNATURE;
119
- }
120
-
121
- return part;
122
- }) : [];
123
-
124
- if (lastMessage?.role === "model" && hasToolCalls && !hasContent){
125
- lastMessage.parts.push(...antigravityTools)
126
- }else{
127
- const parts = [];
128
 
129
- // 对于启用思考的模型,在历史 assistant 消息中补一个思考块 + 签名块
130
- // 结构示例:
131
- // {
132
- // "role": "model",
133
- // "parts": [
134
- // { "text": "␈", "thought": true },
135
- // { "text": "␈", "thoughtSignature": "..." },
136
- // { "text": "正常回复..." }
137
- // ]
138
- // }
139
- if (enableThinking) {
140
- // 普通思维链签名:
141
- // 1. 优先使用消息自身携带的 thoughtSignature
142
- // 2. 其次使用缓存中的最新签名(同 session + model)
143
- // 3. 最后按模型类型选择内置兜底签名
144
- const cachedSig = getReasoningSignature(sessionId, actualModelName);
145
- const thoughtSignature = message.thoughtSignature || cachedSig || getThoughtSignatureForModel(actualModelName);
146
- // 默认思考内容不能是完全空字符串,否则上游会要求 thinking 字段
147
- // 这里用一个不可见的退格符作为占位,实际展示时等价于“空思考块”
148
- let reasoningText = '';
149
- if (typeof message.reasoning_content === 'string' && message.reasoning_content.length > 0) {
150
- reasoningText = message.reasoning_content;
151
- } else {
152
- reasoningText = ' '; // 退格符占位
153
- }
154
- parts.push({ text: reasoningText, thought: true });
155
- // 思维链签名占位,避免上游校验缺少签名字段
156
- parts.push({ text: ' ', thoughtSignature });
157
- }
158
 
159
- if (hasContent) parts.push({ text: message.content.trimEnd() });
160
- parts.push(...antigravityTools);
161
-
162
- antigravityMessages.push({
163
- role: "model",
164
- parts
165
- })
166
- }
167
- }
168
- function handleToolCall(message, antigravityMessages){
169
- // 从之前的 model 消息中找到对应的 functionCall name
170
- let functionName = '';
171
- for (let i = antigravityMessages.length - 1; i >= 0; i--) {
172
- if (antigravityMessages[i].role === 'model') {
173
- const parts = antigravityMessages[i].parts;
174
- for (const part of parts) {
175
- if (part.functionCall && part.functionCall.id === message.tool_call_id) {
176
- functionName = part.functionCall.name;
177
- break;
178
- }
179
- }
180
- if (functionName) break;
181
- }
182
- }
183
-
184
- const lastMessage = antigravityMessages[antigravityMessages.length - 1];
185
- const functionResponse = {
186
- functionResponse: {
187
- id: message.tool_call_id,
188
- name: functionName,
189
- response: {
190
- output: message.content
191
- }
192
- }
193
- };
194
-
195
- // 如果上一条消息是 user 且包含 functionResponse,则合并
196
- if (lastMessage?.role === "user" && lastMessage.parts.some(p => p.functionResponse)) {
197
- lastMessage.parts.push(functionResponse);
198
- } else {
199
- antigravityMessages.push({
200
- role: "user",
201
- parts: [functionResponse]
202
- });
203
  }
 
204
  }
205
- function openaiMessageToAntigravity(openaiMessages, enableThinking, actualModelName, sessionId){
206
- const antigravityMessages = [];
207
- for (const message of openaiMessages) {
208
- if (message.role === "user" || message.role === "system") {
209
- // system 消息作为 user 处理(开头的 system 已在 generateRequestBody 中过滤)
210
- const extracted = extractImagesFromContent(message.content);
211
- handleUserMessage(extracted, antigravityMessages);
212
- } else if (message.role === "assistant") {
213
- handleAssistantMessage(message, antigravityMessages, enableThinking, actualModelName, sessionId);
214
- } else if (message.role === "tool") {
215
- handleToolCall(message, antigravityMessages);
216
- }
217
- }
218
-
219
- return antigravityMessages;
220
  }
221
 
222
- /**
223
- * 从 OpenAI 消息中提取并合并 system 指令
224
- * 规则:
225
- * 1. SYSTEM_INSTRUCTION 作为基础 system,可为空
226
- * 2. 根据 useContextSystemPrompt 配置决定是否收集请求中的 system 消息
227
- * 3. 如果 useContextSystemPrompt=true,收集开头连续的 system 消息并合并
228
- * 4. 如果 useContextSystemPrompt=false,只使用基础 SYSTEM_INSTRUCTION
229
- */
230
- function extractSystemInstruction(openaiMessages) {
231
- const baseSystem = config.systemInstruction || '';
232
-
233
- // 如果不使用上下文 system,只返回基础 system
234
- if (!config.useContextSystemPrompt) {
235
- return baseSystem;
236
- }
237
-
238
- // 收集开头连续的 system 消息
239
- const systemTexts = [];
240
- for (const message of openaiMessages) {
241
- if (message.role === 'system') {
242
- const content = typeof message.content === 'string'
243
- ? message.content
244
- : (Array.isArray(message.content)
245
- ? message.content.filter(item => item.type === 'text').map(item => item.text).join('')
246
- : '');
247
- if (content.trim()) {
248
- systemTexts.push(content.trim());
249
- }
250
- } else {
251
- // 遇到非 system 消息就停止收集
252
- break;
253
- }
254
- }
255
-
256
- // 合并:基础 system + 用户的 system 消息
257
- const parts = [];
258
- if (baseSystem.trim()) {
259
- parts.push(baseSystem.trim());
260
- }
261
- if (systemTexts.length > 0) {
262
- parts.push(systemTexts.join('\n\n'));
263
- }
264
-
265
- return parts.join('\n\n');
266
  }
267
- // reasoning_effort 到 thinkingBudget 的映射
268
- const REASONING_EFFORT_MAP = {
269
- 'low': 1024,
270
- 'medium': 16000,
271
- 'high': 32000
272
- };
273
 
274
- function generateGenerationConfig(parameters, enableThinking, actualModelName){
275
- // 获取思考预算:
276
- // 1. 优先使用 thinking_budget(直接数值)
277
- // 2. 其次使用 reasoning_effort(OpenAI 格式:low/medium/high)
278
- // 3. 最后使用配置默认值或硬编码默认值
279
  const defaultThinkingBudget = config.defaults.thinking_budget ?? 1024;
280
-
281
  let thinkingBudget = 0;
282
  if (enableThinking) {
283
  if (parameters.thinking_budget !== undefined) {
@@ -288,199 +82,67 @@ function generateGenerationConfig(parameters, enableThinking, actualModelName){
288
  thinkingBudget = defaultThinkingBudget;
289
  }
290
  }
291
-
292
  const generationConfig = {
293
  topP: parameters.top_p ?? config.defaults.top_p,
294
  topK: parameters.top_k ?? config.defaults.top_k,
295
  temperature: parameters.temperature ?? config.defaults.temperature,
296
  candidateCount: 1,
297
  maxOutputTokens: parameters.max_tokens ?? config.defaults.max_tokens,
298
- stopSequences: [
299
- "<|user|>",
300
- "<|bot|>",
301
- "<|context_request|>",
302
- "<|endoftext|>",
303
- "<|end_of_turn|>"
304
- ],
305
  thinkingConfig: {
306
  includeThoughts: enableThinking,
307
  thinkingBudget: thinkingBudget
308
  }
309
- }
310
- if (enableThinking && actualModelName.includes("claude")){
311
  delete generationConfig.topP;
312
  }
313
- return generationConfig
314
  }
315
- // 不被 Google 工具参数 Schema 支持的字段,在这里统一过滤掉
316
- // 包括:
317
- // - JSON Schema 的元信息字段:$schema, additionalProperties
318
- // - 长度/数量约束:minLength, maxLength, minItems, maxItems, uniqueItems(不必传给后端)
319
- // - 严格上下界 / 常量:exclusiveMaximum, exclusiveMinimum, const(Google Schema 不支持)
320
- // - 组合约束:anyOf/oneOf/allOf 以及其非标准写法 any_of/one_of/all_of(为避免上游实现差异,这里一律去掉)
321
- const EXCLUDED_KEYS = new Set([
322
- '$schema',
323
- 'additionalProperties',
324
- 'minLength',
325
- 'maxLength',
326
- 'minItems',
327
- 'maxItems',
328
- 'uniqueItems',
329
- 'exclusiveMaximum',
330
- 'exclusiveMinimum',
331
- 'const',
332
- 'anyOf',
333
- 'oneOf',
334
- 'allOf',
335
- 'any_of',
336
- 'one_of',
337
- 'all_of'
338
- ]);
339
 
340
- function cleanParameters(obj) {
341
- if (!obj || typeof obj !== 'object') return obj;
342
-
343
- const cleaned = Array.isArray(obj) ? [] : {};
344
-
345
- for (const [key, value] of Object.entries(obj)) {
346
- if (EXCLUDED_KEYS.has(key)) continue;
347
- const cleanedValue = (value && typeof value === 'object') ? cleanParameters(value) : value;
348
- cleaned[key] = cleanedValue;
349
- }
350
-
351
- return cleaned;
352
- }
353
-
354
- function convertOpenAIToolsToAntigravity(openaiTools, sessionId, actualModelName){
355
- if (!openaiTools || openaiTools.length === 0) return [];
356
- return openaiTools.map((tool)=>{
357
- // 先清洗一遍参数,过滤/规范化不兼容字段
358
- const rawParams = tool.function?.parameters || {};
359
- const cleanedParams = cleanParameters(rawParams) || {};
360
-
361
- // 确保顶层是一个合法的 JSON Schema 对象
362
- // 如果用户没显式指定 type,则默认按 OpenAI 习惯设为 object
363
- if (cleanedParams.type === undefined) {
364
- cleanedParams.type = 'object';
365
- }
366
- // 对于 object 类型,至少保证有 properties 字段
367
- if (cleanedParams.type === 'object' && cleanedParams.properties === undefined) {
368
- cleanedParams.properties = {};
369
- }
370
-
371
- const originalName = tool.function?.name;
372
- const safeName = sanitizeToolName(originalName);
373
-
374
- // 仅当发生转换时才缓存映射
375
- if (sessionId && actualModelName && safeName !== originalName) {
376
- setToolNameMapping(sessionId, actualModelName, safeName, originalName);
377
- }
378
 
379
- return {
380
- functionDeclarations: [
381
- {
382
- name: safeName,
383
- description: tool.function.description,
384
- parameters: cleanedParams
385
- }
386
- ]
 
 
 
387
  }
388
- })
389
- }
390
-
391
- function modelMapping(modelName){
392
- if (modelName === "claude-sonnet-4-5-thinking"){
393
- return "claude-sonnet-4-5";
394
- } else if (modelName === "claude-opus-4-5"){
395
- return "claude-opus-4-5-thinking";
396
- } else if (modelName === "gemini-2.5-flash-thinking"){
397
- return "gemini-2.5-flash";
398
  }
399
- return modelName;
400
- }
401
 
402
- function isEnableThinking(modelName){
403
- // 只要模型名里包含 -thinking(例如 gemini-2.0-flash-thinking-exp),就认为支持思考配置
404
- return modelName.includes('-thinking') ||
405
- modelName === 'gemini-2.5-pro' ||
406
- modelName.startsWith('gemini-3-pro-') ||
407
- modelName === "rev19-uic3-1p" ||
408
- modelName === "gpt-oss-120b-medium";
409
  }
410
 
411
- function generateRequestBody(openaiMessages,modelName,parameters,openaiTools,token){
412
-
413
- const enableThinking = isEnableThinking(modelName);
414
- const actualModelName = modelMapping(modelName);
415
-
416
- // 提取合并后的 system 指令
417
- const mergedSystemInstruction = extractSystemInstruction(openaiMessages);
418
-
419
- // 根据 useContextSystemPrompt 配置决定如何处理 system 消息
420
- let startIndex = 0;
421
- if (config.useContextSystemPrompt) {
422
- // 过滤掉开头连续的 system 消息,避免重复作为 user 发送
423
- for (let i = 0; i < openaiMessages.length; i++) {
424
- if (openaiMessages[i].role === 'system') {
425
- startIndex = i + 1;
426
- } else {
427
- break;
428
- }
429
- }
430
- }
431
- const filteredMessages = openaiMessages.slice(startIndex);
432
-
433
- const requestBody = {
434
- project: token.projectId,
435
- requestId: generateRequestId(),
436
- request: {
437
- contents: openaiMessageToAntigravity(filteredMessages, enableThinking, actualModelName, token.sessionId),
438
- tools: convertOpenAIToolsToAntigravity(openaiTools, token.sessionId, actualModelName),
439
- toolConfig: {
440
- functionCallingConfig: {
441
- mode: "VALIDATED"
442
- }
443
- },
444
- generationConfig: generateGenerationConfig(parameters, enableThinking, actualModelName),
445
- sessionId: token.sessionId
446
- },
447
- model: actualModelName,
448
- userAgent: "antigravity"
449
- };
450
-
451
- // 只有当有 system 指令时才添加 systemInstruction 字段
452
- if (mergedSystemInstruction) {
453
- requestBody.request.systemInstruction = {
454
- role: "user",
455
- parts: [{ text: mergedSystemInstruction }]
456
- };
457
- }
458
-
459
- return requestBody;
460
- }
461
- /**
462
- * 将通用文本对话请求体转换为图片生成请求体
463
- * 统一配置 image_gen 所需字段,避免在各处手动删除/覆盖字段
464
- */
465
- function prepareImageRequest(requestBody) {
466
  if (!requestBody || !requestBody.request) return requestBody;
467
-
468
  requestBody.request.generationConfig = { candidateCount: 1 };
469
  requestBody.requestType = 'image_gen';
470
-
471
- // image_gen 模式下不需要这些字段
472
  delete requestBody.request.systemInstruction;
473
  delete requestBody.request.tools;
474
  delete requestBody.request.toolConfig;
475
-
476
  return requestBody;
477
  }
478
 
479
- function getDefaultIp(){
 
480
  const interfaces = os.networkInterfaces();
481
- for (const iface of Object.values(interfaces)){
482
- for (const inter of iface){
483
- if (inter.family === 'IPv4' && !inter.internal){
484
  return inter.address;
485
  }
486
  }
@@ -488,405 +150,8 @@ function getDefaultIp(){
488
  return '127.0.0.1';
489
  }
490
 
491
- function generateGeminiRequestBody(geminiBody, modelName, token){
492
- const enableThinking = isEnableThinking(modelName);
493
- const actualModelName = modelMapping(modelName);
494
-
495
- // 深拷贝 body,避免修改原始对象
496
- const request = JSON.parse(JSON.stringify(geminiBody));
497
- //console.log(JSON.stringify(request,null,2));
498
-
499
- // 处理 contents 中的 functionCall 和 functionResponse,确保 id 匹配
500
- if (request.contents && Array.isArray(request.contents)) {
501
- // 收集所有 functionCall 的 id(按顺序)
502
- const functionCallIds = [];
503
- request.contents.forEach(content => {
504
- if (content.role === 'model' && content.parts && Array.isArray(content.parts)) {
505
- content.parts.forEach(part => {
506
- if (part.functionCall) {
507
- if (!part.functionCall.id) {
508
- part.functionCall.id = `call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
509
- }
510
- functionCallIds.push(part.functionCall.id);
511
- }
512
- });
513
- }
514
- });
515
-
516
- // 为 functionResponse 匹配对应的 functionCall id
517
- let responseIndex = 0;
518
- request.contents.forEach(content => {
519
- if (content.role === 'user' && content.parts && Array.isArray(content.parts)) {
520
- content.parts.forEach(part => {
521
- if (part.functionResponse) {
522
- // 如果没有 id,按顺序匹配 functionCall 的 id
523
- if (!part.functionResponse.id && responseIndex < functionCallIds.length) {
524
- part.functionResponse.id = functionCallIds[responseIndex];
525
- responseIndex++;
526
- }
527
- }
528
- });
529
- }
530
- });
531
-
532
- // 处理思考模型的思维链
533
- if (enableThinking) {
534
- const cachedSig = getReasoningSignature(token.sessionId, actualModelName);
535
- const thoughtSignature = cachedSig || getThoughtSignatureForModel(actualModelName);
536
-
537
- request.contents.forEach(content => {
538
- if (content.role === 'model' && content.parts && Array.isArray(content.parts)) {
539
- // 检查是否已有思维链标记
540
- const hasThought = content.parts.some(p => p.thought === true);
541
- if (!hasThought) {
542
- // 在 parts 开头插入思维链占位符和签名
543
- content.parts.unshift(
544
- { text: ' ', thought: true },
545
- { text: ' ', thoughtSignature },
546
- );
547
- }
548
- }
549
- });
550
- }
551
- }
552
-
553
- // 确保 generationConfig 存在
554
- if (!request.generationConfig) {
555
- request.generationConfig = {};
556
- }
557
-
558
- // 处理思考模型配置
559
- if (enableThinking) {
560
- const defaultThinkingBudget = config.defaults.thinking_budget ?? 1024;
561
- // 如果没有 thinkingConfig,尝试注入
562
- if (!request.generationConfig.thinkingConfig) {
563
- request.generationConfig.thinkingConfig = {
564
- includeThoughts: true,
565
- thinkingBudget: defaultThinkingBudget
566
- };
567
- }
568
- }
569
-
570
- // 强制 candidateCount 为 1
571
- request.generationConfig.candidateCount = 1;
572
-
573
- // 注入 sessionId
574
- request.sessionId = token.sessionId;
575
- delete request.safetySettings;
576
-
577
- // 构造 Antigravity 请求体
578
- const requestBody = {
579
- project: token.projectId,
580
- requestId: generateRequestId(),
581
- request: request,
582
- model: actualModelName,
583
- userAgent: "antigravity"
584
- };
585
- //console.log(JSON.stringify(requestBody, null, 2))
586
-
587
- return requestBody;
588
- }
589
-
590
- // ==================== Claude API 转换函数 ====================
591
-
592
- /**
593
- * 从 Claude 消息内容中提取图片
594
- * Claude 格式: { type: "image", source: { type: "base64", media_type: "image/png", data: "..." } }
595
- */
596
- function extractImagesFromClaudeContent(content) {
597
- const result = { text: '', images: [] };
598
-
599
- if (typeof content === 'string') {
600
- result.text = content;
601
- return result;
602
- }
603
-
604
- if (Array.isArray(content)) {
605
- for (const item of content) {
606
- if (item.type === 'text') {
607
- result.text += item.text || '';
608
- } else if (item.type === 'image') {
609
- // Claude 格式的图片
610
- const source = item.source;
611
- if (source && source.type === 'base64' && source.data) {
612
- result.images.push({
613
- inlineData: {
614
- mimeType: source.media_type || 'image/png',
615
- data: source.data
616
- }
617
- });
618
- }
619
- }
620
- }
621
- }
622
-
623
- return result;
624
- }
625
-
626
- /**
627
- * 处理 Claude 用户消息
628
- */
629
- function handleClaudeUserMessage(extracted, antigravityMessages) {
630
- antigravityMessages.push({
631
- role: "user",
632
- parts: [
633
- { text: extracted.text },
634
- ...extracted.images
635
- ]
636
- });
637
- }
638
-
639
- /**
640
- * 处理 Claude 助手消息(包含 tool_use)
641
- */
642
- function handleClaudeAssistantMessage(message, antigravityMessages, enableThinking, actualModelName, sessionId) {
643
- const lastMessage = antigravityMessages[antigravityMessages.length - 1];
644
- const content = message.content;
645
-
646
- // 解析 content 数组
647
- let textContent = '';
648
- const toolCalls = [];
649
-
650
- if (typeof content === 'string') {
651
- textContent = content;
652
- } else if (Array.isArray(content)) {
653
- for (const item of content) {
654
- if (item.type === 'text') {
655
- textContent += item.text || '';
656
- } else if (item.type === 'tool_use') {
657
- // Claude 的 tool_use 格式
658
- const originalName = item.name;
659
- const safeName = sanitizeToolName(originalName);
660
-
661
- const part = {
662
- functionCall: {
663
- id: item.id,
664
- name: safeName,
665
- args: {
666
- query: JSON.stringify(item.input || {})
667
- }
668
- }
669
- };
670
-
671
- // 记录工具名映射
672
- if (sessionId && actualModelName && safeName !== originalName) {
673
- setToolNameMapping(sessionId, actualModelName, safeName, originalName);
674
- }
675
-
676
- toolCalls.push(part);
677
- }
678
- }
679
- }
680
-
681
- const hasToolCalls = toolCalls.length > 0;
682
- const hasContent = textContent && textContent.trim() !== '';
683
-
684
- if (lastMessage?.role === "model" && hasToolCalls && !hasContent) {
685
- lastMessage.parts.push(...toolCalls);
686
- } else {
687
- const parts = [];
688
-
689
- // 思维链处理(与 OpenAI 相同)
690
- if (enableThinking) {
691
- const cachedSig = getReasoningSignature(sessionId, actualModelName);
692
- const thoughtSignature = cachedSig || getThoughtSignatureForModel(actualModelName);
693
- parts.push({ text: ' ', thought: true });
694
- parts.push({ text: ' ', thoughtSignature });
695
- }
696
-
697
- if (hasContent) parts.push({ text: textContent.trimEnd() });
698
- parts.push(...toolCalls);
699
-
700
- antigravityMessages.push({
701
- role: "model",
702
- parts
703
- });
704
- }
705
- }
706
-
707
- /**
708
- * 处理 Claude tool_result 消息
709
- */
710
- function handleClaudeToolResult(message, antigravityMessages) {
711
- const content = message.content;
712
-
713
- if (!Array.isArray(content)) return;
714
-
715
- for (const item of content) {
716
- if (item.type !== 'tool_result') continue;
717
-
718
- const toolUseId = item.tool_use_id;
719
-
720
- // 从之前的 model 消息中找到对应的 functionCall name
721
- let functionName = '';
722
- for (let i = antigravityMessages.length - 1; i >= 0; i--) {
723
- if (antigravityMessages[i].role === 'model') {
724
- const parts = antigravityMessages[i].parts;
725
- for (const part of parts) {
726
- if (part.functionCall && part.functionCall.id === toolUseId) {
727
- functionName = part.functionCall.name;
728
- break;
729
- }
730
- }
731
- if (functionName) break;
732
- }
733
- }
734
-
735
- const lastMessage = antigravityMessages[antigravityMessages.length - 1];
736
-
737
- // 提取工具结果内容
738
- let resultContent = '';
739
- if (typeof item.content === 'string') {
740
- resultContent = item.content;
741
- } else if (Array.isArray(item.content)) {
742
- resultContent = item.content
743
- .filter(c => c.type === 'text')
744
- .map(c => c.text)
745
- .join('');
746
- }
747
-
748
- const functionResponse = {
749
- functionResponse: {
750
- id: toolUseId,
751
- name: functionName,
752
- response: {
753
- output: resultContent
754
- }
755
- }
756
- };
757
-
758
- // 如果上一条消息是 user 且包含 functionResponse,则合并
759
- if (lastMessage?.role === "user" && lastMessage.parts.some(p => p.functionResponse)) {
760
- lastMessage.parts.push(functionResponse);
761
- } else {
762
- antigravityMessages.push({
763
- role: "user",
764
- parts: [functionResponse]
765
- });
766
- }
767
- }
768
- }
769
-
770
- /**
771
- * 将 Claude 消息转换为 Antigravity 格式
772
- */
773
- function claudeMessageToAntigravity(claudeMessages, enableThinking, actualModelName, sessionId) {
774
- const antigravityMessages = [];
775
-
776
- for (const message of claudeMessages) {
777
- if (message.role === "user") {
778
- // 检查是否包含 tool_result
779
- const content = message.content;
780
- if (Array.isArray(content) && content.some(item => item.type === 'tool_result')) {
781
- handleClaudeToolResult(message, antigravityMessages);
782
- } else {
783
- const extracted = extractImagesFromClaudeContent(content);
784
- handleClaudeUserMessage(extracted, antigravityMessages);
785
- }
786
- } else if (message.role === "assistant") {
787
- handleClaudeAssistantMessage(message, antigravityMessages, enableThinking, actualModelName, sessionId);
788
- }
789
- }
790
-
791
- return antigravityMessages;
792
- }
793
-
794
- /**
795
- * 将 Claude 工具格式转换为 Antigravity 格式
796
- * Claude: { name, description, input_schema: {...} }
797
- * Antigravity/Gemini: { functionDeclarations: [{ name, description, parameters: {...} }] }
798
- */
799
- function convertClaudeToolsToAntigravity(claudeTools, sessionId, actualModelName) {
800
- if (!claudeTools || claudeTools.length === 0) return [];
801
-
802
- return claudeTools.map((tool) => {
803
- // 清洗参数
804
- const rawParams = tool.input_schema || {};
805
- const cleanedParams = cleanParameters(rawParams) || {};
806
-
807
- // 确保顶层是合法的 JSON Schema 对象
808
- if (cleanedParams.type === undefined) {
809
- cleanedParams.type = 'object';
810
- }
811
- if (cleanedParams.type === 'object' && cleanedParams.properties === undefined) {
812
- cleanedParams.properties = {};
813
- }
814
-
815
- const originalName = tool.name;
816
- const safeName = sanitizeToolName(originalName);
817
-
818
- // 缓存映射
819
- if (sessionId && actualModelName && safeName !== originalName) {
820
- setToolNameMapping(sessionId, actualModelName, safeName, originalName);
821
- }
822
-
823
- return {
824
- functionDeclarations: [
825
- {
826
- name: safeName,
827
- description: tool.description || '',
828
- parameters: cleanedParams
829
- }
830
- ]
831
- };
832
- });
833
- }
834
-
835
- /**
836
- * 生成 Claude 请求体并转换为 Antigravity 格式
837
- */
838
- function generateClaudeRequestBody(claudeMessages, modelName, parameters, claudeTools, systemPrompt, token) {
839
- const enableThinking = isEnableThinking(modelName);
840
- const actualModelName = modelMapping(modelName);
841
-
842
- // 合并 system 指令
843
- const baseSystem = config.systemInstruction || '';
844
- let mergedSystem = '';
845
-
846
- if (config.useContextSystemPrompt && systemPrompt) {
847
- const parts = [];
848
- if (baseSystem.trim()) parts.push(baseSystem.trim());
849
- if (systemPrompt.trim()) parts.push(systemPrompt.trim());
850
- mergedSystem = parts.join('\n\n');
851
- } else {
852
- mergedSystem = baseSystem;
853
- }
854
-
855
- const requestBody = {
856
- project: token.projectId,
857
- requestId: generateRequestId(),
858
- request: {
859
- contents: claudeMessageToAntigravity(claudeMessages, enableThinking, actualModelName, token.sessionId),
860
- tools: convertClaudeToolsToAntigravity(claudeTools, token.sessionId, actualModelName),
861
- toolConfig: {
862
- functionCallingConfig: {
863
- mode: "VALIDATED"
864
- }
865
- },
866
- generationConfig: generateGenerationConfig(parameters, enableThinking, actualModelName),
867
- sessionId: token.sessionId
868
- },
869
- model: actualModelName,
870
- userAgent: "antigravity"
871
- };
872
-
873
- // 只有当有 system 指令时才添加
874
- if (mergedSystem) {
875
- requestBody.request.systemInstruction = {
876
- role: "user",
877
- parts: [{ text: mergedSystem }]
878
- };
879
- }
880
- //console.log(JSON.stringify(requestBody, null, 2));
881
-
882
- return requestBody;
883
- }
884
-
885
- export{
886
- generateRequestId,
887
- generateRequestBody,
888
- generateGeminiRequestBody,
889
- generateClaudeRequestBody,
890
- prepareImageRequest,
891
- getDefaultIp
892
- }
 
1
+ // 通用工具函数
2
  import config from '../config/config.js';
 
 
3
  import os from 'os';
4
+ import { REASONING_EFFORT_MAP, DEFAULT_STOP_SEQUENCES } from '../constants/index.js';
 
5
 
6
+ // ==================== 签名常量 ====================
7
+ const CLAUDE_THOUGHT_SIGNATURE = 'RXFRRENrZ0lDaEFDR0FJcVFKV1Bvcy9GV20wSmtMV2FmWkFEbGF1ZTZzQTdRcFlTc1NvbklmemtSNFo4c1dqeitIRHBOYW9hS2NYTE1TeTF3bjh2T1RHdE1KVjVuYUNQclZ5cm9DMFNETHk4M0hOSWsrTG1aRUhNZ3hvTTl0ZEpXUDl6UUMzOExxc2ZJakI0UkkxWE1mdWJ1VDQrZnY0Znp0VEoyTlhtMjZKL2daYi9HL1gwcmR4b2x0VE54empLemtLcEp0ZXRia2plb3NBcWlRSWlXUHloMGhVVTk1dHNha1dyNDVWNUo3MTJjZDNxdHQ5Z0dkbjdFaFk4dUllUC9CcThVY2VZZC9YbFpYbDc2bHpEbmdzL2lDZXlNY3NuZXdQMjZBTDRaQzJReXdibVQzbXlSZmpld3ZSaUxxOWR1TVNidHIxYXRtYTJ0U1JIRjI0Z0JwUnpadE1RTmoyMjR4bTZVNUdRNXlOSWVzUXNFNmJzRGNSV0RTMGFVOEZERExybmhVQWZQT2JYMG5lTGR1QnU1VGZOWW9NZglRbTgyUHVqVE1xaTlmN0t2QmJEUUdCeXdyVXR2eUNnTEFHNHNqeWluZDRCOEg3N2ZJamt5blI3Q3ZpQzlIOTVxSENVTCt3K3JzMmsvV0sxNlVsbGlTK0pET3UxWXpPMWRPOUp3V3hEMHd5ZVU0a0Y5MjIxaUE5Z2lUd2djZXhSU2c4TWJVMm1NSjJlaGdlY3g0YjJ3QloxR0FFPQ==';
 
 
8
  const GEMINI_THOUGHT_SIGNATURE = 'EqAHCp0HAXLI2nygRbdzD4Vgzxxi7tbM87zIRkNgPLqTj+Jxv9mY8Q0G87DzbTtvsIFhWB0RZMoEK6ntm5GmUe6ADtxHk4zgHUs/FKqTu8tzUdPRDrKn3KCAtFW4LJqijZoFxNKMyQRmlgPUX4tGYE7pllD77UK6SjCwKhKZoSVZLMiPXP9YFktbida1Q5upXMrzG1t8abPmpFo983T/rgWlNqJp+Fb+bsoH0zuSpmU4cPKO3LIGsxBhvRhM/xydahZD+VpEX7TEJAN58z1RomFyx9u0IR7ukwZr2UyoNA+uj8OChUDFupQsVwbm3XE1UAt22BGvfYIyyZ42fxgOgsFFY+AZ72AOufcmZb/8vIw3uEUgxHczdl+NGLuS4Hsy/AAntdcH9sojSMF3qTf+ZK1FMav23SPxUBtU5T9HCEkKqQWRnMsVGYV1pupFisWo85hRLDTUipxVy9ug1hN8JBYBNmGLf8KtWLhVp7Z11PIAZj3C6HzoVyiVeuiorwNrn0ZaaXNe+y5LHuDF0DNZhrIfnXByq6grLLSAv4fTLeCJvfGzTWWyZDMbVXNx1HgumKq8calP9wv33t0hfEaOlcmfGIyh1J/N+rOGR0WXcuZZP5/VsFR44S2ncpwTPT+MmR0PsjocDenRY5m/X4EXbGGkZ+cfPnWoA64bn3eLeJTwxl9W1ZbmYS6kjpRGUMxExgRNOzWoGISddHCLcQvN7o50K8SF5k97rxiS5q4rqDmqgRPXzQTQnZyoL3dCxScX9cvLSjNCZDcotonDBAWHfkXZ0/EmFiONQcLJdANtAjwoA44Mbn50gubrTsNd7d0Rm/hbNEh/ZceUalV5MMcl6tJtahCJoybQMsnjWuBXl7cXiKmqAvxTDxIaBgQBYAo4FrbV4zQv35zlol+O3YiyjJn/U0oBeO5pEcH1d0vnLgYP71jZVY2FjWRKnDR9aw4JhiuqAa+i0tupkBy+H4/SVwHADFQq6wcsL8qvXlwktJL9MIAoaXDkIssw6gKE9EuGd7bSO9f+sA8CZ0I8LfJ3jcHUsE/3qd4pFrn5RaET56+1p8ZHZDDUQ0p1okApUCCYsC2WuL6O9P4fcg3yitAA/AfUUNjHKANE+ANneQ0efMG7fx9bvI+iLbXgPupApoov24JRkmhHsrJiu9bp+G/pImd2PNv7ArunJ6upl0VAUWtRyLWyGfdl6etGuY8vVJ7JdWEQ8aWzRK3g6e+8YmDtP5DAfw==';
9
+ const CLAUDE_TOOL_SIGNATURE = 'RXVNQkNrZ0lDaEFDR0FJcVFLZGsvMnlyR0VTbmNKMXEyTFIrcWwyY2ozeHhoZHRPb0VOYWJ2VjZMSnE2MlBhcEQrUWdIM3ZWeHBBUG9rbGN1aXhEbXprZTcvcGlkbWRDQWs5MWcrTVNERnRhbWJFOU1vZWZGc1pWSGhvTUxsMXVLUzRoT3BIaWwyeXBJakNYa05EVElMWS9talprdUxvRjFtMmw5dnkrbENhSDNNM3BYNTM0K1lRZ0NaWTQvSUNmOXo4SkhZVzU2Sm1WcTZBcVNRUURBRGVMV1BQRXk1Q0JsS0dCZXlNdHp2NGRJQVlGbDFSMDBXNGhqNHNiSWNKeGY0UGZVQTBIeE1mZjJEYU5BRXdrWUJ4MmNzRFMrZGM1N1hnUlVNblpkZ0hTVHVNaGdod1lBUT09';
10
+ const GEMINI_TOOL_SIGNATURE = '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';
 
 
11
 
12
+ export function getThoughtSignatureForModel(actualModelName) {
13
+ if (!actualModelName) return CLAUDE_THOUGHT_SIGNATURE;
14
  const lower = actualModelName.toLowerCase();
15
  if (lower.includes('claude')) return CLAUDE_THOUGHT_SIGNATURE;
16
  if (lower.includes('gemini')) return GEMINI_THOUGHT_SIGNATURE;
17
+ return CLAUDE_THOUGHT_SIGNATURE;
18
  }
19
 
20
+ export function getToolSignatureForModel(actualModelName) {
21
+ if (!actualModelName) return CLAUDE_TOOL_SIGNATURE;
22
+ const lower = actualModelName.toLowerCase();
23
+ if (lower.includes('claude')) return CLAUDE_TOOL_SIGNATURE;
24
+ if (lower.includes('gemini')) return GEMINI_TOOL_SIGNATURE;
25
+ return CLAUDE_TOOL_SIGNATURE;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
27
+
28
+ // ==================== 工具名称规范化 ====================
29
+ export function sanitizeToolName(name) {
30
+ if (!name || typeof name !== 'string') return 'tool';
 
 
31
  let cleaned = name.replace(/[^a-zA-Z0-9_-]/g, '_');
 
32
  cleaned = cleaned.replace(/^_+|_+$/g, '');
33
+ if (!cleaned) cleaned = 'tool';
34
+ if (cleaned.length > 128) cleaned = cleaned.slice(0, 128);
 
 
 
 
 
35
  return cleaned;
36
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
+ // ==================== 参数清理 ====================
39
+ const EXCLUDED_KEYS = new Set([
40
+ '$schema', 'additionalProperties', 'minLength', 'maxLength',
41
+ 'minItems', 'maxItems', 'uniqueItems', 'exclusiveMaximum',
42
+ 'exclusiveMinimum', 'const', 'anyOf', 'oneOf', 'allOf',
43
+ 'any_of', 'one_of', 'all_of'
44
+ ]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
+ export function cleanParameters(obj) {
47
+ if (!obj || typeof obj !== 'object') return obj;
48
+ const cleaned = Array.isArray(obj) ? [] : {};
49
+ for (const [key, value] of Object.entries(obj)) {
50
+ if (EXCLUDED_KEYS.has(key)) continue;
51
+ cleaned[key] = (value && typeof value === 'object') ? cleanParameters(value) : value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  }
53
+ return cleaned;
54
  }
55
+
56
+ // ==================== 模型映射 ====================
57
+ export function modelMapping(modelName) {
58
+ if (modelName === 'claude-sonnet-4-5-thinking') return 'claude-sonnet-4-5';
59
+ if (modelName === 'claude-opus-4-5') return 'claude-opus-4-5-thinking';
60
+ if (modelName === 'gemini-2.5-flash-thinking') return 'gemini-2.5-flash';
61
+ return modelName;
 
 
 
 
 
 
 
 
62
  }
63
 
64
+ export function isEnableThinking(modelName) {
65
+ return modelName.includes('-thinking') ||
66
+ modelName === 'gemini-2.5-pro' ||
67
+ modelName.startsWith('gemini-3-pro-') ||
68
+ modelName === 'rev19-uic3-1p' ||
69
+ modelName === 'gpt-oss-120b-medium';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  }
 
 
 
 
 
 
71
 
72
+ // ==================== 生成配置 ====================
73
+ export function generateGenerationConfig(parameters, enableThinking, actualModelName) {
 
 
 
74
  const defaultThinkingBudget = config.defaults.thinking_budget ?? 1024;
 
75
  let thinkingBudget = 0;
76
  if (enableThinking) {
77
  if (parameters.thinking_budget !== undefined) {
 
82
  thinkingBudget = defaultThinkingBudget;
83
  }
84
  }
85
+
86
  const generationConfig = {
87
  topP: parameters.top_p ?? config.defaults.top_p,
88
  topK: parameters.top_k ?? config.defaults.top_k,
89
  temperature: parameters.temperature ?? config.defaults.temperature,
90
  candidateCount: 1,
91
  maxOutputTokens: parameters.max_tokens ?? config.defaults.max_tokens,
92
+ stopSequences: DEFAULT_STOP_SEQUENCES,
 
 
 
 
 
 
93
  thinkingConfig: {
94
  includeThoughts: enableThinking,
95
  thinkingBudget: thinkingBudget
96
  }
97
+ };
98
+ if (enableThinking && actualModelName.includes('claude')) {
99
  delete generationConfig.topP;
100
  }
101
+ return generationConfig;
102
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
+ // ==================== System 指令提取 ====================
105
+ export function extractSystemInstruction(openaiMessages) {
106
+ const baseSystem = config.systemInstruction || '';
107
+ if (!config.useContextSystemPrompt) return baseSystem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
+ const systemTexts = [];
110
+ for (const message of openaiMessages) {
111
+ if (message.role === 'system') {
112
+ const content = typeof message.content === 'string'
113
+ ? message.content
114
+ : (Array.isArray(message.content)
115
+ ? message.content.filter(item => item.type === 'text').map(item => item.text).join('')
116
+ : '');
117
+ if (content.trim()) systemTexts.push(content.trim());
118
+ } else {
119
+ break;
120
  }
 
 
 
 
 
 
 
 
 
 
121
  }
 
 
122
 
123
+ const parts = [];
124
+ if (baseSystem.trim()) parts.push(baseSystem.trim());
125
+ if (systemTexts.length > 0) parts.push(systemTexts.join('\n\n'));
126
+ return parts.join('\n\n');
 
 
 
127
  }
128
 
129
+ // ==================== 图片请求准备 ====================
130
+ export function prepareImageRequest(requestBody) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  if (!requestBody || !requestBody.request) return requestBody;
 
132
  requestBody.request.generationConfig = { candidateCount: 1 };
133
  requestBody.requestType = 'image_gen';
 
 
134
  delete requestBody.request.systemInstruction;
135
  delete requestBody.request.tools;
136
  delete requestBody.request.toolConfig;
 
137
  return requestBody;
138
  }
139
 
140
+ // ==================== 其他工具 ====================
141
+ export function getDefaultIp() {
142
  const interfaces = os.networkInterfaces();
143
+ for (const iface of Object.values(interfaces)) {
144
+ for (const inter of iface) {
145
+ if (inter.family === 'IPv4' && !inter.internal) {
146
  return inter.address;
147
  }
148
  }
 
150
  return '127.0.0.1';
151
  }
152
 
153
+ // 重导出主要函数
154
+ export { generateRequestId } from './idGenerator.js';
155
+ export { generateRequestBody } from './converters/openai.js';
156
+ export { generateClaudeRequestBody } from './converters/claude.js';
157
+ export { generateGeminiRequestBody } from './converters/gemini.js';