Merge origin/master (v0.5.69) into gitea/new_feature

This commit is contained in:
2026-09-07 14:10:11 +07:00
222 changed files with 12285 additions and 3371 deletions

View File

@@ -327,7 +327,6 @@ export function claudeToKiroRequest(model, body, stream, credentials) {
};
if (profileArn) payload.profileArn = profileArn;
if (systemPrompt) payload.systemPrompt = systemPrompt;
if (additionalModelRequestFields) {
payload.additionalModelRequestFields = additionalModelRequestFields;
}

View File

@@ -6,12 +6,15 @@
*/
import { register } from "../index.js";
import { FORMATS } from "../formats.js";
import { normalizeResponsesInput } from "../formats/responsesApi.js";
import {
normalizeResponsesInput,
clampResponsesCallId,
coerceResponsesArguments,
coerceResponsesOutput,
} from "../formats/responsesApi.js";
import { ROLE, OPENAI_BLOCK, RESPONSES_ITEM } from "../schema/index.js";
// Responses API enforces max 64 chars on call_id (#393)
const MAX_CALL_ID_LEN = 64;
const clampCallId = (id) => (typeof id === "string" && id.length > MAX_CALL_ID_LEN ? id.substring(0, MAX_CALL_ID_LEN) : id);
const MAX_TOOL_NAME_LEN = 128;
/**
* Convert OpenAI Responses API request to OpenAI Chat Completions format
@@ -249,6 +252,23 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
return result;
}
/**
* Extract plain text from a system/developer message for Responses instructions.
* Array content (text parts) is joined; anything else falls back to "" rather
* than leaking "[object Object]" upstream.
*/
function extractInstructionsText(content) {
if (typeof content === "string") return content;
if (Array.isArray(content)) {
return content.map((c) => {
if (typeof c?.text === "string") return c.text;
if (typeof c?.content === "string") return c.content;
return "";
}).filter(Boolean).join("\n");
}
return "";
}
/**
* Ensure object schema always has properties field (required by Codex Responses API)
*/
@@ -300,7 +320,16 @@ function buildReasoningInputItem(msg) {
*/
export function openaiToOpenAIResponsesRequest(model, body, stream, credentials) {
// Body already in Responses API format (e.g. Cursor CLI calling /chat/completions with input[])
if (body.input) return { ...body, model, stream: true };
if (body.input) {
const out = { ...body, model, stream: true };
if (out.max_output_tokens === undefined) {
if (out.max_completion_tokens !== undefined) out.max_output_tokens = out.max_completion_tokens;
else if (out.max_tokens !== undefined) out.max_output_tokens = out.max_tokens;
}
delete out.max_tokens;
delete out.max_completion_tokens;
return out;
}
const result = {
model,
@@ -318,7 +347,7 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
// Use the first instruction-bearing message as instructions.
// OpenAI recommends role="developer" for GPT-5/Codex as the system-level prompt.
if (!hasSystemMessage) {
result.instructions = typeof msg.content === "string" ? msg.content : "";
result.instructions = extractInstructionsText(msg.content);
hasSystemMessage = true;
}
continue; // Skip instruction messages in input
@@ -369,26 +398,24 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
// Convert tool calls
if (msg.role === ROLE.ASSISTANT && msg.tool_calls) {
for (const tc of msg.tool_calls) {
// Skip nameless calls — strict Responses upstreams reject them (#444)
const name = typeof tc.function?.name === "string" ? tc.function.name.trim() : "";
if (!name) continue;
result.input.push({
type: RESPONSES_ITEM.FUNCTION_CALL,
call_id: clampCallId(tc.id),
name: tc.function?.name || "_unknown",
arguments: tc.function?.arguments || "{}"
call_id: clampResponsesCallId(tc.id),
name: name.slice(0, MAX_TOOL_NAME_LEN),
arguments: coerceResponsesArguments(tc.function?.arguments)
});
}
}
// Convert tool results - output must be a string for Responses API
if (msg.role === ROLE.TOOL) {
const output = typeof msg.content === "string"
? msg.content
: Array.isArray(msg.content)
? msg.content.map(c => c.text || JSON.stringify(c)).join("")
: JSON.stringify(msg.content);
result.input.push({
type: RESPONSES_ITEM.FUNCTION_CALL_OUTPUT,
call_id: clampCallId(msg.tool_call_id),
output
call_id: clampResponsesCallId(msg.tool_call_id),
output: coerceResponsesOutput(msg.content)
});
}
}
@@ -402,21 +429,30 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
if (body.tools && Array.isArray(body.tools)) {
result.tools = body.tools.map(tool => {
if (tool.type === OPENAI_BLOCK.FUNCTION) {
// Strict upstreams reject nameless/overlong tool declarations
const name = typeof tool.function?.name === "string" ? tool.function.name.trim() : "";
if (!name) return null;
return {
type: OPENAI_BLOCK.FUNCTION,
name: tool.function.name,
name: name.slice(0, MAX_TOOL_NAME_LEN),
description: String(tool.function.description || ""),
parameters: normalizeToolParameters(tool.function.parameters),
strict: tool.function.strict
};
}
return tool;
});
}).filter(Boolean);
}
// Pass through other relevant fields
if (body.temperature !== undefined) result.temperature = body.temperature;
if (body.max_tokens !== undefined) result.max_tokens = body.max_tokens;
if (body.max_output_tokens !== undefined) {
result.max_output_tokens = body.max_output_tokens;
} else if (body.max_completion_tokens !== undefined) {
result.max_output_tokens = body.max_completion_tokens;
} else if (body.max_tokens !== undefined) {
result.max_output_tokens = body.max_tokens;
}
if (body.top_p !== undefined) result.top_p = body.top_p;
if (body.reasoning !== undefined) result.reasoning = body.reasoning;
if (body.reasoning_effort !== undefined) result.reasoning = { effort: body.reasoning_effort, summary: "auto" };

View File

@@ -2,6 +2,7 @@ import { register } from "../index.js";
import { FORMATS } from "../formats.js";
import { DEFAULT_THINKING_AG_SIGNATURE, DEFAULT_THINKING_GEMINI_CLI_SIGNATURE } from "../../config/defaultThinkingSignature.js";
import { openaiToClaudeRequestForAntigravity } from "./openai-to-claude.js";
import { getGeminiThoughtSignatureSync } from "../../services/thoughtSignatureStore.js";
function generateUUID() {
return crypto.randomUUID();
}
@@ -46,7 +47,7 @@ function normalizeGeminiContents(contents) {
}
// Core: Convert OpenAI request to Gemini format (base for all variants)
function openaiToGeminiBase(model, body, stream, signature = DEFAULT_THINKING_AG_SIGNATURE) {
function openaiToGeminiBase(model, body, stream, signature = DEFAULT_THINKING_AG_SIGNATURE, sessionId = null) {
const result = {
model: model,
contents: [],
@@ -133,18 +134,27 @@ function openaiToGeminiBase(model, body, stream, signature = DEFAULT_THINKING_AG
if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
const toolCallIds = [];
let firstFunctionCallSeen = false;
for (const tc of msg.tool_calls) {
if (tc.type !== OPENAI_BLOCK.FUNCTION) continue;
const args = tryParseJSON(tc.function?.arguments || "{}");
parts.push({
thoughtSignature: signature,
const cachedSig = tc.id ? getGeminiThoughtSignatureSync(tc.id, sessionId) : null;
// First call gets cached signature or fallback; sibling calls remain unsigned if no cached sig
const callSig = cachedSig || (!firstFunctionCallSeen ? signature : undefined);
firstFunctionCallSeen = true;
const part = {
functionCall: {
id: tc.id,
name: sanitizeGeminiFunctionName(tc.function.name),
args: args
}
});
};
if (callSig) {
part.thoughtSignature = callSig;
}
parts.push(part);
toolCallIds.push(tc.id);
}
@@ -232,13 +242,13 @@ function openaiToGeminiBase(model, body, stream, signature = DEFAULT_THINKING_AG
}
// OpenAI -> Gemini (standard API)
export function openaiToGeminiRequest(model, body, stream) {
return openaiToGeminiBase(model, body, stream);
export function openaiToGeminiRequest(model, body, stream, credentials = null) {
return openaiToGeminiBase(model, body, stream, DEFAULT_THINKING_AG_SIGNATURE, credentials?._clientSessionId);
}
// OpenAI -> Gemini CLI (Cloud Code Assist)
export function openaiToGeminiCLIRequest(model, body, stream) {
const gemini = openaiToGeminiBase(model, body, stream, DEFAULT_THINKING_GEMINI_CLI_SIGNATURE);
export function openaiToGeminiCLIRequest(model, body, stream, credentials = null) {
const gemini = openaiToGeminiBase(model, body, stream, DEFAULT_THINKING_GEMINI_CLI_SIGNATURE, credentials?._clientSessionId);
// Thinking is normalized centrally by applyThinking (thinkingUnified.js) after translation.
// Clean schema for tools
@@ -335,18 +345,26 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu
const parts = [];
if (Array.isArray(msg.content)) {
let firstToolUseSeen = false;
for (const block of msg.content) {
if (block.type === CLAUDE_BLOCK.TEXT) {
parts.push({ text: block.text });
} else if (block.type === CLAUDE_BLOCK.TOOL_USE) {
parts.push({
thoughtSignature: signature,
const cachedSig = block.id ? getGeminiThoughtSignatureSync(block.id, credentials?._clientSessionId) : null;
const callSig = cachedSig || (!firstToolUseSeen ? signature : undefined);
firstToolUseSeen = true;
const part = {
functionCall: {
id: block.id,
name: sanitizeGeminiFunctionName(block.name),
args: block.input || {}
}
});
};
if (callSig) {
part.thoughtSignature = callSig;
}
parts.push(part);
} else if (block.type === CLAUDE_BLOCK.TOOL_RESULT) {
let content = block.content;
if (Array.isArray(content)) {

View File

@@ -420,7 +420,6 @@ export function openaiToKiroRequest(model, body, stream, credentials) {
if (profileArn) {
payload.profileArn = profileArn;
}
if (systemPrompt) payload.systemPrompt = systemPrompt;
if (additionalModelRequestFields) {
payload.additionalModelRequestFields = additionalModelRequestFields;
}