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

@@ -138,12 +138,14 @@ function convertContent(content) {
// Text with thoughtSignature = regular text after thinking
if (part.thoughtSignature && part.text !== undefined) {
textParts.push({ type: OPENAI_BLOCK.TEXT, text: part.text });
if (part.text) {
textParts.push({ type: OPENAI_BLOCK.TEXT, text: part.text });
}
continue;
}
// Regular text
if (part.text !== undefined) {
if (part.text !== undefined && part.text !== "") {
textParts.push({ type: OPENAI_BLOCK.TEXT, text: part.text });
}
@@ -180,8 +182,22 @@ function convertContent(content) {
}
}
// Content with only functionResponses → return array of tool messages
// Content with functionResponses — return array of tool result messages,
// plus an assistant message for any co-located tool calls / text.
if (toolResults.length > 0) {
if (toolCalls.length > 0 || textParts.length > 0 || reasoningContent) {
const assistantMsg = { role: ROLE.ASSISTANT };
if (textParts.length > 0) {
assistantMsg.content = collapseTextParts(textParts);
}
if (reasoningContent) {
assistantMsg.reasoning_content = reasoningContent;
}
if (toolCalls.length > 0) {
assistantMsg.tool_calls = toolCalls;
}
return [...toolResults, assistantMsg];
}
return toolResults;
}

View File

@@ -393,9 +393,12 @@ export function claudeToKiroRequest(model, body, stream, credentials) {
reconcileOrphanedToolResults(history, currentMessage);
}
// API-key auth must never use the shared default ARN (403); OAuth/social fall back to it.
// api_key / idc / external_idp must never use the shared default ARN (belongs
// to another account → 403 "bearer token invalid"); OAuth/social fall back to it.
const authMethod = credentials?.providerSpecificData?.authMethod;
const profileArn = authMethod === "api_key"
const accountBoundAuth =
authMethod === "api_key" || authMethod === "idc" || authMethod === "external_idp";
const profileArn = accountBoundAuth
? (credentials?.providerSpecificData?.profileArn || "")
: (credentials?.providerSpecificData?.profileArn || resolveDefaultProfileArn(authMethod));

View File

@@ -129,8 +129,24 @@ function fixMissingToolResponsesOpenAI(messages) {
}
}
// Wrap mid-conversation system text so it ends as a user turn (avoids Anthropic prefill 400)
function systemReminderText(content) {
const parts = Array.isArray(content)
? content.filter(c => c?.type === CLAUDE_BLOCK.TEXT).map(c => c.text || "")
: [typeof content === "string" ? content : ""];
const text = parts.filter(Boolean).join("\n");
if (!text.trim()) return "";
return `<system-reminder>\n${text}\n</system-reminder>`;
}
// Convert single Claude message - returns single message or array of messages
function convertClaudeMessage(msg) {
// Mid-conversation system message -> user (per Anthropic placement rules)
if (msg.role === ROLE.SYSTEM) {
const text = systemReminderText(msg.content);
return text ? { role: ROLE.USER, content: text } : null;
}
const role = msg.role === ROLE.USER || msg.role === ROLE.TOOL ? ROLE.USER : ROLE.ASSISTANT;
// Simple string content

View File

@@ -35,6 +35,17 @@ function sanitizeGeminiFunctionName(name) {
return sanitized.substring(0, 64);
}
function normalizeGeminiContents(contents) {
const out = [];
for (const c of contents || []) {
if (!c?.role || !Array.isArray(c.parts) || c.parts.length === 0) continue;
const last = out.at(-1);
if (last?.role === c.role) last.parts.push(...c.parts);
else out.push({ ...c, parts: [...c.parts] });
}
return out;
}
// Core: Convert OpenAI request to Gemini format (base for all variants)
function openaiToGeminiBase(model, body, stream, signature = DEFAULT_THINKING_AG_SIGNATURE) {
const result = {
@@ -217,6 +228,7 @@ function openaiToGeminiBase(model, body, stream, signature = DEFAULT_THINKING_AG
}
}
result.contents = normalizeGeminiContents(result.contents);
return result;
}
@@ -299,7 +311,7 @@ function wrapInCloudCodeEnvelope(model, geminiCLI, credentials = null, isAntigra
}
// Wrap Claude format in Cloud Code envelope for Antigravity
function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = null) {
function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = null, signature = DEFAULT_THINKING_AG_SIGNATURE) {
const projectId = credentials?.projectId || generateProjectId();
const envelope = {
@@ -343,6 +355,7 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu
parts.push({ text: block.text });
} else if (block.type === CLAUDE_BLOCK.TOOL_USE) {
parts.push({
thoughtSignature: signature,
functionCall: {
id: block.id,
name: sanitizeGeminiFunctionName(block.name),
@@ -425,6 +438,7 @@ function wrapInCloudCodeEnvelopeForClaude(model, claudeRequest, credentials = nu
envelope.request.systemInstruction = { role: GEMINI_ROLE.USER, parts: systemParts };
}
envelope.request.contents = normalizeGeminiContents(envelope.request.contents);
return envelope;
}

View File

@@ -530,8 +530,15 @@ export function openaiToKiroRequest(model, body, stream, credentials) {
// (the ARN doesn't belong to the key's account). So for api_key, only send a
// profileArn that was actually resolved for this connection — never the default.
// OAuth/social keep the default fallback (their tokens accept it).
// api_key / idc / external_idp carry an account-specific (or token-bound)
// profile. The shared builder-id/social default ARN belongs to a different
// account and triggers 403 "bearer token invalid", so never fall back to it —
// send the resolved ARN, or an empty string so CodeWhisperer uses the token's
// own default profile. Only OAuth/social keep the shared placeholder.
const authMethod = credentials?.providerSpecificData?.authMethod;
const profileArn = authMethod === "api_key"
const accountBoundAuth =
authMethod === "api_key" || authMethod === "idc" || authMethod === "external_idp";
const profileArn = accountBoundAuth
? (credentials?.providerSpecificData?.profileArn || "")
: (credentials?.providerSpecificData?.profileArn || resolveDefaultProfileArn(authMethod));