You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
🚀 Feature Request: No public way to send a message the way CopilotChat's input does #7859
I have searched existing issues, and this hasn't been requested yet.
Problem or Motivation
There is no public way for code outside the composer to send a message exactly the way CopilotChat's input does.
The use case is components rendered inside tool calls that offer follow-up actions, such as a suggestion chip or a "Compare with last quarter" button in a card. The documented programmatic path is agent.addMessage(...) then copilotkit.runAgent({ agent }) (programmatic control; it is also the answer given in #4215). On most chats that matches what the input does. On a chat with native intelligence run activity it does not, because the input also marks its run as local, and only CopilotChat can do that.
What the input does (v1.79.1, unchanged on main as of 7b446f8):
onSubmitInput (L891-L1006) and the suggestion path handleSelectSuggestion (L1008-L1067) add the message, generate a runId, add it to activeLocalRunIdsRef, then call copilotkit.runAgent({ agent, runId }) (L963-L975).
activeLocalRunIdsRef is a useRef inside CopilotChat (L334-L337). A run-activity notification is treated as local only if its id is on that set or on the 30-second recent list (L404-L425, used at L766). The set is also checked when deciding whether a run is still active (L676-L677).
The submit function leaves CopilotChat only as the onSubmitMessage slot prop (L1320) and the suggestion handler as onSelectSuggestion (L1219). Neither is exposed through useCopilotChatConfiguration, another context, or tool render props.
Why passing our own runId doesn't help:runAgent accepts a public runId (run-handler.ts L50-L57), but that id never reaches the private set, so the run is still treated as remote.
Side by side:
// What a component inside a tool render can do with public APIsconstFollowUps=({ prompts }: {prompts: string[]})=>{const{ agent }=useAgent();const{ copilotkit }=useCopilotKit();constsend=(text: string)=>{agent.addMessage({id: crypto.randomUUID(),role: "user",content: text});voidcopilotkit.runAgent({ agent,runId: crypto.randomUUID()});// id stays unregistered};return<>{prompts.map((p)=><buttonkey={p}onClick={()=>send(p)}>{p}</button>)}</>;};useFrontendTool({name: "show_follow_ups",parameters: z.object({prompts: z.array(z.string())}),handler: async()=>({displayed: true}),render: ({ args })=><FollowUpsprompts={args.prompts??[]}/>,});// What CopilotChat's input does (simplified from onSubmitInput)agent.addMessage({id: randomUUID(),role: "user",content: value});constlocalRunId=hasNativeIntelligenceRunActivity ? randomUUID() : undefined;if(localRunId)activeLocalRunIdsRef.current.add(localRunId);// private to CopilotChatawaitcopilotkit.runAgent({ agent, ...(localRunId ? {runId: localRunId} : {})});
Consequence today. This is read from the source; we have not observed it on an intelligence-runtime host. It applies only when hasNativeIntelligenceRunActivity holds (L351-L355: explicit threadId, connected runtime, intelligence.wsUrl, threadEndpoints.realtimeMetadata === true). We expect the run's start and finish notifications to be handled as another client's run: the chat marks a reconnect pending, waits for the agent to go idle, and then calls connectAgent({ agent }) (L676-L695, L697-L759). It may connect a second time if the finish notification arrives during the first connect. That is the path meant for a message sent from another tab or device, so a send from inside the chat gets an extra re-sync the input's own send does not.
This was also present in 1.76.0.
🎤 Tell us your idea
Either of these would let a component send the way the input does:
Expose the chat's send. Put the submit function (or a sendMessage(text) wrapping it) on the chat configuration context, e.g. useCopilotChatConfiguration(), so any component under a CopilotChat sends exactly as the input does, including run registration and send serialization.
Let a caller register a run as local. For example, a documented option on runAgent that CopilotChat honours, or a small registerLocalRun(runId) on the chat configuration, so a run started with the documented addMessage + runAgent path is not mistaken for a remote one.
Option 2 is the smaller change, and it keeps the documented programmatic path equivalent to the input on every host. Option 1 would also give tool renders the input's queueing behaviour for free.
Who would find this useful: anyone rendering interactive components in tool calls (suggestion chips, "do this next" buttons, quick replies) on a chat that uses the intelligence runtime's thread sync.
Would you like to work on this?
Yes, I'd love to work on it!
I'm open to collaborating but need guidance.
No, I'm just sharing the idea.
Environment: @copilotkit/react-core 1.79.1 (/v2 entry), React 19. Also present in 1.76.0.
This is a well-scoped gap. The source confirms exactly what you've described: activeLocalRunIdsRef is private to CopilotChat, neither onSubmitInput nor handleSelectSuggestion is reachable from outside the component, and copilotkit.runAgent({ agent, runId }) does not register the caller-supplied id into that set (CopilotChat.tsx L334–337, L963–975, L1008–1067).
The consequence you've traced — a programmatic send being treated as a remote run and triggering a reconnect cycle — follows from that structure. Whether it manifests the way you expect on a live intelligence-runtime host is something engineering will need to verify, but the structural gap (no public path to register a run as local, no public path to invoke the input's send with its queueing) is real and visible in the code.
Flagging this for the maintainers to assess which surface — a sendMessage on the chat configuration context, a registerLocalRun option on runAgent, or something else — fits best with the roadmap. Your option 2 analysis (smaller delta, keeps the documented addMessage + runAgent path equivalent) is a useful framing for that conversation.
🤖 Generated by CopilotKit AI Support · Documentation
Thanks for the triage. I'd be happy to implement this and open a PR.
My proposal is to start with option 2, the smaller change: a way to register a caller-supplied runId as local. That would be either a documented option on runAgent that CopilotChat honours, or a registerLocalRun(runId) on the chat configuration context. Both keep the documented addMessage + runAgent path equivalent to the input's send without adding a send wrapper, which I understand from #4215 you'd rather not take on. Tests would cover the native-intelligence path in CopilotChat, the commits would follow your conventional-commit rules, and there would be no changeset.
Could a maintainer confirm which surface you'd accept, or point me to a different one? I'll wait for that before starting.
Thanks for the detailed trace. The gap is real on current main, but we'd rather not add either surface. The documented addMessage + runAgent path should just behave like the input, so the fix belongs inside CopilotChat: a run this client started through runAgent should count as local, not only the ones the input registers.
If you want to take that on, go ahead. Please start with a test next to CopilotChat.runActivityReconnect.test.tsx that starts a run via runAgent and shows the extra reconnect, then the fix that makes it go away.
Thanks @lukasmoschitz , that makes sense. I'll start with the regression test alongside CopilotChat.runActivityReconnect.test.tsx, verify the extra reconnect, and then work on the fix within CopilotChat without introducing a new public API.
Activity
copilotkit-outpost-bot commentedon Oct 18, 2026
This is a well-scoped gap. The source confirms exactly what you've described:
activeLocalRunIdsRefis private toCopilotChat, neitheronSubmitInputnorhandleSelectSuggestionis reachable from outside the component, andcopilotkit.runAgent({ agent, runId })does not register the caller-supplied id into that set (CopilotChat.tsxL334–337, L963–975, L1008–1067).The consequence you've traced — a programmatic send being treated as a remote run and triggering a reconnect cycle — follows from that structure. Whether it manifests the way you expect on a live intelligence-runtime host is something engineering will need to verify, but the structural gap (no public path to register a run as local, no public path to invoke the input's send with its queueing) is real and visible in the code.
Flagging this for the maintainers to assess which surface — a
sendMessageon the chat configuration context, aregisterLocalRunoption onrunAgent, or something else — fits best with the roadmap. Your option 2 analysis (smaller delta, keeps the documentedaddMessage+runAgentpath equivalent) is a useful framing for that conversation.🤖 Generated by CopilotKit AI Support · Documentation
Was this helpful? React with 👍 or 👎
HoussemDjeghri commentedon Oct 18, 2026
Thanks for the triage. I'd be happy to implement this and open a PR.
My proposal is to start with option 2, the smaller change: a way to register a caller-supplied
runIdas local. That would be either a documented option onrunAgentthatCopilotChathonours, or aregisterLocalRun(runId)on the chat configuration context. Both keep the documentedaddMessage+runAgentpath equivalent to the input's send without adding a send wrapper, which I understand from #4215 you'd rather not take on. Tests would cover the native-intelligence path inCopilotChat, the commits would follow your conventional-commit rules, and there would be no changeset.Could a maintainer confirm which surface you'd accept, or point me to a different one? I'll wait for that before starting.
lukasmoschitz commentedon Oct 19, 2026
Thanks for the detailed trace. The gap is real on current main, but we'd rather not add either surface. The documented
addMessage+runAgentpath should just behave like the input, so the fix belongs inside CopilotChat: a run this client started throughrunAgentshould count as local, not only the ones the input registers.If you want to take that on, go ahead. Please start with a test next to
CopilotChat.runActivityReconnect.test.tsxthat starts a run viarunAgentand shows the extra reconnect, then the fix that makes it go away.HoussemDjeghri commentedon Oct 19, 2026
Thanks @lukasmoschitz , that makes sense. I'll start with the regression test alongside
CopilotChat.runActivityReconnect.test.tsx, verify the extra reconnect, and then work on the fix within CopilotChat without introducing a new public API.