Skip to content

fix(openai-base): pass the streamed apply_patch diff to the app - #1948

Open
peter-daptic wants to merge 1 commit into
TanStack:mainfrom
peter-daptic:fix/openai-base-streamed-apply-patch-diff
Open

peter-daptic wants to merge 1 commit into
TanStack:mainfrom
peter-daptic:fix/openai-base-streamed-apply-patch-diff

Conversation

@peter-daptic

@peter-daptic peter-daptic commented Oct 29, 2026 •

Copy link
Copy Markdown

A streamed apply_patch call reached the app with diff: "", so execute never got the patch. This PR starts a user-run call when its item is added, and ends it with its input when the item is no longer in_progress. Function calls already work this way.

🎯 Changes

  • userToolChunks in packages/openai-base/src/adapters/responses-text.ts emits TOOL_CALL_START once, when it first sees the call. It emits TOOL_CALL_END with the input only when the item status is not in_progress.
  • A unit test in packages/openai-base/tests/responses-text.test.ts streams the real event sequence: output_item.added with an empty diff, the diff deltas, then output_item.done with the full diff.
  • An E2E wire route and spec (openai-apply-patch-wire) send the same sequence through chat() and check the input that execute gets.
  • A changeset for @tanstack/openai-base.

No docs change: the fix restores the documented behavior.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with pnpm run test:pr, or these tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.
  • Docs: I updated docs/ for this change, or this change is not user-facing.
  • Changeset: I added a changeset (pnpm changeset), or this PR does not change a published package.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Root cause

Issue. An app that runs applyPatchTool() with chat() on the Responses API gets operation.diff === "" for every streamed patch. An agent loop then sends empty patches until it hits its iteration limit.

Cause. The API adds an apply_patch_call with status: "in_progress" and an empty diff, then streams the diff. processStreamChunks calls userToolChunks at response.output_item.added. userToolChunks emits TOOL_CALL_START and TOOL_CALL_END with the empty diff and marks the call ended. At output_item.done and in the response.completed fallback, the ended check skips the complete item.

Fix. userToolChunks now reads the item status. While the item is in_progress, it emits only TOOL_CALL_START. When the item is done, it emits TOOL_CALL_END with the complete input and marks the call ended.

Possible alternatives

  • Remove the userToolChunks call at output_item.added. It is a one-line fix, because output_item.done and response.completed already emit the full call. This PR did not take it because the status check states the rule directly and matches how function calls work.
  • Skip an apply_patch_call with an empty diff. It fixes this tool only. The shell tools are safe today only because their command list is empty when the item is added. A status check does not depend on that.

Testing

Commands run

  1. vitest run tests/responses-text.test.ts -t "apply_patch" in packages/openai-base. The new test fails on main and passes on this branch.
  2. vitest run in packages/openai-base: 33 files, 404 tests pass.
  3. playwright test tests/openai-apply-patch-wire.spec.ts against the e2e dev server. It fails on main and passes on this branch.
  4. playwright test for openai-computer-use-wire, openai-shell-skills-wire, computer-use and anthropic-computer-use-wire. All 12 pass on this branch.
  5. oxfmt --check on the changed files, oxlint on the changed files, and tsc for openai-base and testing/e2e. All pass.
  6. pnpm test:pr on Node 24.8.0 and pnpm 11.9.0. It passes: all targets for 30 affected projects, and test:dts. @tanstack/openai-base has 404 tests and @tanstack/ai-openai has 431 tests, and all pass.

Not run: the full E2E matrix.

Unit test on main:

 FAIL  tests/responses-text.test.ts > OpenAIBaseResponsesTextAdapter > user-executed shell and apply_patch calls > passes the streamed diff of an apply_patch_call to the app
-       "diff": "@@
- -def fib
- +def fibonacci
- ",
+       "diff": "",
      Tests  1 failed | 101 skipped (102)

Unit test on this branch:

      Tests  12 passed | 90 skipped (102)

E2E on main:

  1) [chromium] › tests/openai-apply-patch-wire.spec.ts:11:3 › openai — apply_patch wire format › runs apply_patch with the streamed diff
    -       "diff": "@@
    - -def fib
    - +def fibonacci
    - ",
    +       "diff": "",
  1 failed

E2E on this branch:

  4 passed (505ms)    # openai-apply-patch-wire, openai-computer-use-wire, openai-shell-skills-wire (2)
  8 passed (3.1s)     # computer-use, anthropic-computer-use-wire

Manual test

  1. On main, run pnpm --filter @tanstack/ai-e2e test:e2e -- tests/openai-apply-patch-wire.spec.ts. It fails: the tool gets diff: "".
  2. On this branch, run the same command. It passes: the tool gets the full diff.

How this PR makes testing easy

The unit test and the E2E spec replay the real stream, so a reviewer needs no API key.

Linked issues

Fixes #1947

Risk / rollback

Low risk. For a user-run call, TOOL_CALL_END now comes at output_item.done, not at output_item.added. An item with no status, or with any status other than in_progress, ends at once, as before. To undo, revert this PR.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Streamed patch operations now include the complete diff in the input delivered to the app, even when the diff arrives incrementally.
    • User-run tool calls now finish only after processing is complete, with their final input included.
  • Tests
    • Added coverage for streamed patch diffs and their delivery through the end-to-end flow.

@changeset-bot

