Skip to content

fix(translator): merge consecutive same-role contents for Gemini - #5659

Closed
diegosouzapw wants to merge 1 commit into
release/v3.8.42from
fix/port-issue-2191-gemini-consecutive-role
Closed

diegosouzapw wants to merge 1 commit into
release/v3.8.42from
fix/port-issue-2191-gemini-consecutive-role

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Summary

  • The OpenAI→Gemini request translator now merges consecutive same-role contents[] entries before sending, so Gemini-family endpoints (incl. Antigravity / Vertex) stop rejecting valid requests.

Root cause

open-sse/translator/request/openai-to-gemini.ts::openaiToGeminiBase() built result.contents with no consecutive-same-role merge pass — unlike the Kiro (openai-to-kiro.ts) and Claude (claudeHelper.ts) request paths which already merge/alternate. Client history with two adjacent user turns — or a tool-result turn (mapped to role:"user") immediately followed by a plain user turn — produced two adjacent role:"user" entries. Gemini rejects that with 400 INVALID_ARGUMENT "Request contains consecutive messages with the same role". (The empty-parts half was already guarded by if (parts.length > 0).)

Fix

Add a small mergeConsecutiveSameRoleContents() pass that collapses adjacent same-role entries by concatenating their parts, applied right after the message-conversion loop. Normal alternating conversations are unchanged.

Attribution

Thanks to @warelik for the original report.

Test plan

  • New regression test tests/unit/translator-gemini-consecutive-role-2191.test.ts (red → green): two-user merge, tool-result-then-user alternation, alternating-unchanged.
  • Existing Gemini translator suites green (openai-to-gemini 40, claude-to-gemini 11, signatureless, thought-signature, antigravity, vertex, sse — 0 fails).
  • eslint (changed files) clean
  • npm run typecheck:core clean
  • npm run check:cycles clean
  • typecheck:noimplicit:core — only 2 pre-existing base-reds in open-sse/services/combo.ts (untouched by this PR)

…t from 9router#2191)

The OpenAI->Gemini request translator built result.contents without a
consecutive-same-role merge pass (unlike the Kiro and Claude request paths).
Client history with adjacent user turns — or a tool-result turn (mapped to
role:"user") immediately followed by a plain user turn — produced two adjacent
role:"user" entries, which Gemini-family endpoints (incl. Antigravity / Vertex)
reject with 400 INVALID_ARGUMENT "Request contains consecutive messages with the
same role". The empty-parts half was already guarded (if parts.length > 0).

Collapse adjacent same-role entries by concatenating their parts right after the
message-conversion loop. Regression guard reproduces both the two-user and the
tool-result-then-user cases and asserts a normal alternating conversation is
unchanged.

Reported-by: warelik (decolua/9router#2191)
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a helper function mergeConsecutiveSameRoleContents to merge adjacent messages with the same role when translating OpenAI requests to Gemini format, preventing 400 errors from Gemini APIs. It also adds unit tests to verify this behavior. The review feedback points out that the merge function mutates the input array's elements directly, which can cause unintended side-effects, and suggests a pure alternative using shallow copying.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +249 to +260
function mergeConsecutiveSameRoleContents(contents: GeminiContent[]): GeminiContent[] {
const merged: GeminiContent[] = [];
for (const entry of contents) {
const last = merged[merged.length - 1];
if (last && last.role === entry.role) {
last.parts.push(...entry.parts);
} else {
merged.push(entry);
}
}
return merged;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The mergeConsecutiveSameRoleContents function mutates the elements of the input contents array directly via last.parts.push(...). This introduces side-effects if the input array or its elements are referenced or reused elsewhere (e.g., in other parts of the application, caches, or tests).

To ensure the function is pure and free of side-effects, consider returning new objects and shallow copying the parts array instead of mutating the existing ones.

function mergeConsecutiveSameRoleContents(contents: GeminiContent[]): GeminiContent[] {
  const merged: GeminiContent[] = [];
  for (const entry of contents) {
    const last = merged[merged.length - 1];
    if (last && last.role === entry.role) {
      merged[merged.length - 1] = {
        ...last,
        parts: [...last.parts, ...entry.parts],
      };
    } else {
      merged.push(entry);
    }
  }
  return merged;
}

@diegosouzapw

Copy link
Copy Markdown
Collaborator Author

Superseded by #5661. Base release/v3.8.42 was released (tagged + squashed to main) by a parallel /generate-release mid-session; the active release is now release/v3.8.43. Re-cut the same fix cleanly onto release/v3.8.43 in #5661 (identical 3-file diff). Closing this one.

@diegosouzapw
diegosouzapw deleted the fix/port-issue-2191-gemini-consecutive-role branch June 30, 2026 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant