openai-base: streamed `apply_patch` calls reach the app with an empty `diff`
Los mantenedores suelen responder en 1 día
Evaluación
Este issue todavía no se ha evaluado.
Descripción
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.
- Lenguaje dominante
- TypeScript
- Estrellas
- 3.3k
- Forks
- 378
- Merge medio
- 1 d 1 h
- PR fusionados (30 d)
- 214
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de TanStack/ai
-
openai-base: a Responses max_output_tokens cutoff ends as RUN_ERROR without usage instead of finishReason 'length'Posiblemente ocupada @tombeckenham la tomó hace 3 días. Abiertohas-pr waiting-on: maintainer
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
TanStack/ai#1916 · 1 comentario · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
ai-harness: view.dispose() doesn't cancel snapshot, transcript or describe requestsPosiblemente ocupada @AlemTuzlak la tomó hoy. Abiertoneeds-repro waiting-on: maintainer
TanStack/ai#1960 · 1 comentario · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
ai-anthropic: 400 on signed thinking blocks after compaction rewrites history (missing block_binding drop_block)Posiblemente ocupada @tombeckenham la tomó hace 1 día. Abiertowaiting-on: maintainer
TanStack/ai#1959 · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
Chat UI: a fallback widget for tool calls whose names aren't known when the kit is createdPosiblemente ocupada @AlemTuzlak la tomó hace 2 días. Abiertowaiting-on: maintainer
Dificultad 4/5 1-2 días Aptitud para principiantes 58/100
TanStack/ai#1953 · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
`ChatEngine.handleTextMessageContentEvent` is O(n²) in answer length: `startsWith(before)` copies the whole accumulated text on every chunkPosiblemente ocupada @tombeckenham la tomó hace 2 días. Abiertowaiting-on: maintainer
TanStack/ai#1951 · 1 asignado ·
Los mantenedores suelen responder en 1 día
Todos los issues de TanStack/ai
Issues similares
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Dificultad 1/5 Menos de una hora Aptitud para principiantes 61/100
lingdojo/kana-dojo#33465 · 1 comentario · 5 reacciones ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
opensheetmusicdisplay/opensheetmusicdisplay#1885 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
Los mantenedores suelen responder en 1 día
-
Time zone picker still shows America/Winnipeg as -06:00 after Manitoba’s move to permanent UTC-5Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 66/100
home-assistant/frontend#55537 ·
Los mantenedores suelen responder en 1 día
-
What's New sheet: inline rows join title and summary with no space in copied/accessibility textAbiertoneeds-triage
Dificultad 1/5 Menos de una hora Aptitud para principiantes 82/100
manaflow-ai/cmux#31347 ·
Los mantenedores suelen responder en 1 día