Skip to content

Conversation

@houssemzaier
Copy link
Contributor

@houssemzaier houssemzaier commented Jan 19, 2026

Fixes #8773

When GPT-5 makes parallel tool calls via the Responses API, each function_call has a unique call_id (fc_*). These IDs were being overwritten instead of accumulated, causing:

"No tool call found for function call output with call_id fc_xxx"

Changes

  • gui/src/redux/slices/sessionSlice.ts: Accumulate IDs in responsesOutputItemIds[] array during streaming
  • core/llm/openaiTypeConverters.ts: Emit function_call for each tool call with positional ID matching
  • core/llm/openaiTypeConverters.test.ts: Added comprehensive tests for parallel tool call scenarios

How it works

  1. During streaming, when fromResponsesChunk sets message.metadata.responsesOutputItemId, we now accumulate these into an array responsesOutputItemIds[]
  2. When converting to Responses API input format, we iterate over all tool calls and match each one with its corresponding fc_ ID by position
  3. This ensures each parallel tool call gets its correct call_id when sent back to OpenAI

Test plan

  • Unit tests pass (14 new tests covering parallel tool calls, edge cases)
  • Manual testing with GPT-5 and GPT-5.1 parallel tool calls

Related: #8935


Continue Tasks

Status Task Actions
▶️ Queued Update docs on PR View

Powered by Continue


Summary by cubic

Fixes lost call_id mapping for parallel tool calls in the OpenAI Responses API, preventing “No tool call found for function call output” errors and restoring correct pairing between function_call and tool outputs.

  • Bug Fixes
    • Accumulate fc_ IDs during streaming in responsesOutputItemIds[] (keeps the singular field for backward compatibility).
    • Emit a function_call item for each tool call, matching IDs by position; also emit assistant text when present.
    • Add unit tests covering parallel calls, legacy scenarios, and edge cases.

Written for commit 4ad4a6e. Summary will update on new commits.

When GPT-5 makes parallel tool calls, each function_call has a unique
call_id (fc_*). These IDs were being overwritten instead of accumulated,
causing the error: "No tool call found for function call output with call_id"

Changes:
- sessionSlice.ts: Accumulate IDs in responsesOutputItemIds[] array
- openaiTypeConverters.ts: Emit function_call for EACH toolCall with matching ID

Ref: https://platform.openai.com/docs/guides/function-calling
@houssemzaier houssemzaier requested a review from a team as a code owner January 19, 2026 12:43
@houssemzaier houssemzaier requested review from sestinj and removed request for a team January 19, 2026 12:43
@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Jan 19, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 3 files

Copy link
Collaborator

@RomneyDa RomneyDa left a comment

Choose a reason for hiding this comment

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

@houssemzaier thanks for this contribution. I don't fully understand why the call IDs are being lost, and I wonder if it's a bug in the conversion logic which doesn't require adding an additional metadata. Our data model and many of our providers support parallel tool calls without issues. Could you clarify why this approach is needed rather than just fixing the overwriting bug?

@github-project-automation github-project-automation bot moved this from Todo to In Progress in Issues and PRs Jan 26, 2026
@houssemzaier
Copy link
Contributor Author

Thanks for the review @RomneyDa!

To reproduce the issue:

  1. Use GPT-5 or GPT-5.1 with the Responses API
  2. Configure an MCP server with filesystem tools
  3. Create a folder with 3+ files (e.g. /manual-testing-sandbox)

Prompt:
Using my Local MCP server: Read the first 50 lines of every file in this workspace.

What happens without the fix:
The model issues parallel read_file calls. Each one gets a unique fc_* ID from OpenAI (e.g. fc_001, fc_002, fc_003). When we send tool results back, OpenAI returns:

"No tool call found for function call output with call_id fc_xxx"

Why this happens:
The fc_* IDs are server-assigned by the Responses API and must be echoed back exactly, they can't be regenerated client-side.

The overwriting bug exists because Continue merges parallel tool calls into a single ChatMessage with a toolCalls[] array. With 3 parallel tool calls you end up with:

  • 1 ChatMessage
  • 3 entries in toolCalls[]
  • but only 1 responsesOutputItemId field (which gets overwritten by the last call)

Why this fix:
The array is the smallest change that correctly stores N IDs for N tool calls. It's also backwards-compatible: the existing singular field is still populated for any code that relies on it.

Why not change the data model?
We could emit separate ChatMessages per tool call, but that's a much bigger refactor across the codebase and would likely break existing provider implementations.

If you'd prefer a different approach, I'm happy to align, but I'd like to keep this PR scoped to the minimal/compatible fix so we don't have to rework the whole tool-call pipeline.

@RomneyDa
Copy link
Collaborator

RomneyDa commented Jan 26, 2026

The fc_* IDs are server-assigned by the Responses API and must be echoed back exactly, they can't be regenerated client-side.

The overwriting bug exists because Continue merges parallel tool calls into a single ChatMessage with a toolCalls[] array. With 3 parallel tool calls you end up with:

1 ChatMessage
3 entries in toolCalls[]
but only 1 responsesOutputItemId field (which gets overwritten by the last call)

@houssemzaier I might be missing something but is the responseOutputItemId fundamentally different than the toolCallId? I'm just questioning if we need responseOutputItemId at all, since each tool call already has an ID which is typically set on the provider side and we usually just translate between the two. You're right that this would change existing tool call pipeline but I think the changes would be minimal. Remove from redux, fix whatever translation isn't happening where it should be in responses code. Probably same ish PR size

If there is a difference in the nature of the IDs being stored than I would agree that this is the right approach

@houssemzaier
Copy link
Contributor Author

@RomneyDa Yep ! there are two different identifiers on a Responses API function_call:

  • call_id (call_abc123): the tool-call id you must echo back in function_call_output
  • id (fc_xyz789): the function_call item id (aka item_id in streaming events)

For returning the tool result, only call_id is used for pairing:

{
"type": "function_call_output",
"call_id": "call_abc123",
"output": "..."
}

The 400 happens when OpenAI can't find a pending function_call matching that call_id , which usually means the next request doesn't include the original function_call item(s) in the replayed history (or it got altered during
reconstruction). Since toResponsesInput() replays history (stateless mode), we need to preserve and replay the original function_call items verbatim (including their id + call_id) instead of regenerating them.

That's why we keep the streamed function_call item ids around for parallel calls.

Ref: https://community.openai.com/t/issue-with-new-responses-api-400-no-tool-call-found-for-function-call-output-with-call-id/1142327

@RomneyDa
Copy link
Collaborator

@houssemzaier thanks for explaining! Then looks good to me.

@RomneyDa
Copy link
Collaborator

Looks like a linting check is failing, may need to split up a function or something. Our nesting rule is a bit strict

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

No tool call found for function call output with call_id

2 participants