Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

fix: force finish_reason='tool_calls' when tool calls were emitted#8

Open
wonsik-song wants to merge 1 commit into
EvanZhouDev:main from
wonsik-song:fix/force-tool-calls-finish-reason
Open

fix: force finish_reason='tool_calls' when tool calls were emitted #8
wonsik-song wants to merge 1 commit into
EvanZhouDev:main from
wonsik-song:fix/force-tool-calls-finish-reason

Conversation

@wonsik-song

@wonsik-song wonsik-song commented Apr 19, 2026

Copy link
Copy Markdown

Summary

Codex Responses API sometimes reports finishReason as "stop" or undefined even when the turn ends with tool calls. OpenAI-compatible clients (e.g. any SDK that parses streaming chat.completions and drains tool_call deltas on finish_reason="tool_calls") silently drop accumulated tool calls when the final chunk carries finish_reason: null / "stop".

This PR overrides the mapped finish_reason to "tool_calls" in both the streaming and non-streaming paths whenever any tool calls were actually emitted during the turn.

Why this matters

OpenAI-compat clients accumulate tool_call argument deltas across streaming chunks (delta.tool_calls[].function.arguments chunks). They only flush the accumulated tool call into a real ToolCall event when the final chunk signals finish_reason="tool_calls" (or on [DONE], but clients that follow the OpenAI reference behavior rely on the former). If the proxy hands them finish_reason=null or "stop" on a turn that emitted tool_calls, the tool call is dropped and the assistant appears to return an empty response.

I hit this from a Rust client that uses the official OpenAI streaming format. Turn ends with a complete tool_calls delta stream for context_get(...), proxy emits finish_reason: null in the finish chunk, client never drains, agent loop exits with 0 tool calls, UI shows nothing.

Changes

  • packages/openai-oauth/src/chat-stream.ts — in the "finish" case, if any tool calls were emitted during the stream (toolIndexes.size > 0) and the mapped finish_reason is not already "tool_calls", override it.
  • packages/openai-oauth/src/chat-completions.ts — same defensive override in the non-streaming response builder when toolCalls.length > 0.

Test plan

  • Hit /v1/chat/completions with stream: true and a prompt that triggers a tool call → verify the finish chunk carries finish_reason: "tool_calls".
  • Same without streaming → verify the response's choices[0].finish_reason is "tool_calls".
  • Verify regular text-only responses still emit "stop" (not overridden).

🤖 Generated with Claude Code

Codex Responses API sometimes reports finishReason as 'stop' or
undefined even when the turn ends with tool calls. OpenAI-compatible
clients rely on finish_reason='tool_calls' on the final chunk to drain
tool_call deltas accumulated across streaming chunks — without it,
accumulated tool calls are silently dropped and the assistant appears
to return an empty response.
Override the mapped finish_reason to 'tool_calls' whenever the stream
emitted any tool_calls (or the non-stream response contains toolCalls)
and the upstream finishReason didn't already map to 'tool_calls'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /