Observation-class. Nothing a user hits today; filing so it is on the record rather than rediscovered.
packages/plugin-chatbot/src/useObjectChat.ts memoizes the transport:
consttransport=useMemo(()=>{if(!isApiMode)returnundefined;returnnewDefaultChatTransport({api: api!, ... });},[isApiMode,api,headers,body,model,systemPrompt,streamingEnabled,conversationId]);body and headers are object props, and every caller passes a fresh literal on each render. AiChatPage's ChatPane is the clearest case:
body: {context: {activeApp: 'AI',agentName: activeAgent,autoPublishAiBuilds: getRuntimeConfig().features.autoPublishAiBuilds,
...(editPackageId ? {packageId: editPackageId} : {}),},},That object has a new identity on every render, so the memo never hits and a DefaultChatTransport is constructed on every render of every chat surface — during a streaming turn, that is once per token batch.
Why it is dormant rather than broken
@ai-sdk/react@4.0.59's useChat does not rebuild its Chat when the transport identity changes. It stores the latest transport in a ref and its own chatOptions.transport delegates through getTransport():
latestRef.current={ ...,transport: options.transport};constchatOptions={ ...,transport: {sendMessages: (o)=>getTransport().sendMessages(o), ... }};constshouldRecreateChat="chat"inoptions&&options.chat!==chatRef.current||"id"inoptions&&options.id!=null&&chatRef.current.id!==options.id;useObjectChat passes neither chat nor id, so the Chat object — and with it the whole message list — survives. The wasted construction is the entire cost.
Why it is worth recording anyway
The safety here is entirely a property of the SDK's internals, not of this call site. The memo looks like it is protecting something, and the dep list reads as if a changing transport would be disruptive. An SDK upgrade that starts keying the Chat on the transport (or a future id being threaded through) would turn a silent inefficiency into a thread that resets on every render — and the memo would give a reviewer no warning, because it is already written as though it works.
Two candidate directions, neither obviously right without measuring:
- memoize at the call sites (
body / headers behind useMemo), which fixes it wherever it is done but is unenforceable and will re-rot; - drop
body/headers from the dep list and read them through a ref inside prepareSendMessagesRequest, which is what the file already does for model (modelRef) and for the handoff id (parentConvRef) — the established idiom in this very hook, and the only one that cannot be undone by a new caller.
Found while working #2627 (the AI build thread's blank-out was a different mechanism entirely — a pane remount driven by useChatConversation clearing the conversation id; PR #4183). This was ruled out as its cause and is unrelated to it.
Observation-class. Nothing a user hits today; filing so it is on the record rather than rediscovered.
packages/plugin-chatbot/src/useObjectChat.tsmemoizes the transport:bodyandheadersare object props, and every caller passes a fresh literal on each render.AiChatPage'sChatPaneis the clearest case:That object has a new identity on every render, so the memo never hits and a
DefaultChatTransportis constructed on every render of every chat surface — during a streaming turn, that is once per token batch.Why it is dormant rather than broken
@ai-sdk/react@4.0.59'suseChatdoes not rebuild itsChatwhen the transport identity changes. It stores the latest transport in a ref and its ownchatOptions.transportdelegates throughgetTransport():useObjectChatpasses neitherchatnorid, so theChatobject — and with it the whole message list — survives. The wasted construction is the entire cost.Why it is worth recording anyway
The safety here is entirely a property of the SDK's internals, not of this call site. The memo looks like it is protecting something, and the dep list reads as if a changing transport would be disruptive. An SDK upgrade that starts keying the
Chaton the transport (or a futureidbeing threaded through) would turn a silent inefficiency into a thread that resets on every render — and the memo would give a reviewer no warning, because it is already written as though it works.Two candidate directions, neither obviously right without measuring:
body/headersbehinduseMemo), which fixes it wherever it is done but is unenforceable and will re-rot;body/headersfrom the dep list and read them through a ref insideprepareSendMessagesRequest, which is what the file already does formodel(modelRef) and for the handoff id (parentConvRef) — the established idiom in this very hook, and the only one that cannot be undone by a new caller.Found while working #2627 (the AI build thread's blank-out was a different mechanism entirely — a pane remount driven by
useChatConversationclearing the conversation id; PR #4183). This was ruled out as its cause and is unrelated to it.