mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 23:01:56 +00:00
Avoid cross-provider thought signatures (#654)
* Avoid cross-provider thought signatures * Fix Google thought signature replay Filter thought signatures to same provider with base64 validation and rename the transform helper for clarity.
This commit is contained in:
parent
6f3ba88733
commit
934e7e470b
6 changed files with 28 additions and 9 deletions
|
|
@ -26,7 +26,7 @@ import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|||
import { parseStreamingJson } from "../utils/json-parse.js";
|
||||
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
||||
|
||||
import { transformMessages } from "./transorm-messages.js";
|
||||
import { transformMessages } from "./transform-messages.js";
|
||||
|
||||
// Stealth mode: Mimic Claude Code's tool naming exactly
|
||||
const claudeCodeVersion = "2.1.2";
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
import { type Content, FinishReason, FunctionCallingConfigMode, type Part, type Schema } from "@google/genai";
|
||||
import type { Context, ImageContent, Model, StopReason, TextContent, Tool } from "../types.js";
|
||||
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
||||
import { transformMessages } from "./transorm-messages.js";
|
||||
import { transformMessages } from "./transform-messages.js";
|
||||
|
||||
type GoogleApiType = "google-generative-ai" | "google-gemini-cli" | "google-vertex";
|
||||
|
||||
|
|
@ -42,6 +42,22 @@ export function retainThoughtSignature(existing: string | undefined, incoming: s
|
|||
return existing;
|
||||
}
|
||||
|
||||
// Thought signatures must be base64 for Google APIs (TYPE_BYTES).
|
||||
const base64SignaturePattern = /^[A-Za-z0-9+/]+={0,2}$/;
|
||||
|
||||
function isValidThoughtSignature(signature: string | undefined): boolean {
|
||||
if (!signature) return false;
|
||||
if (signature.length % 4 !== 0) return false;
|
||||
return base64SignaturePattern.test(signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only keep signatures from the same provider/model and with valid base64.
|
||||
*/
|
||||
function resolveThoughtSignature(isSameProviderAndModel: boolean, signature: string | undefined): string | undefined {
|
||||
return isSameProviderAndModel && isValidThoughtSignature(signature) ? signature : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert internal messages to Gemini Content[] format.
|
||||
*/
|
||||
|
|
@ -85,9 +101,10 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
|
|||
if (block.type === "text") {
|
||||
// Skip empty text blocks - they can cause issues with some models (e.g. Claude via Antigravity)
|
||||
if (!block.text || block.text.trim() === "") continue;
|
||||
const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.textSignature);
|
||||
parts.push({
|
||||
text: sanitizeSurrogates(block.text),
|
||||
...(block.textSignature && { thoughtSignature: block.textSignature }),
|
||||
...(thoughtSignature && { thoughtSignature }),
|
||||
});
|
||||
} else if (block.type === "thinking") {
|
||||
// Skip empty thinking blocks
|
||||
|
|
@ -95,10 +112,11 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
|
|||
// Only keep as thinking block if same provider AND same model
|
||||
// Otherwise convert to plain text (no tags to avoid model mimicking them)
|
||||
if (isSameProviderAndModel) {
|
||||
const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thinkingSignature);
|
||||
parts.push({
|
||||
thought: true,
|
||||
text: sanitizeSurrogates(block.thinking),
|
||||
...(block.thinkingSignature && { thoughtSignature: block.thinkingSignature }),
|
||||
...(thoughtSignature && { thoughtSignature }),
|
||||
});
|
||||
} else {
|
||||
parts.push({
|
||||
|
|
@ -112,8 +130,9 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
|
|||
args: block.arguments,
|
||||
},
|
||||
};
|
||||
if (block.thoughtSignature) {
|
||||
part.thoughtSignature = block.thoughtSignature;
|
||||
const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thoughtSignature);
|
||||
if (thoughtSignature) {
|
||||
part.thoughtSignature = thoughtSignature;
|
||||
}
|
||||
parts.push(part);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ import {
|
|||
transformRequestBody,
|
||||
} from "./openai-codex/request-transformer.js";
|
||||
import { parseCodexError, parseCodexSseStream } from "./openai-codex/response-handler.js";
|
||||
import { transformMessages } from "./transorm-messages.js";
|
||||
import { transformMessages } from "./transform-messages.js";
|
||||
|
||||
export interface OpenAICodexResponsesOptions extends StreamOptions {
|
||||
reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import type {
|
|||
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
||||
import { parseStreamingJson } from "../utils/json-parse.js";
|
||||
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
||||
import { transformMessages } from "./transorm-messages.js";
|
||||
import { transformMessages } from "./transform-messages.js";
|
||||
|
||||
/**
|
||||
* Normalize tool call ID for Mistral.
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import type {
|
|||
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
||||
import { parseStreamingJson } from "../utils/json-parse.js";
|
||||
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
||||
import { transformMessages } from "./transorm-messages.js";
|
||||
import { transformMessages } from "./transform-messages.js";
|
||||
|
||||
/** Fast deterministic hash to shorten long strings */
|
||||
function shortHash(str: string): string {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue