- Add message-converter.ts for OpenAI messages to Gemini contents conversion - Use SKIP_THOUGHT_SIGNATURE_VALIDATOR as default signature (CLIProxyAPI approach) - Restore loadCodeAssist API call to get user's actual project ID - Improve debug logging for troubleshooting - Fix tool normalization edge cases 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
244 lines
6.3 KiB
TypeScript
244 lines
6.3 KiB
TypeScript
/**
|
|
* Antigravity Tool Normalization
|
|
* Converts tools between OpenAI and Gemini formats.
|
|
*
|
|
* OpenAI format:
|
|
* { "type": "function", "function": { "name": "x", "description": "...", "parameters": {...} } }
|
|
*
|
|
* Gemini format:
|
|
* { "functionDeclarations": [{ "name": "x", "description": "...", "parameters": {...} }] }
|
|
*
|
|
* Note: This is for Gemini models ONLY. Claude models are not supported via Antigravity.
|
|
*/
|
|
|
|
/**
|
|
* OpenAI function tool format
|
|
*/
|
|
export interface OpenAITool {
|
|
type: string
|
|
function?: {
|
|
name: string
|
|
description?: string
|
|
parameters?: Record<string, unknown>
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gemini function declaration format
|
|
*/
|
|
export interface GeminiFunctionDeclaration {
|
|
name: string
|
|
description?: string
|
|
parameters?: Record<string, unknown>
|
|
}
|
|
|
|
/**
|
|
* Gemini tools format (array of functionDeclarations)
|
|
*/
|
|
export interface GeminiTools {
|
|
functionDeclarations: GeminiFunctionDeclaration[]
|
|
}
|
|
|
|
/**
|
|
* OpenAI tool call in response
|
|
*/
|
|
export interface OpenAIToolCall {
|
|
id: string
|
|
type: "function"
|
|
function: {
|
|
name: string
|
|
arguments: string
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gemini function call in response
|
|
*/
|
|
export interface GeminiFunctionCall {
|
|
name: string
|
|
args: Record<string, unknown>
|
|
}
|
|
|
|
/**
|
|
* Gemini function response format
|
|
*/
|
|
export interface GeminiFunctionResponse {
|
|
name: string
|
|
response: Record<string, unknown>
|
|
}
|
|
|
|
/**
|
|
* Gemini tool result containing function calls
|
|
*/
|
|
export interface GeminiToolResult {
|
|
functionCall?: GeminiFunctionCall
|
|
functionResponse?: GeminiFunctionResponse
|
|
}
|
|
|
|
/**
|
|
* Normalize OpenAI-format tools to Gemini format.
|
|
* Converts an array of OpenAI tools to Gemini's functionDeclarations format.
|
|
*
|
|
* - Handles `function` type tools with name, description, parameters
|
|
* - Logs warning for unsupported tool types (does NOT silently drop them)
|
|
* - Creates a single object with functionDeclarations array
|
|
*
|
|
* @param tools - Array of OpenAI-format tools
|
|
* @returns Gemini-format tools object with functionDeclarations, or undefined if no valid tools
|
|
*/
|
|
export function normalizeToolsForGemini(
|
|
tools: OpenAITool[]
|
|
): GeminiTools | undefined {
|
|
if (!tools || tools.length === 0) {
|
|
return undefined
|
|
}
|
|
|
|
const functionDeclarations: GeminiFunctionDeclaration[] = []
|
|
|
|
for (const tool of tools) {
|
|
if (!tool || typeof tool !== "object") {
|
|
continue
|
|
}
|
|
|
|
const toolType = tool.type ?? "function"
|
|
if (toolType === "function" && tool.function) {
|
|
const declaration: GeminiFunctionDeclaration = {
|
|
name: tool.function.name,
|
|
}
|
|
|
|
if (tool.function.description) {
|
|
declaration.description = tool.function.description
|
|
}
|
|
|
|
if (tool.function.parameters) {
|
|
declaration.parameters = tool.function.parameters
|
|
} else {
|
|
declaration.parameters = { type: "object", properties: {} }
|
|
}
|
|
|
|
functionDeclarations.push(declaration)
|
|
} else if (toolType !== "function" && process.env.ANTIGRAVITY_DEBUG === "1") {
|
|
console.warn(
|
|
`[antigravity-tools] Unsupported tool type: "${toolType}". Tool will be skipped.`
|
|
)
|
|
}
|
|
}
|
|
|
|
// Return undefined if no valid function declarations
|
|
if (functionDeclarations.length === 0) {
|
|
return undefined
|
|
}
|
|
|
|
return { functionDeclarations }
|
|
}
|
|
|
|
/**
|
|
* Convert Gemini tool results (functionCall) back to OpenAI tool_call format.
|
|
* Handles both functionCall (request) and functionResponse (result) formats.
|
|
*
|
|
* Gemini functionCall format:
|
|
* { "name": "tool_name", "args": { ... } }
|
|
*
|
|
* OpenAI tool_call format:
|
|
* { "id": "call_xxx", "type": "function", "function": { "name": "tool_name", "arguments": "..." } }
|
|
*
|
|
* @param results - Array of Gemini tool results containing functionCall or functionResponse
|
|
* @returns Array of OpenAI-format tool calls
|
|
*/
|
|
export function normalizeToolResultsFromGemini(
|
|
results: GeminiToolResult[]
|
|
): OpenAIToolCall[] {
|
|
if (!results || results.length === 0) {
|
|
return []
|
|
}
|
|
|
|
const toolCalls: OpenAIToolCall[] = []
|
|
let callCounter = 0
|
|
|
|
for (const result of results) {
|
|
// Handle functionCall (tool invocation from model)
|
|
if (result.functionCall) {
|
|
callCounter++
|
|
const toolCall: OpenAIToolCall = {
|
|
id: `call_${Date.now()}_${callCounter}`,
|
|
type: "function",
|
|
function: {
|
|
name: result.functionCall.name,
|
|
arguments: JSON.stringify(result.functionCall.args ?? {}),
|
|
},
|
|
}
|
|
toolCalls.push(toolCall)
|
|
}
|
|
}
|
|
|
|
return toolCalls
|
|
}
|
|
|
|
/**
|
|
* Convert a single Gemini functionCall to OpenAI tool_call format.
|
|
* Useful for streaming responses where each chunk may contain a function call.
|
|
*
|
|
* @param functionCall - Gemini function call
|
|
* @param id - Optional tool call ID (generates one if not provided)
|
|
* @returns OpenAI-format tool call
|
|
*/
|
|
export function convertFunctionCallToToolCall(
|
|
functionCall: GeminiFunctionCall,
|
|
id?: string
|
|
): OpenAIToolCall {
|
|
return {
|
|
id: id ?? `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
type: "function",
|
|
function: {
|
|
name: functionCall.name,
|
|
arguments: JSON.stringify(functionCall.args ?? {}),
|
|
},
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a tool array contains any function-type tools.
|
|
*
|
|
* @param tools - Array of OpenAI-format tools
|
|
* @returns true if there are function tools to normalize
|
|
*/
|
|
export function hasFunctionTools(tools: OpenAITool[]): boolean {
|
|
if (!tools || tools.length === 0) {
|
|
return false
|
|
}
|
|
|
|
return tools.some((tool) => tool.type === "function" && tool.function)
|
|
}
|
|
|
|
/**
|
|
* Extract function declarations from already-normalized Gemini tools.
|
|
* Useful when tools may already be in Gemini format.
|
|
*
|
|
* @param tools - Tools that may be in Gemini or OpenAI format
|
|
* @returns Array of function declarations
|
|
*/
|
|
export function extractFunctionDeclarations(
|
|
tools: unknown
|
|
): GeminiFunctionDeclaration[] {
|
|
if (!tools || typeof tools !== "object") {
|
|
return []
|
|
}
|
|
|
|
// Check if already in Gemini format
|
|
const geminiTools = tools as Record<string, unknown>
|
|
if (
|
|
Array.isArray(geminiTools.functionDeclarations) &&
|
|
geminiTools.functionDeclarations.length > 0
|
|
) {
|
|
return geminiTools.functionDeclarations as GeminiFunctionDeclaration[]
|
|
}
|
|
|
|
// Check if it's an array of OpenAI tools
|
|
if (Array.isArray(tools)) {
|
|
const normalized = normalizeToolsForGemini(tools as OpenAITool[])
|
|
return normalized?.functionDeclarations ?? []
|
|
}
|
|
|
|
return []
|
|
}
|