This commit is contained in:
2026-07-06 00:01:34 +07:00
parent 2729408ef3
commit e7470e955e
108 changed files with 4131 additions and 529 deletions

View File

@@ -150,6 +150,33 @@ export function normalizeClaudePassthrough(body, model = "") {
}
}
// 3. Drop thinking blocks whose signature is not Claude's (combo mixes models,
// so foreign signatures leak into history and Anthropic rejects them).
const thinkingEnabled = body.thinking?.type === "enabled";
if (Array.isArray(body.messages)) {
for (const msg of body.messages) {
if (msg.role !== ROLE.ASSISTANT || !Array.isArray(msg.content)) continue;
let hasToolUse = false;
let hasKeptThinking = false;
const kept = [];
for (const block of msg.content) {
if (block.type === CLAUDE_BLOCK.THINKING || block.type === CLAUDE_BLOCK.REDACTED_THINKING) {
if (isValidClaudeSignature(block.signature)) {
hasKeptThinking = true;
kept.push(block);
}
continue;
}
if (block.type === CLAUDE_BLOCK.TOOL_USE) hasToolUse = true;
kept.push(block);
}
msg.content = kept;
if (thinkingEnabled && !hasKeptThinking && hasToolUse) {
msg.content.unshift(buildThinkingPlaceholder("claude"));
}
}
}
return body;
}

View File

@@ -12,6 +12,8 @@ export const UNSUPPORTED_SCHEMA_CONSTRAINTS = [
"default", "examples",
// JSON Schema meta keywords
"$schema", "$defs", "definitions", "const", "$ref", "$comment",
// Annotation keywords (rejected by Gemini/Antigravity - e.g. MCP tool schemas set these)
"deprecated", "readOnly", "writeOnly",
// Object validation keywords (not supported)
"additionalProperties", "propertyNames", "patternProperties", "enumDescriptions",
// Complex schema keywords (handled by flattenAnyOfOneOf/mergeAllOf)
@@ -19,7 +21,7 @@ export const UNSUPPORTED_SCHEMA_CONSTRAINTS = [
// Dependency keywords (not supported)
"dependencies", "dependentSchemas", "dependentRequired",
// Other unsupported keywords
"title", "optional", "if", "then", "else", "contentMediaType", "contentEncoding",
"title", "optional", "deprecated", "if", "then", "else", "contentMediaType", "contentEncoding",
// UI/Styling properties (from Cursor tools - NOT JSON Schema standard)
"cornerRadius", "fillColor", "fontFamily", "fontSize", "fontWeight",
"gap", "padding", "strokeColor", "strokeThickness", "textColor"

View File

@@ -6,42 +6,46 @@ export { VALID_OPENAI_CONTENT_TYPES, VALID_OPENAI_MESSAGE_TYPES };
// Filter messages to OpenAI standard format
// Remove: thinking, redacted_thinking, signature, and other non-OpenAI blocks
export function filterToOpenAIFormat(body) {
// opts.preserveCacheControl: keep cache_control on content blocks (e.g. for DashScope/alicode)
export function filterToOpenAIFormat(body, opts = {}) {
if (!body.messages || !Array.isArray(body.messages)) return body;
const keepCache = !!opts.preserveCacheControl;
function stripBlock(block) {
const { signature, cache_control, ...rest } = block;
return keepCache && cache_control ? { ...rest, cache_control } : rest;
}
body.messages = body.messages.map(msg => {
// Normalize developer role to system (many providers don't support developer)
if (msg.role === ROLE.DEVELOPER) msg = { ...msg, role: ROLE.SYSTEM };
// Keep tool messages as-is (OpenAI format)
if (msg.role === ROLE.TOOL) return msg;
// Keep assistant messages with tool_calls as-is
if (msg.role === ROLE.ASSISTANT && msg.tool_calls) return msg;
// Handle string content
if (typeof msg.content === "string") return msg;
// Handle array content
if (Array.isArray(msg.content)) {
const filteredContent = [];
for (const block of msg.content) {
// Skip thinking blocks
if (block.type === CLAUDE_BLOCK.THINKING || block.type === CLAUDE_BLOCK.REDACTED_THINKING) continue;
// Only keep valid OpenAI content types
if (VALID_OPENAI_CONTENT_TYPES.includes(block.type)) {
// Remove signature field if exists
const { signature, cache_control, ...cleanBlock } = block;
filteredContent.push(cleanBlock);
filteredContent.push(stripBlock(block));
} else if (block.type === CLAUDE_BLOCK.TOOL_USE) {
// Convert tool_use to tool_calls format (handled separately)
continue;
} else if (block.type === CLAUDE_BLOCK.TOOL_RESULT) {
// Keep tool_result but clean it
const { signature, cache_control, ...cleanBlock } = block;
filteredContent.push(cleanBlock);
filteredContent.push(stripBlock(block));
}
}