StreamProcessor folds long text and tool-call streams in quadratic memory and time
Maintainer thường phản hồi trong vòng 1 ngày
@jherr đang làm issue này rồi.
Từ ngày 1/11/2026.
Đánh giá
Issue này chưa được đánh giá.
Mô tả
Describe the bug
StreamProcessor gets slower and heavier with every delta of one message. Two places cause it, both still in @tanstack/ai 1.0.1 (packages/ai/src/activities/chat/stream/processor.ts).
1. Text deltas hold a copy of the whole message per delta (memory and time).
handleTextMessageContentEvent, line 2343:
const nextText = delta !== '' ? currentText + delta : currentText
const textDelta = nextText.slice(currentText.length)
state.currentSegmentText = nextText
state.totalTextContent += textDelta
In V8, currentText + delta builds a rope. slice on a rope first flattens it into one buffer. For a result of 13 characters or more it then returns a sliced string that points into that buffer, so the delta is not copied out. totalTextContent concatenates those slices, and each one keeps a full flattened copy of the message alive. One message of 10,000 deltas of 16 characters (160 KB of text) holds about 760 MB after a forced GC. 5,000 deltas of 200 characters run Node out of memory at the default 4 GB heap.
textDelta always equals delta here, so the fix is:
state.currentSegmentText = nextText
state.totalTextContent += delta
2. Tool-call arguments are re-parsed in full on every delta (time).
handleToolCallArgsEvent, line 2515:
existingToolCall.arguments += chunk.delta || ''
existingToolCall.parsedArguments = this.jsonParser.parse(existingToolCall.arguments)
The partial parse is O(length of the arguments so far), so a call whose arguments arrive in n deltas costs O(n²). A tool writing a large file streams thousands of TOOL_CALL_ARGS deltas: 4,000 deltas of 100 characters take about 2 s, 10,000 about 12 s. Within the processor only getState() reads parsedArguments before TOOL_CALL_END or the completion path sets the final value. One fix is to parse on read:
// getState()
for (const [id, tc] of state.toolCalls) {
if (tc.state === 'awaiting-input' || tc.state === 'input-streaming') {
tc.parsedArguments = this.jsonParser.parse(tc.arguments)
}
toolCalls.set(id, tc)
}
Reproduction
import { StreamProcessor } from '@tanstack/ai'
import { setFlagsFromString } from 'node:v8'
import { runInNewContext } from 'node:vm'
setFlagsFromString('--expose-gc')
const gc = runInNewContext('gc')
gc()
const before = process.memoryUsage().heapUsed
const p = new StreamProcessor()
p.processChunk({ type: 'RUN_STARTED', runId: 'r', threadId: 't' })
p.processChunk({ type: 'TEXT_MESSAGE_START', messageId: 'm', role: 'assistant' })
for (let i = 0; i < 10_000; i++)
p.processChunk({ type: 'TEXT_MESSAGE_CONTENT', messageId: 'm', delta: 'x'.repeat(16) })
gc()
const held = Math.round((process.memoryUsage().heapUsed - before) / 1e6)
console.log(held, 'MB held for', p.getMessages()[0].parts[0].content.length, 'characters')
// 0.61.0, whose code here matches 1.0.1: ~800 MB. With the fix: ~1 MB.
let parsed = 0
const q = new StreamProcessor({ jsonParser: { parse: (s) => ((parsed += s.length), undefined) } })
q.processChunk({ type: 'RUN_STARTED', runId: 'r', threadId: 't' })
q.processChunk({ type: 'TEXT_MESSAGE_START', messageId: 'm', role: 'assistant' })
q.processChunk({ type: 'TOOL_CALL_START', toolCallId: 'c', toolCallName: 'Write', parentMessageId: 'm' })
for (let i = 0; i < 10_000; i++)
q.processChunk({ type: 'TOOL_CALL_ARGS', toolCallId: 'c', delta: 'x'.repeat(10) })
console.log(parsed, 'characters parsed for 100,000 characters of arguments')
// 0.61.0: 500,050,000. With the fix: 0.
Expected behavior
Folding a message of n deltas takes time and memory linear in its length.
Environment
@tanstack/ai 0.61.0 and 1.0.1, Node 24.21. We run the two fixes above as a local patch on 0.61.0 and can open a PR if that helps.
- Ngôn ngữ chính
- TypeScript
- Star
- 3.3k
- Fork
- 378
- Merge trung bình
- 1 ngày 1 phút
- Pull request đã merge (30 ngày)
- 208
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của TanStack/ai
-
openai-base: a Responses max_output_tokens cutoff ends as RUN_ERROR without usage instead of finishReason 'length'Có thể đã có người làm @tombeckenham đã nhận 4 ngày trước. Đang mởhas-pr waiting-on: maintainer
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
TanStack/ai#1916 · 1 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
openai-base: leaving a Responses stream at `response.completed` aborts its request, so complete calls are logged as client-closed (499)Có thể đã có người làm @AlemTuzlak đã nhận hôm nay. Đang mởhas-pr waiting-on: maintainer
TanStack/ai#1965 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
ai-harness: view.dispose() doesn't cancel snapshot, transcript or describe requestsCó thể đã có người làm @AlemTuzlak đã nhận 1 ngày trước. Đang mởneeds-repro waiting-on: maintainer
TanStack/ai#1960 · 2 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
ai-anthropic: 400 on signed thinking blocks after compaction rewrites history (missing block_binding drop_block)Có thể đã có người làm @tombeckenham đã nhận 1 ngày trước. Đang mởwaiting-on: maintainer
TanStack/ai#1959 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Chat UI: a fallback widget for tool calls whose names aren't known when the kit is createdCó thể đã có người làm @AlemTuzlak đã nhận 3 ngày trước. Đang mởwaiting-on: maintainer
Độ khó 4/5 1-2 ngày Mức phù hợp với người mới 58/100
TanStack/ai#1953 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
kunchenguid/gh-axi#247 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
lidge-jun/opencodex#8101 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
effort:S P3 refactor
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
nightscout/nocturne#3243 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
JuliusBrussee/caveman#1449 ·
Maintainer thường phản hồi trong vòng 1 ngày