Skip to content

fix(responses): preserve combined assistant tool turns - #4775

Open
Bosn wants to merge 4 commits into
router-for-me:devfrom
Bosn:agent/preserve-responses-assistant-turn
Open

fix(responses): preserve combined assistant tool turns#4775
Bosn wants to merge 4 commits into
router-for-me:devfrom
Bosn:agent/preserve-responses-assistant-turn

Conversation

@Bosn

@Bosn Bosn commented Aug 4, 2026

Copy link
Copy Markdown

What changed

  • preserve a Responses assistant turn containing reasoning, visible text, and one or more function calls as one Chat Completions assistant message
  • keep user-message boundaries intact when flushing pending tool calls
  • merge duplicate reasoning safely while preserving distinct reasoning content
  • add translator, streaming round-trip, and Kimi executor regression coverage

Why

The Responses request translator currently reconstructs this normalized output sequence:

reasoning -> message -> function_call(s)

as two adjacent Chat Completions assistant messages:

assistant(content + reasoning_content)
assistant(tool_calls)

That changes the original assistant turn semantics and can cause an upstream model to treat the first message as a completed reply instead of continuing the tool-use turn. The converter now reconstructs the sequence atomically as:

assistant(content + reasoning_content + tool_calls) -> tool

Validation

  • go test ./internal/translator/openai/openai/responses
  • go test ./internal/runtime/executor -run '^TestKimiNativeAssistantMessageSurvivesResponsesRoundTrip$'
  • go build -o <temporary-path>/cli-proxy-api ./cmd/server
  • git diff --check origin/dev...HEAD

Fixes #4676

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c39bf30407

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +99 to +100
if lastMessage.Get("role").String() == "assistant" && !lastMessage.Get("tool_calls").Exists() {
assistantMessage, _ := sjson.SetBytes(messages[lastIndex], "tool_calls", pendingToolCalls)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep tool calls separated across completion choices

When the Chat Completions upstream emits mixed choices—such as choice 0 returning text while choice 1 returns a tool call—the response converter intentionally serializes them as distinct output items and output indexes (openai_openai-responses_response_test.go:338-380). If that output is replayed as Responses input, this role-only check merges choice 1's tool call into choice 0's assistant text, corrupting the alternatives into a single assistant turn. The merge needs provenance indicating that the message and call came from the same choice/turn rather than merely being adjacent.

Useful? React with 👍 / 👎.

@Bosn

Bosn commented Aug 4, 2026

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4bff4993f9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

lastMessage := gjson.ParseBytes(messages[lastIndex])
if lastMessage.Get("role").String() == "assistant" &&
!lastMessage.Get("tool_calls").Exists() &&
messageProvenances[lastIndex] == pendingToolCallProvenance {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require known provenance before merging tool calls

When the response items do not use the new synthetic ID shape (for example native msg_1/fc_1 histories, or streaming chat chunks whose response id is empty so emitted IDs become unparsable like msg__0/fc__1_0), openAIResponsesOutputItemProvenance returns "" for both items. The fresh evidence in this revision is that this equality check treats two unknown provenances as the same turn, so a mixed-n replay can still merge choice 1's tool call into choice 0's assistant text instead of keeping the alternatives separate; unknown provenance needs to be distinguished from matching provenance, or synthesized before these IDs are emitted.

Useful? React with 👍 / 👎.

Bosn commented Aug 4, 2026

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 023f59adb4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
item := []byte(`{"id":"","type":"function_call","status":"completed","arguments":"","call_id":"","name":""}`)
item, _ = sjson.SetBytes(item, "id", fmt.Sprintf("fc_%s", callID))
item, _ = sjson.SetBytes(item, "id", openAIResponsesNonStreamToolItemID(id, choice.Get("index").Int(), tcIndex.Int(), false))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Generate unique IDs for parallel non-stream tool calls

When a non-streaming chat completion returns multiple tool_calls in the same choice, this builds the Responses item ID from tcIndex.Int(). In gjson ForEach array iteration does not provide the array index as the key, so tcIndex.Int() is 0 for every element; two distinct upstream calls therefore both get an ID like fc_<response>_<choice>_0 (the custom branch above has the same issue). That leaves duplicate response.output[].id values for parallel tool calls even when the upstream call_ids are unique, which breaks clients/history code that keys items by item_id; use an explicit counter or iterate over tcs.Array() with an index.

Useful? React with 👍 / 👎.

Bosn commented Aug 4, 2026

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 28ab908761

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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.

2 participants