refactor(sse): extract openai-to-gemini pure helpers into a leaf (#5824)

Split open-sse/translator/request/openai-to-gemini.ts (873 -> 756 LOC, back under
the 800-line cap) by moving the module-private pure helpers — the historical-tool-
context string builders (stringifyHistoricalToolArguments, buildInertHistorical*,
escapeHistoricalContext*, buildHistoricalToolResultContext), deepCleanUndefined,
extractClientThoughtSignature, buildChangedToolNameMap, isVertexGeminiProvider, and
applyAntigravityGenerationDefaults (with its GeminiGenerationConfig shape) — into
openai-to-gemini/helpers.ts.

These were module-private, so the translator's public API is unchanged; the host
imports them back internally. Bodies are verbatim: the code-line multiset of host +
leaf equals the original. Adds tests/unit/openai-to-gemini-helpers-split.test.ts
pinning the leaf's pure behaviour (escaping, undefined-pruning, signature extraction,
antigravity generation-config defaults) and the host wiring.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-01 10:10:14 -03:00
committed by GitHub
parent ec457e264d
commit 6cff67e6ac
3 changed files with 244 additions and 134 deletions

View File

@@ -24,6 +24,20 @@ import {
cleanJSONSchemaForAntigravity,
} from "../helpers/geminiHelper.ts";
import { buildGeminiTools, sanitizeGeminiToolName } from "../helpers/geminiToolsSanitizer.ts";
import {
type GeminiGenerationConfig,
isVertexGeminiProvider,
buildChangedToolNameMap,
extractClientThoughtSignature,
deepCleanUndefined,
applyAntigravityGenerationDefaults,
stringifyHistoricalToolArguments,
buildInertHistoricalToolCallText,
buildInertHistoricalToolResponseText,
escapeHistoricalContextAttribute,
escapeHistoricalContextContent,
buildHistoricalToolResultContext,
} from "./openai-to-gemini/helpers.ts";
// Observed Antigravity wrapper output cap, not an underlying model capability.
// Keep this bridge-local: Antigravity currently caps visible output around 16K.
@@ -43,20 +57,6 @@ const GEMINI_BUILTIN_TOOL_NAMES = new Set<string>([
type GeminiPart = Record<string, unknown>;
type GeminiContent = { role: string; parts: GeminiPart[] };
type GeminiGenerationConfig = {
temperature?: unknown;
topP?: unknown;
topK?: unknown;
maxOutputTokens?: unknown;
thinkingConfig?: {
thinkingBudget: number;
includeThoughts: boolean;
};
responseMimeType?: string;
responseSchema?: unknown;
stopSequences?: string[] | unknown[];
};
type GeminiFunctionDeclaration = {
name: string;
description: string;
@@ -118,126 +118,6 @@ type GeminiToolNameOptions = {
supportsSignatureBypass?: boolean;
};
// Vertex AI (and Vertex Partner models) reject the OpenAI-style `id` field inside
// function_call / function_response parts. Detect these by the routed provider id.
function isVertexGeminiProvider(provider: unknown): boolean {
return provider === "vertex" || provider === "vertex-partner";
}
type OpenAIToolCallLike = {
thoughtSignature?: unknown;
thought_signature?: unknown;
function?: {
thoughtSignature?: unknown;
thought_signature?: unknown;
};
};
function buildChangedToolNameMap(toolNameMap: Map<string, string>): Map<string, string> | null {
const changedEntries = [...toolNameMap.entries()].filter(
([sanitizedName, originalName]) => sanitizedName !== originalName
);
return changedEntries.length > 0 ? new Map(changedEntries) : null;
}
function extractClientThoughtSignature(toolCall: unknown): string | null {
if (!toolCall || typeof toolCall !== "object") return null;
const candidate = toolCall as OpenAIToolCallLike;
const signature =
candidate.thoughtSignature ||
candidate.thought_signature ||
candidate.function?.thoughtSignature ||
candidate.function?.thought_signature ||
null;
return typeof signature === "string" && signature.length > 0 ? signature : null;
}
function deepCleanUndefined(value: unknown, depth = 0): void {
if (depth > 10 || !value || typeof value !== "object") {
return;
}
if (Array.isArray(value)) {
for (const item of value) {
deepCleanUndefined(item, depth + 1);
}
} else {
const obj = value as Record<string, unknown>;
for (const key of Object.keys(obj)) {
const val = obj[key];
if (typeof val === "string" && val === "[undefined]") {
delete obj[key];
} else {
deepCleanUndefined(val, depth + 1);
}
}
}
}
function applyAntigravityGenerationDefaults(generationConfig: GeminiGenerationConfig) {
const config = { ...generationConfig };
if (config.topK === undefined) {
config.topK = 40;
}
if (config.topP === undefined) {
config.topP = 1;
}
const thinkingBudget = Number(config.thinkingConfig?.thinkingBudget);
const maxOutputTokens = Number(config.maxOutputTokens);
if (
Number.isFinite(thinkingBudget) &&
thinkingBudget > 0 &&
(!Number.isFinite(maxOutputTokens) || maxOutputTokens <= thinkingBudget)
) {
config.maxOutputTokens = Math.floor(thinkingBudget) + 1;
}
return config;
}
function stringifyHistoricalToolArguments(value: unknown): string {
if (typeof value === "string") return value;
try {
return JSON.stringify(value ?? {});
} catch {
return String(value ?? "{}");
}
}
function buildInertHistoricalToolCallText(name: string | undefined, args: unknown): string {
const toolName = name || "unknown";
return `[tool_history_call: ${toolName}] ${stringifyHistoricalToolArguments(args || "{}")}`;
}
function buildInertHistoricalToolResponseText(name: string, response: unknown): string {
return `[tool_history_result: ${name || "unknown"}] ${typeof response === "string" ? response : stringifyHistoricalToolArguments(response)}`;
}
function escapeHistoricalContextAttribute(value: string): string {
return value
.replaceAll("&", "&amp;")
.replaceAll('"', "&quot;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;");
}
function escapeHistoricalContextContent(value: string): string {
return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
}
function buildHistoricalToolResultContext(name: string, response: unknown): string {
const source = escapeHistoricalContextAttribute(name || "unknown");
const rawResult =
typeof response === "string" ? response : stringifyHistoricalToolArguments(response);
const result = escapeHistoricalContextContent(rawResult);
return [
`<previous_tool_result_context source="${source}">`,
result,
"</previous_tool_result_context>",
].join("\n");
}
// Gemini-family APIs (incl. Antigravity / Vertex) reject a `contents[]` array that
// has two adjacent entries with the same role:
// 400 INVALID_ARGUMENT "Request contains consecutive messages with the same role".

View File

@@ -0,0 +1,142 @@
// Pure, self-contained helpers extracted verbatim from ../openai-to-gemini.ts
// (god-file decomposition): historical-tool-context string builders, undefined-
// pruning, thought-signature extraction, tool-name remapping, and the Vertex
// provider check + Antigravity generation-config defaults. No I/O or module state;
// the host imports them back internally (these were module-private — no public API
// change). The GeminiGenerationConfig shape lives here with its only mutator.
export type GeminiGenerationConfig = {
temperature?: unknown;
topP?: unknown;
topK?: unknown;
maxOutputTokens?: unknown;
thinkingConfig?: {
thinkingBudget: number;
includeThoughts: boolean;
};
responseMimeType?: string;
responseSchema?: unknown;
stopSequences?: string[] | unknown[];
};
// Vertex AI (and Vertex Partner models) reject the OpenAI-style `id` field inside
// function_call / function_response parts. Detect these by the routed provider id.
export function isVertexGeminiProvider(provider: unknown): boolean {
return provider === "vertex" || provider === "vertex-partner";
}
type OpenAIToolCallLike = {
thoughtSignature?: unknown;
thought_signature?: unknown;
function?: {
thoughtSignature?: unknown;
thought_signature?: unknown;
};
};
export function buildChangedToolNameMap(
toolNameMap: Map<string, string>
): Map<string, string> | null {
const changedEntries = [...toolNameMap.entries()].filter(
([sanitizedName, originalName]) => sanitizedName !== originalName
);
return changedEntries.length > 0 ? new Map(changedEntries) : null;
}
export function extractClientThoughtSignature(toolCall: unknown): string | null {
if (!toolCall || typeof toolCall !== "object") return null;
const candidate = toolCall as OpenAIToolCallLike;
const signature =
candidate.thoughtSignature ||
candidate.thought_signature ||
candidate.function?.thoughtSignature ||
candidate.function?.thought_signature ||
null;
return typeof signature === "string" && signature.length > 0 ? signature : null;
}
export function deepCleanUndefined(value: unknown, depth = 0): void {
if (depth > 10 || !value || typeof value !== "object") {
return;
}
if (Array.isArray(value)) {
for (const item of value) {
deepCleanUndefined(item, depth + 1);
}
} else {
const obj = value as Record<string, unknown>;
for (const key of Object.keys(obj)) {
const val = obj[key];
if (typeof val === "string" && val === "[undefined]") {
delete obj[key];
} else {
deepCleanUndefined(val, depth + 1);
}
}
}
}
export function applyAntigravityGenerationDefaults(generationConfig: GeminiGenerationConfig) {
const config = { ...generationConfig };
if (config.topK === undefined) {
config.topK = 40;
}
if (config.topP === undefined) {
config.topP = 1;
}
const thinkingBudget = Number(config.thinkingConfig?.thinkingBudget);
const maxOutputTokens = Number(config.maxOutputTokens);
if (
Number.isFinite(thinkingBudget) &&
thinkingBudget > 0 &&
(!Number.isFinite(maxOutputTokens) || maxOutputTokens <= thinkingBudget)
) {
config.maxOutputTokens = Math.floor(thinkingBudget) + 1;
}
return config;
}
export function stringifyHistoricalToolArguments(value: unknown): string {
if (typeof value === "string") return value;
try {
return JSON.stringify(value ?? {});
} catch {
return String(value ?? "{}");
}
}
export function buildInertHistoricalToolCallText(name: string | undefined, args: unknown): string {
const toolName = name || "unknown";
return `[tool_history_call: ${toolName}] ${stringifyHistoricalToolArguments(args || "{}")}`;
}
export function buildInertHistoricalToolResponseText(name: string, response: unknown): string {
return `[tool_history_result: ${name || "unknown"}] ${typeof response === "string" ? response : stringifyHistoricalToolArguments(response)}`;
}
export function escapeHistoricalContextAttribute(value: string): string {
return value
.replaceAll("&", "&amp;")
.replaceAll('"', "&quot;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;");
}
export function escapeHistoricalContextContent(value: string): string {
return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
}
export function buildHistoricalToolResultContext(name: string, response: unknown): string {
const source = escapeHistoricalContextAttribute(name || "unknown");
const rawResult =
typeof response === "string" ? response : stringifyHistoricalToolArguments(response);
const result = escapeHistoricalContextContent(rawResult);
return [
`<previous_tool_result_context source="${source}">`,
result,
"</previous_tool_result_context>",
].join("\n");
}

View File

@@ -0,0 +1,88 @@
// Split-guard for the openai-to-gemini helpers extraction (god-file decomposition):
// the pure historical-tool-context builders, undefined-pruning, thought-signature
// extraction, tool-name remapping, the Vertex provider check, and the Antigravity
// generation-config defaults moved verbatim from openai-to-gemini.ts into
// openai-to-gemini/helpers.ts. These were module-private, so the translator's public
// API is unchanged; the host imports them back internally. The locks pin the leaf's
// pure behaviour and that the host now imports the leaf.
import { test } from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import * as h from "../../open-sse/translator/request/openai-to-gemini/helpers.ts";
test("isVertexGeminiProvider matches only the vertex provider ids", () => {
assert.equal(h.isVertexGeminiProvider("vertex"), true);
assert.equal(h.isVertexGeminiProvider("vertex-partner"), true);
assert.equal(h.isVertexGeminiProvider("openai"), false);
assert.equal(h.isVertexGeminiProvider(undefined), false);
});
test("buildChangedToolNameMap keeps only renamed entries, else null", () => {
const changed = h.buildChangedToolNameMap(
new Map([
["a", "a"],
["b_sanitized", "b"],
])
);
assert.deepEqual([...(changed ?? new Map()).entries()], [["b_sanitized", "b"]]);
assert.equal(h.buildChangedToolNameMap(new Map([["a", "a"]])), null);
});
test("extractClientThoughtSignature reads the first non-empty signature field", () => {
assert.equal(h.extractClientThoughtSignature({ thoughtSignature: "sig" }), "sig");
assert.equal(h.extractClientThoughtSignature({ function: { thought_signature: "s2" } }), "s2");
assert.equal(h.extractClientThoughtSignature({ thoughtSignature: "" }), null);
assert.equal(h.extractClientThoughtSignature({}), null);
assert.equal(h.extractClientThoughtSignature(null), null);
});
test("deepCleanUndefined deletes '[undefined]' string values in place, recursively", () => {
const obj = { a: 1, b: "[undefined]", c: { d: "[undefined]", e: 2 }, f: ["[undefined]"] };
h.deepCleanUndefined(obj);
assert.deepEqual(obj, { a: 1, c: { e: 2 }, f: ["[undefined]"] });
});
test("applyAntigravityGenerationDefaults fills topK/topP and bumps maxOutputTokens past the budget", () => {
assert.deepEqual(h.applyAntigravityGenerationDefaults({}), { topK: 40, topP: 1 });
assert.deepEqual(h.applyAntigravityGenerationDefaults({ topK: 5 }), { topK: 5, topP: 1 });
const withBudget = h.applyAntigravityGenerationDefaults({
thinkingConfig: { thinkingBudget: 100, includeThoughts: true },
});
assert.equal(withBudget.maxOutputTokens, 101);
});
test("historical-tool-context builders stringify and escape as expected", () => {
assert.equal(h.stringifyHistoricalToolArguments("raw"), "raw");
assert.equal(h.stringifyHistoricalToolArguments({ a: 1 }), '{"a":1}');
assert.equal(h.stringifyHistoricalToolArguments(undefined), "{}");
assert.equal(
h.buildInertHistoricalToolCallText("foo", { a: 1 }),
'[tool_history_call: foo] {"a":1}'
);
assert.equal(
h.buildInertHistoricalToolResponseText("bar", "ok"),
"[tool_history_result: bar] ok"
);
// Attribute escaping includes quotes; content escaping does not.
assert.equal(h.escapeHistoricalContextAttribute('<t>"&'), "&lt;t&gt;&quot;&amp;");
assert.equal(h.escapeHistoricalContextContent('<t>"&'), '&lt;t&gt;"&amp;');
});
test("buildHistoricalToolResultContext wraps escaped source + result in the context tag", () => {
assert.equal(
h.buildHistoricalToolResultContext("myTool", { r: 1 }),
'<previous_tool_result_context source="myTool">\n{"r":1}\n</previous_tool_result_context>'
);
});
test("host imports the helpers leaf and no longer defines them inline", () => {
const host = fs.readFileSync(
path.join("open-sse", "translator", "request", "openai-to-gemini.ts"),
"utf-8"
);
assert.match(host, /from "\.\/openai-to-gemini\/helpers\.ts"/);
assert.doesNotMatch(host, /^function deepCleanUndefined\(/m);
assert.doesNotMatch(host, /^type GeminiGenerationConfig =/m);
});