openai-base: streamed `apply_patch` calls reach the app with an empty `diff`
I maintainer di solito rispondono entro 1 giorno
Valutazione
Questa issue non è ancora stata valutata.
Descrizione
TanStack AI version
@tanstack/ai 0.68.0, @tanstack/ai-openai 0.29.1, @tanstack/openai-base 0.13.1. The same code is on main (@tanstack/openai-base 1.0.0, caaa97b).
Framework/Library version
Node 26.10, Next.js 16.3 (server-side chat()), openai 5.18
Describe the bug and the steps to reproduce it
When the Responses API streams an apply_patch_call, chat() runs the app's apply_patch tool with operation.diff === "". The real diff never reaches the app.
The streamed events for one patch look like this. They were captured from real responses from two models (gpt-5.6 and gpt-6.1), served by Azure OpenAI through a LiteLLM proxy. Both models produced the same sequence:
response.output_item.added item: { type: "apply_patch_call", status: "in_progress",
operation: { type: "update_file", path: "document.json", diff: "" } }
response.apply_patch_call_operation_diff.delta "@@"
response.apply_patch_call_operation_diff.delta "\n"
…
response.apply_patch_call_operation_diff.done
response.output_item.done item: { type: "apply_patch_call", status: "completed",
operation: { type: "update_file", path: "document.json",
diff: "@@\n- \"title\": \"Draft memo\",\n+ \"title\": \"Final memo\",\n" } }
response.completed
processStreamChunks calls userToolChunks(item, …) on response.output_item.added (responses-text.ts#L2029). At that point the item's diff is still "". userToolChunks emits TOOL_CALL_START and TOOL_CALL_END with that input and marks the call ended (#L1598–L1614). When response.output_item.done arrives with the complete diff, the ?.ended check makes userToolChunks return nothing (#L2243). So does the response.completed fallback (#L2426).
In an agent loop this fails silently. The model keeps sending patches that never arrive, until the iteration limit stops the loop.
There's no way around streamed Responses for this tool: Chat Completions rejects it (Invalid value: 'apply_patch'. Supported values are: 'function' and 'custom'.), so apply_patch only works through the Responses API.
The other user-run tools are protected by accident. readUserExecutedCall returns null for a local_shell_call or shell_call until its command list is non-empty, so an incomplete item at output_item.added is skipped and picked up later at output_item.done. apply_patch_call only checks that operation.diff is a string, and "" passes.
The existing test, user-executed shell and apply_patch calls in packages/openai-base/tests/responses-text.test.ts, doesn't catch this. It only sends response.created and then response.completed, with the finished item, so the output_item.added path never runs.
Steps to reproduce
- Call
chat()with an OpenAI Responses adapter andtools: [{ ...applyPatchTool(), execute }], whereexecutelogsoperation.diffand returns{ status: "completed", output: "ok" }. - Ask for a one-line edit to a file whose contents are in the prompt.
executelogs""every time. The model keeps callingapply_patchuntilmaxIterationsstops it.
Suggested fix
Handle user-run calls the way function_call is already handled. For a function call, output_item.added emits only TOOL_CALL_START, and TOOL_CALL_END (with the input) waits until the arguments are complete. User-run calls get both START and END from the first item seen.
The check is the item's own status. The apply_patch_call item arrives at output_item.added with status: "in_progress" and is "completed" at output_item.done and in response.completed. So userToolChunks can emit TOOL_CALL_START while the item is in_progress, and emit TOOL_CALL_END with the input once it isn't. This covers every user-run type (apply_patch_call, local_shell_call, shell_call, computer_call) and doesn't depend on each type's content looking incomplete.
The narrowest fix is to remove the userToolChunks call at L2029, since output_item.done and the response.completed fallback already emit the complete call.
Open question: should an item that ends "incomplete" (for example, the response hit max_output_tokens) be handed to the app at all? A truncated diff probably shouldn't be applied.
Workaround for anyone hitting this: wrap the OpenAI client's responses.create and drop response.output_item.added events whose item.type is apply_patch_call. The call is then emitted from output_item.done, with the full diff.
Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
An E2E wire route that streams the sequence above to the adapter, with a spec that fails on main: api.openai-apply-patch-wire.ts and openai-apply-patch-wire.spec.ts. The PR adds the same sequence as a unit test in packages/openai-base/tests/responses-text.test.ts.
Do you intend to try to help solve this bug with your own PR?
Yes, I am also opening a PR that solves the problem along side this issue
Terms & Code of Conduct
- I agree to follow this project's Code of Conduct
- I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.
- Lingua principale
- TypeScript
- Stelle
- 3.2k
- Fork
- 372
- Merge medio
- 1g 5h
- PR unite (30g)
- 233
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di TanStack/ai
-
openai-base: a Responses max_output_tokens cutoff ends as RUN_ERROR without usage instead of finishReason 'length'Forse già presa @tombeckenham l’ha presa 1 giorno fa. Apertahas-pr waiting-on: maintainer
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
TanStack/ai#1916 · 1 commento · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 1-2 giorni Idoneità per principianti 58/100
I maintainer di solito rispondono entro 1 giorno
-
`ChatEngine.handleTextMessageContentEvent` is O(n²) in answer length: `startsWith(before)` copies the whole accumulated text on every chunkForse già presa @tombeckenham l’ha presa oggi. Apertawaiting-on: maintainer
TanStack/ai#1951 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
needsApproval client tools can't be approved without a server (custom connection adapters)Forse già presa @jherr l’ha presa oggi. Apertawaiting-on: maintainer
TanStack/ai#1945 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
feat: live speech-to-text sessions for voice and telephony agentsForse già presa @AlemTuzlak l’ha presa 1 giorno fa. Apertawaiting-on: maintainer
TanStack/ai#1944 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
Issue simili
-
bug
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 86/100
awslabs/visual-asset-management-system#937 ·
I maintainer di solito rispondono entro 1 giorno
-
t0ggles
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 82/100
nolimits4web/swiper#8255 · 1 commento ·
I maintainer di solito rispondono entro 15 giorni
-
render: --variables-file (and --variables <path>) fails with "io.readFile is not a function" since 0.8.212Forse già presa @miguel-heygen l’ha presa oggi. Apertabug triage/needs-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
heygen-com/hyperframes#6830 ·
I maintainer di solito rispondono entro 1 giorno
-
bug launch local use
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
I maintainer di solito rispondono entro 1 giorno
-
account: profile bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 5 giorni