changeset-bot Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fbfbec7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@tanstack/openai-base Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: TanStack/ai
  • Branch: main
  • File: .coderabbit.yaml
  • Commit: a7eb584
  • Layers: Repository YAML
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 21054684-2e2d-4317-a2d3-939175dcb50c
Commits

Reviewing files that changed from the base of the PR and between c6d5ed5 and fbfbec7.

Files selected for processing: 1
  • packages/openai-base/src/adapters/responses-text.ts
Files skipped from review as they are similar to previous changes: 1
  • packages/openai-base/src/adapters/responses-text.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Walkthrough

The adapter now waits until a user-run call is no longer in progress before emitting its end event. Unit and end-to-end tests verify that a streamed apply_patch diff reaches the tool input.

Changes: Streamed apply_patch calls

Layer / File(s) Summary
User-run call lifecycle and unit coverage
packages/openai-base/src/adapters/responses-text.ts, packages/openai-base/tests/responses-text.test.ts, .changeset/openai-streamed-apply-patch-diff.md
userToolChunks emits TOOL_CALL_START once and waits until the item is no longer in progress to emit TOOL_CALL_END with its input. A streaming test checks that the end event contains the completed diff. The changeset records a patch release.
End-to-end streamed diff validation
testing/e2e/src/routes/api.openai-apply-patch-wire.ts, testing/e2e/src/routeTree.gen.ts, testing/e2e/tests/openai-apply-patch-wire.spec.ts
A test route streams an apply_patch_call with diff events, runs the tool executor, and returns recorded inputs. The route tree registers the endpoint, and a Playwright test checks the resulting file-update operation.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: alemtuzlak, tombeckenham

Merge Risk: Minimal · up to fbfbe

No actionable issue was established with the streamed diff change; it is ready for normal merge checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title "fix(openai-base): pass the streamed apply_patch diff to the app" describes the main change. The changeset updates userToolChunks in packages/openai-base so that user-run apply_patch_call it…
Description check Passed The description follows the template well. It has a Changes section that names the modified function, the test files, the E2E route and spec, and the changeset. The checklist is filled in. The Release…
Linked Issues check Passed Issue #1947 requires streamed Responses API user-run calls to reach the app with complete input. The adapter now emits TOOL_CALL_START once and delays TOOL_CALL_END while the item status is `in_pr…
Out of Scope Changes check Passed The changes stay within issue #1947. The adapter change implements the required call lifecycle. The unit test, E2E route, E2E spec, generated route registration, and changeset test or release this fix…
Full details: Title check

Explanation

The title "fix(openai-base): pass the streamed apply_patch diff to the app" describes the main change. The changeset updates userToolChunks in packages/openai-base so that user-run apply_patch_call items emit TOOL_CALL_START when added and TOOL_CALL_END with the complete diff once the item is no longer in_progress. The title is specific, uses the conventional commit form, and matches the core fix. It is 63 characters, within a reasonable length.

Full details: Description check

Explanation

The description follows the template well. It has a Changes section that names the modified function, the test files, the E2E route and spec, and the changeset. The checklist is filled in. The Release Impact section is marked. It also adds a root cause analysis, alternatives considered, detailed test commands and results, a manual test procedure, linked issue #1947, and a risk and rollback note. The author explains why no docs change was needed. Minor gap: the template's checklist asks the author to state whether docs were updated, and the author checked the box while also stating no docs change was made, which is slightly inconsistent but still clear.

Full details: Linked Issues check

Explanation

Issue #1947 requires streamed Responses API user-run calls to reach the app with complete input. The adapter now emits TOOL_CALL_START once and delays TOOL_CALL_END while the item status is in_progress. It sends the completed input when the item status changes. The unit test replays the streamed apply_patch_call sequence and verifies the full diff. The implementation applies to the user-run call types listed in the issue.

Full details: Out of Scope Changes check

Explanation

The changes stay within issue #1947. The adapter change implements the required call lifecycle. The unit test, E2E route, E2E spec, generated route registration, and changeset test or release this fix. No unrelated change is demonstrated.

Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 1 functions across 5 files.

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@peter-daptic
peter-daptic force-pushed the fix/openai-base-streamed-apply-patch-diff branch from 2dcc38d to c6d5ed5 Compare October 29, 2026 17:30
The Responses API adds an apply_patch_call with status "in_progress" and
an empty diff, and streams the diff in before the item is done. User-run
calls were started and ended at output_item.added, so the complete item
at output_item.done was skipped and execute always got diff "".

Start a user-run call when its item is added, and end it with its input
once the item is no longer in progress, as function calls already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@peter-daptic
peter-daptic force-pushed the fix/openai-base-streamed-apply-patch-diff branch from e60a714 to fbfbec7 Compare October 29, 2026 20:02
@github-actions
github-actions Bot requested a review from tombeckenham October 30, 2026 01:02
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the PR, @peter-daptic! 🙌 @tombeckenham will take a look.

Automated pre-review checks

  • ✅ CI passing
  • ✅ No merge conflicts
  • ✅ Changeset present
  • ✅ E2E test changes included

Automated triage — a human review follows.

@github-actions github-actions Bot added the waiting-on: maintainer The ball is in the maintainers’ court label Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on: maintainer The ball is in the maintainers’ court

Projects

None yet

Development

Successfully merging this pull request may close these issues.

openai-base: streamed apply_patch calls reach the app with an empty diff

2 participants