fix(provider): validate Base64 thought signatures and strip foreign reasoning JSON (DeepSeek/OpenAI)

This commit is contained in:
Matteo Benedetto
2026-08-21 17:10:49 +02:00
parent d42fa69e1a
commit 99a7ff4b1a
+17 -5
View File
@@ -1871,6 +1871,15 @@ function antgWithQueue<T>(fn: () => Promise<T>): Promise<T> {
return p;
}
function isValidGeminiThoughtSignature(sig: any): sig is string {
if (typeof sig !== "string" || !sig.trim()) return false;
// I payload di reasoning di altri provider (es. DeepSeek / OpenAI) sono stringhe JSON come {"id":"rs_..."}
// che Google rifiuta con errore di decodifica Base64 (TYPE_BYTES).
if (sig.startsWith("{") || sig.startsWith("[")) return false;
if (!/^[A-Za-z0-9+/=_-]+$/.test(sig)) return false;
return sig.length >= 16;
}
function antgBuildGeminiRequest(model: Model<Api>, context: Context, options?: SimpleStreamOptions): any {
const contents: any[] = [];
const push = (role: "user" | "model", part: any) => {
@@ -1892,15 +1901,18 @@ function antgBuildGeminiRequest(model: Model<Api>, context: Context, options?: S
const parts: any[] = [];
for (const b of msg.content) {
if (b.type === "text") {
const sig = b.textSignature || (isSame ? (b as any).thoughtSignature : undefined);
const rawSig = b.textSignature || (isSame ? (b as any).thoughtSignature : undefined);
const sig = isValidGeminiThoughtSignature(rawSig) ? rawSig : undefined;
if ((!b.text || !b.text.trim()) && !sig) continue;
parts.push({ text: b.text, ...(sig ? { thoughtSignature: sig } : {}) });
} else if (b.type === "thinking") {
const sig = b.thinkingSignature || (isSame ? (b as any).thoughtSignature : undefined);
const rawSig = b.thinkingSignature || (isSame ? (b as any).thoughtSignature : undefined);
const sig = isValidGeminiThoughtSignature(rawSig) ? rawSig : undefined;
if ((!b.thinking || !b.thinking.trim()) && !sig) continue;
parts.push({ thought: true, text: b.thinking, ...(sig ? { thoughtSignature: sig } : {}) });
} else if (b.type === "toolCall") {
const sig = b.thoughtSignature || (b as any).thought_signature;
const rawSig = b.thoughtSignature || (b as any).thought_signature;
const sig = isValidGeminiThoughtSignature(rawSig) ? rawSig : undefined;
if (sig) {
if (b.id) signedToolCallIds.add(b.id);
if (b.name) signedToolCallIds.add(b.name);
@@ -1909,8 +1921,8 @@ function antgBuildGeminiRequest(model: Model<Api>, context: Context, options?: S
thoughtSignature: sig,
});
} else {
// Chiamata non firmata (cross-model o da provider terzo): Google Gemini 2.5/3.x
// rigetta con HTTP 400 i functionCall privi di thought_signature.
// Chiamata non firmata (cross-model o da provider terzo come DeepSeek): Google Gemini 2.5/3.x
// rigetta con HTTP 400 i functionCall privi di valida Base64 thought_signature.
// La serializziamo come testo per mantenere il contesto senza causare l'errore 400.
parts.push({
text: `[Tool Call: ${b.name}]\nArgs: ${JSON.stringify(b.arguments ?? {})}`,