Merge remote-tracking branch 'origin/release/v3.8.50' into green/8728

This commit is contained in:
diegosouzapw
2026-08-06 07:50:15 -03:00
2 changed files with 56 additions and 10 deletions

View File

@@ -6,12 +6,12 @@ import {
cleanJSONSchemaForAntigravity,
} from "../helpers/geminiHelper.ts";
import { buildGeminiTools, sanitizeGeminiToolName } from "../helpers/geminiToolsSanitizer.ts";
import { capMaxOutputTokens, capThinkingBudget } from "../../../src/lib/modelCapabilities.ts";
import { getModelSpec } from "../../../src/shared/constants/modelSpecs.ts";
import {
buildGeminiThoughtSignatureKey,
resolveGeminiThoughtSignature,
} from "../../services/geminiThoughtSignatureStore.ts";
import { capMaxOutputTokens, capThinkingBudget } from "../../../src/lib/modelCapabilities.ts";
import { getModelSpec } from "../../../src/shared/constants/modelSpecs.ts";
import { buildHistoricalToolResultContext } from "./openai-to-gemini/helpers.ts";
/**
@@ -127,6 +127,9 @@ export function claudeToGeminiRequest(model, body, stream, credentials = null) {
// ── Convert messages ───────────────────────────────────────────
if (body.messages && Array.isArray(body.messages)) {
// Tool-ids whose functionCall was omitted (no stored thought_signature) so the
// matching tool_result becomes text instead of a Gemini-400'd functionResponse.
const omittedToolCallIds = new Set<string>();
for (const msg of body.messages) {
const parts = [];
let shouldUseEmbeddedSignature = true;

View File

@@ -11,15 +11,24 @@
import test from "node:test";
import assert from "node:assert/strict";
const { openaiToGeminiRequest } = await import(
"../../open-sse/translator/request/openai-to-gemini.ts"
);
const { claudeToGeminiRequest } = await import(
"../../open-sse/translator/request/claude-to-gemini.ts"
);
const { openaiToGeminiRequest } =
await import("../../open-sse/translator/request/openai-to-gemini.ts");
const { claudeToGeminiRequest } =
await import("../../open-sse/translator/request/claude-to-gemini.ts");
const { buildGeminiThoughtSignatureKey, storeGeminiThoughtSignature } =
await import("../../open-sse/services/geminiThoughtSignatureStore.ts");
type UnknownRecord = Record<string, unknown>;
const CLAUDE_SIGNATURE_NAMESPACE = "regression-3440";
function seedClaudeThoughtSignature() {
storeGeminiThoughtSignature(
buildGeminiThoughtSignatureKey(CLAUDE_SIGNATURE_NAMESPACE, "tu_weather_1"),
"SIG_3440"
);
}
function findFunctionCall(result: any): UnknownRecord | undefined {
for (const content of result.contents ?? []) {
for (const part of content.parts ?? []) {
@@ -29,6 +38,15 @@ function findFunctionCall(result: any): UnknownRecord | undefined {
return undefined;
}
function findFunctionCallPart(result: any): UnknownRecord | undefined {
for (const content of result.contents ?? []) {
for (const part of content.parts ?? []) {
if (part?.functionCall) return part as UnknownRecord;
}
}
return undefined;
}
function findFunctionResponse(result: any): UnknownRecord | undefined {
for (const content of result.contents ?? []) {
for (const part of content.parts ?? []) {
@@ -126,18 +144,43 @@ test("#3440 OpenAI->Gemini: no provider hint PRESERVES id (default, non-vertex)"
});
test("#3440 Claude->Gemini: vertex provider omits id from functionCall and functionResponse", () => {
seedClaudeThoughtSignature();
const result = claudeToGeminiRequest("gemini-2.5-pro", CLAUDE_TOOL_BODY, false, {
_provider: "vertex",
_signatureNamespace: CLAUDE_SIGNATURE_NAMESPACE,
});
assert.equal(findFunctionCall(result)?.id, undefined, "functionCall.id must be omitted for Vertex");
assert.equal(
findFunctionCall(result)?.id,
undefined,
"functionCall.id must be omitted for Vertex"
);
assert.equal(
findFunctionResponse(result)?.id,
undefined,
"functionResponse.id must be omitted for Vertex"
);
const vertexPart = findFunctionCallPart(result);
assert.ok(vertexPart, "expected a functionCall part");
assert.equal(
vertexPart.thoughtSignature,
"SIG_3440",
"thoughtSignature must be replayed even for Vertex (only id is stripped)"
);
});
test("#3440 Claude->Gemini: no provider hint PRESERVES id (default, non-vertex)", () => {
const result = claudeToGeminiRequest("gemini-2.5-pro", CLAUDE_TOOL_BODY, false);
seedClaudeThoughtSignature();
const result = claudeToGeminiRequest("gemini-2.5-pro", CLAUDE_TOOL_BODY, false, {
_signatureNamespace: CLAUDE_SIGNATURE_NAMESPACE,
});
assert.equal(findFunctionCall(result)?.id, "tu_weather_1");
const nonVertexPart = findFunctionCallPart(result);
assert.ok(nonVertexPart, "expected a functionCall part");
assert.equal(
nonVertexPart.thoughtSignature,
"SIG_3440",
"thoughtSignature must be replayed for direct Claude->Gemini path"
);
});