Fixed gemini error Function call is missing a thought_signature in functionCall parts (#426)
* docs(docs): add agent guidance and repository instructions - Created `AGENTS.md` and `CLAUDE.md` to provide high-signal guidance for AI agents and developers working in the repository. - Outlined critical developer commands for building, testing, and running diagnostics using `bun`. - Documented the repository architecture, source entrypoints, and core service logic. - Defined framework-specific quirks, including module stubbing for internal modules and macro versioning. - Established style and workflow guidelines regarding telemetry, environment variables, and security scan requirements. * feat(api): support gemini thought signatures in openai shim - Added `isGeminiMode` utility to detect Gemini backends via `CLAUDE_CODE_USE_GEMINI` or `OPENAI_BASE_URL`. - Updated `convertMessages` to extract `thought_signature` from thinking blocks and inject them into tool calls. - Implemented a fallback mechanism that provides a `skip_thought_signature_validator` string to avoid 400 validation errors when a signature is missing. - Enhanced `openaiStreamToAnthropic` and `OpenAIShimMessages` to correctly preserve and pass through Gemini-specific metadata in `extra_content`. * refactor(api): improve gemini metadata handling and remove redundant docs - Updated `src/services/api/openaiShim.ts` to merge existing `google`-specific metadata within `extra_content` instead of overwriting it. - Simplified the `thought_signature` assignment logic to use a fallback value of `skip_thought_signature_validator` when no signature is provided. - Deleted `AGENTS.md` and `CLAUDE.md` files to eliminate redundant agent guidance documentation. * fix(api): propagate gemini thought signatures to all parallel tool calls - Removed the index constraint when assigning the `signature` from a `thinkingBlock` to tool calls in `openaiShim.ts`. - Ensured that the `thought_signature` is applied to every tool call in a parallel set, rather than just the first one. - Aligned the shim with Gemini API requirements, which mandate that the same signature must be present on every replayed function call part within an assistant turn.
This commit is contained in:
@@ -197,6 +197,14 @@ function convertContentBlocks(
|
||||
return parts
|
||||
}
|
||||
|
||||
function isGeminiMode(): boolean {
|
||||
return (
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI) ||
|
||||
(process.env.OPENAI_BASE_URL?.includes('generativelanguage.googleapis.com') ??
|
||||
false)
|
||||
)
|
||||
}
|
||||
|
||||
function convertMessages(
|
||||
messages: Array<{ role: string; message?: { role?: string; content?: unknown }; content?: unknown }>,
|
||||
system: unknown,
|
||||
@@ -248,6 +256,7 @@ function convertMessages(
|
||||
// Check for tool_use blocks
|
||||
if (Array.isArray(content)) {
|
||||
const toolUses = content.filter((b: { type?: string }) => b.type === 'tool_use')
|
||||
const thinkingBlock = content.find((b: { type?: string }) => b.type === 'thinking')
|
||||
const textContent = content.filter(
|
||||
(b: { type?: string }) => b.type !== 'tool_use' && b.type !== 'thinking',
|
||||
)
|
||||
@@ -267,18 +276,46 @@ function convertMessages(
|
||||
name?: string
|
||||
input?: unknown
|
||||
extra_content?: Record<string, unknown>
|
||||
}) => ({
|
||||
id: tu.id ?? `call_${crypto.randomUUID().replace(/-/g, '')}`,
|
||||
type: 'function' as const,
|
||||
function: {
|
||||
name: tu.name ?? 'unknown',
|
||||
arguments:
|
||||
typeof tu.input === 'string'
|
||||
? tu.input
|
||||
: JSON.stringify(tu.input ?? {}),
|
||||
},
|
||||
...(tu.extra_content ? { extra_content: tu.extra_content } : {}),
|
||||
}),
|
||||
signature?: string
|
||||
}, index) => {
|
||||
const toolCall: NonNullable<OpenAIMessage['tool_calls']>[number] = {
|
||||
id: tu.id ?? `call_${crypto.randomUUID().replace(/-/g, '')}`,
|
||||
type: 'function' as const,
|
||||
function: {
|
||||
name: tu.name ?? 'unknown',
|
||||
arguments:
|
||||
typeof tu.input === 'string'
|
||||
? tu.input
|
||||
: JSON.stringify(tu.input ?? {}),
|
||||
},
|
||||
}
|
||||
|
||||
// Preserve existing extra_content if present
|
||||
if (tu.extra_content) {
|
||||
toolCall.extra_content = { ...tu.extra_content }
|
||||
}
|
||||
|
||||
// Handle Gemini thought_signature
|
||||
if (isGeminiMode()) {
|
||||
// If the model provided a signature in the tool_use block itself (e.g. from a previous Turn/Step)
|
||||
// Use thinkingBlock.signature for ALL tool calls in the same assistant turn if available.
|
||||
// The API requires the same signature on every replayed function call part in a parallel set.
|
||||
const signature = tu.signature ?? (thinkingBlock as any)?.signature
|
||||
|
||||
// Merge into existing google-specific metadata if present
|
||||
const existingGoogle = (toolCall.extra_content?.google as Record<string, unknown>) ?? {}
|
||||
|
||||
toolCall.extra_content = {
|
||||
...toolCall.extra_content,
|
||||
google: {
|
||||
...existingGoogle,
|
||||
thought_signature: signature ?? "skip_thought_signature_validator"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return toolCall
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -397,7 +434,7 @@ function normalizeSchemaForOpenAI(
|
||||
function convertTools(
|
||||
tools: Array<{ name: string; description?: string; input_schema?: Record<string, unknown> }>,
|
||||
): OpenAITool[] {
|
||||
const isGemini = isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI)
|
||||
const isGemini = isGeminiMode()
|
||||
|
||||
return tools
|
||||
.filter(t => t.name !== 'ToolSearchTool') // Not relevant for OpenAI
|
||||
@@ -593,6 +630,13 @@ async function* openaiStreamToAnthropic(
|
||||
name: tc.function.name,
|
||||
input: {},
|
||||
...(tc.extra_content ? { extra_content: tc.extra_content } : {}),
|
||||
// Extract Gemini signature from extra_content
|
||||
...((tc.extra_content?.google as any)?.thought_signature
|
||||
? {
|
||||
signature: (tc.extra_content.google as any)
|
||||
.thought_signature,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
}
|
||||
contentBlockIndex++
|
||||
@@ -930,7 +974,7 @@ class OpenAIShimMessages {
|
||||
...(options?.headers ?? {}),
|
||||
}
|
||||
|
||||
const isGemini = isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI)
|
||||
const isGemini = isGeminiMode()
|
||||
const apiKey =
|
||||
this.providerOverride?.apiKey ?? process.env.OPENAI_API_KEY ?? ''
|
||||
// Detect Azure endpoints by hostname (not raw URL) to prevent bypass via
|
||||
@@ -1099,6 +1143,10 @@ class OpenAIShimMessages {
|
||||
name: tc.function.name,
|
||||
input,
|
||||
...(tc.extra_content ? { extra_content: tc.extra_content } : {}),
|
||||
// Extract Gemini signature from extra_content
|
||||
...((tc.extra_content?.google as any)?.thought_signature
|
||||
? { signature: (tc.extra_content.google as any).thought_signature }
|
||||
: {}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user