fix: strip functionCall/functionResponse id and synthetic thoughtSignature for Vertex AI (closes #388) (#414)
This commit is contained in:
parent
a6c764d772
commit
e3a7733a08
6 changed files with 55 additions and 2 deletions
|
|
@ -303,7 +303,7 @@ export const PROVIDERS = {
|
||||||
// baseUrl is not used; VertexExecutor.buildUrl() constructs it dynamically
|
// baseUrl is not used; VertexExecutor.buildUrl() constructs it dynamically
|
||||||
vertex: {
|
vertex: {
|
||||||
baseUrl: "https://aiplatform.googleapis.com",
|
baseUrl: "https://aiplatform.googleapis.com",
|
||||||
format: "gemini"
|
format: "vertex"
|
||||||
},
|
},
|
||||||
// Vertex AI - Partner models (Claude, Llama, Mistral, GLM) via SA JSON
|
// Vertex AI - Partner models (Claude, Llama, Mistral, GLM) via SA JSON
|
||||||
// Uses OpenAI-compatible global endpoint (or rawPredict for Anthropic)
|
// Uses OpenAI-compatible global endpoint (or rawPredict for Anthropic)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
|
||||||
if (targetFormat === sourceFormat || targetFormat === FORMATS.OPENAI) return responseBody;
|
if (targetFormat === sourceFormat || targetFormat === FORMATS.OPENAI) return responseBody;
|
||||||
|
|
||||||
// Gemini / Antigravity
|
// Gemini / Antigravity
|
||||||
if (targetFormat === FORMATS.GEMINI || targetFormat === FORMATS.ANTIGRAVITY || targetFormat === FORMATS.GEMINI_CLI) {
|
if (targetFormat === FORMATS.GEMINI || targetFormat === FORMATS.ANTIGRAVITY || targetFormat === FORMATS.GEMINI_CLI || targetFormat === FORMATS.VERTEX) {
|
||||||
const response = responseBody.response || responseBody;
|
const response = responseBody.response || responseBody;
|
||||||
if (!response?.candidates?.[0]) return responseBody;
|
if (!response?.candidates?.[0]) return responseBody;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export const FORMATS = {
|
||||||
CLAUDE: "claude",
|
CLAUDE: "claude",
|
||||||
GEMINI: "gemini",
|
GEMINI: "gemini",
|
||||||
GEMINI_CLI: "gemini-cli",
|
GEMINI_CLI: "gemini-cli",
|
||||||
|
VERTEX: "vertex",
|
||||||
CODEX: "codex",
|
CODEX: "codex",
|
||||||
ANTIGRAVITY: "antigravity",
|
ANTIGRAVITY: "antigravity",
|
||||||
KIRO: "kiro",
|
KIRO: "kiro",
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ function ensureInitialized() {
|
||||||
require("./request/openai-to-claude.js");
|
require("./request/openai-to-claude.js");
|
||||||
require("./request/gemini-to-openai.js");
|
require("./request/gemini-to-openai.js");
|
||||||
require("./request/openai-to-gemini.js");
|
require("./request/openai-to-gemini.js");
|
||||||
|
require("./request/openai-to-vertex.js");
|
||||||
require("./request/antigravity-to-openai.js");
|
require("./request/antigravity-to-openai.js");
|
||||||
require("./request/openai-responses.js");
|
require("./request/openai-responses.js");
|
||||||
require("./request/openai-to-kiro.js");
|
require("./request/openai-to-kiro.js");
|
||||||
|
|
|
||||||
50
open-sse/translator/request/openai-to-vertex.js
Normal file
50
open-sse/translator/request/openai-to-vertex.js
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { register } from "../index.js";
|
||||||
|
import { FORMATS } from "../formats.js";
|
||||||
|
import { openaiToGeminiRequest } from "./openai-to-gemini.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post-process a Gemini-format body for Vertex AI compatibility:
|
||||||
|
*
|
||||||
|
* 1. Strip `id` from every `functionCall` and `functionResponse` part.
|
||||||
|
* Vertex AI rejects requests that include these fields.
|
||||||
|
*
|
||||||
|
* 2. Strip synthetic `thoughtSignature` parts injected by the base translator.
|
||||||
|
* Vertex rejects fake thought signatures in multi-turn tool-call history;
|
||||||
|
* only real signatures emitted by Vertex itself should be replayed.
|
||||||
|
*/
|
||||||
|
function stripVertexIncompatibleFields(body) {
|
||||||
|
if (!body?.contents) return body;
|
||||||
|
|
||||||
|
for (const turn of body.contents) {
|
||||||
|
if (!Array.isArray(turn.parts)) continue;
|
||||||
|
|
||||||
|
// Remove standalone synthetic thoughtSignature parts (text === "" with thoughtSignature)
|
||||||
|
turn.parts = turn.parts.filter(
|
||||||
|
p => !(p.thoughtSignature !== undefined && p.text === "" && !p.thought)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const part of turn.parts) {
|
||||||
|
// Strip id from functionCall
|
||||||
|
if (part.functionCall && "id" in part.functionCall) {
|
||||||
|
delete part.functionCall.id;
|
||||||
|
}
|
||||||
|
// Strip id from functionResponse
|
||||||
|
if (part.functionResponse && "id" in part.functionResponse) {
|
||||||
|
delete part.functionResponse.id;
|
||||||
|
}
|
||||||
|
// Strip thoughtSignature injected alongside functionCall (synthetic signature)
|
||||||
|
if (part.functionCall && "thoughtSignature" in part) {
|
||||||
|
delete part.thoughtSignature;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openaiToVertexRequest(model, body, stream, credentials) {
|
||||||
|
const gemini = openaiToGeminiRequest(model, body, stream, credentials);
|
||||||
|
return stripVertexIncompatibleFields(gemini);
|
||||||
|
}
|
||||||
|
|
||||||
|
register(FORMATS.OPENAI, FORMATS.VERTEX, openaiToVertexRequest, null);
|
||||||
|
|
@ -237,4 +237,5 @@ export function geminiToOpenAIResponse(chunk, state) {
|
||||||
register(FORMATS.GEMINI, FORMATS.OPENAI, null, geminiToOpenAIResponse);
|
register(FORMATS.GEMINI, FORMATS.OPENAI, null, geminiToOpenAIResponse);
|
||||||
register(FORMATS.GEMINI_CLI, FORMATS.OPENAI, null, geminiToOpenAIResponse);
|
register(FORMATS.GEMINI_CLI, FORMATS.OPENAI, null, geminiToOpenAIResponse);
|
||||||
register(FORMATS.ANTIGRAVITY, FORMATS.OPENAI, null, geminiToOpenAIResponse);
|
register(FORMATS.ANTIGRAVITY, FORMATS.OPENAI, null, geminiToOpenAIResponse);
|
||||||
|
register(FORMATS.VERTEX, FORMATS.OPENAI, null, geminiToOpenAIResponse);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue