From ade3f57d4ca1eedeffa398b30f94d7cf3e46ff29 Mon Sep 17 00:00:00 2001 From: Anurag Saxena Date: Thu, 26 Mar 2026 23:40:29 -0400 Subject: [PATCH] fix: sanitize Gemini function names to meet API requirements (closes #369) (#403) --- .../translator/request/openai-to-gemini.js | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/open-sse/translator/request/openai-to-gemini.js b/open-sse/translator/request/openai-to-gemini.js index 03661bd..bfa84d1 100644 --- a/open-sse/translator/request/openai-to-gemini.js +++ b/open-sse/translator/request/openai-to-gemini.js @@ -20,6 +20,21 @@ import { } from "../helpers/geminiHelper.js"; import { deriveSessionId } from "../../utils/sessionManager.js"; +// Sanitize function names for Gemini API. +// Gemini requires: starts with [a-zA-Z_], followed by [a-zA-Z0-9_.:\-], max 64 chars. +// Replace any invalid character with '_' and truncate to 64. +function sanitizeGeminiFunctionName(name) { + if (!name) return "_unknown"; + // Replace any char not in [a-zA-Z0-9_.:\-] with '_' + let sanitized = name.replace(/[^a-zA-Z0-9_.:\-]/g, "_"); + // First char must be letter or underscore + if (!/^[a-zA-Z_]/.test(sanitized)) { + sanitized = "_" + sanitized; + } + // Truncate to 64 chars + return sanitized.substring(0, 64); +} + // Core: Convert OpenAI request to Gemini format (base for all variants) function openaiToGeminiBase(model, body, stream) { const result = { @@ -116,7 +131,7 @@ function openaiToGeminiBase(model, body, stream) { thoughtSignature: DEFAULT_THINKING_GEMINI_SIGNATURE, functionCall: { id: tc.id, - name: tc.function.name, + name: sanitizeGeminiFunctionName(tc.function.name), args: args } }); @@ -156,7 +171,7 @@ function openaiToGeminiBase(model, body, stream) { toolParts.push({ functionResponse: { id: fid, - name: name, + name: sanitizeGeminiFunctionName(name), response: { result: parsedResp } } }); @@ -180,7 +195,7 @@ function openaiToGeminiBase(model, body, stream) { if (t.name && t.input_schema) { const cleanedSchema = cleanJSONSchemaForAntigravity(structuredClone(t.input_schema || { type: "object", properties: {} })); functionDeclarations.push({ - name: t.name, + name: sanitizeGeminiFunctionName(t.name), description: t.description || "", parameters: cleanedSchema }); @@ -190,7 +205,7 @@ function openaiToGeminiBase(model, body, stream) { const fn = t.function; const cleanedSchema = cleanJSONSchemaForAntigravity(structuredClone(fn.parameters || { type: "object", properties: {} })); functionDeclarations.push({ - name: fn.name, + name: sanitizeGeminiFunctionName(fn.name), description: fn.description || "", parameters: cleanedSchema }); @@ -372,7 +387,7 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu if (tool.name && tool.input_schema) { const cleanedSchema = cleanJSONSchemaForAntigravity(tool.input_schema); functionDeclarations.push({ - name: tool.name, + name: sanitizeGeminiFunctionName(tool.name), description: tool.description || "", parameters: cleanedSchema });