Problem
LLM streaming protocols (OpenAI, Anthropic) deliver tool-call arguments as deltas:
- OpenAI:
choices[].delta.tool_calls[] with index, function.name (first chunk), function.arguments (concat across chunks)
- Anthropic:
content_block_start → content_block_delta (input_json_delta.partial_json chunks) → content_block_stop
Every consumer rewrites the merge logic. Misina can ship two helpers and a generic accumulator.
Proposal
misina/stream adds:
// Generic stream consumer with reducer
const final = await stream.collect(reducer, initial)
// Provider-specific
import { accumulateOpenAIToolCalls, accumulateAnthropicMessage } from 'misina/stream'
const toolCalls = await accumulateOpenAIToolCalls(sseStream(response))
const message = await accumulateAnthropicMessage(sseStream(response))
Both helpers stop at the appropriate sentinel ([DONE] for OpenAI, message_stop for Anthropic). They're 50-80 lines each.
Acceptance criteria
Refs
Problem
LLM streaming protocols (OpenAI, Anthropic) deliver tool-call arguments as deltas:
choices[].delta.tool_calls[]withindex,function.name(first chunk),function.arguments(concat across chunks)content_block_start→content_block_delta(input_json_delta.partial_jsonchunks) →content_block_stopEvery consumer rewrites the merge logic. Misina can ship two helpers and a generic accumulator.
Proposal
misina/streamadds:Both helpers stop at the appropriate sentinel (
[DONE]for OpenAI,message_stopfor Anthropic). They're 50-80 lines each.Acceptance criteria
collect(reducer, initial)async iterator helperindex, name, partial JSON arguments)Refs