Files
openhuman/src/core
Steven EnamakelandGitHub 2824850708 feat(streaming): thinking tokens, tool call deltas, and progressive channel edits (#549)
* feat(conversations): implement live streaming for assistant responses

- Added support for live streaming of assistant responses in the Conversations component, enhancing user experience during interactions.
- Introduced new interfaces for handling streaming state, including `StreamingAssistantState` and related event handlers for text, thinking, and tool argument deltas.
- Updated the state management to accommodate streaming data, ensuring smooth updates to the UI as new content arrives.
- Enhanced the rendering logic to display provisional assistant bubbles and thinking indicators while responses are being composed.
- Refactored existing event handling to integrate with the new streaming functionality, improving overall responsiveness and interactivity.

This commit significantly improves the real-time interaction capabilities of the assistant, providing users with immediate feedback during conversations.

* feat(channels): progressive-edit streaming for inbound channels

ChannelInboundSubscriber now buffers text/tool delta events on a 1s
timer and edits the outbound channel message in place, so Telegram/
Slack-style conduits can show a single evolving reply instead of a
single atomic bubble at the end. Renders a "🔧 tool …" status line
above the partial text and rewrites with the final canonical reply
on chat_done.

Falls back to atomic-final delivery if the backend's PATCH
/channels/:channel/messages/:id endpoint is unavailable or repeatedly
fails, so existing channels keep working while the edit endpoint is
rolled out.

New rest.rs helper: BackendOAuthClient::send_channel_edit.

* style: cargo fmt on streaming-related files

Auto-applied by cargo fmt after the streaming plumbing changes touched
provider traits, the session turn loop, the channel inbound subscriber,
and the compatible-provider SSE path.

* feat(streaming): compact live preview + Telegram typing indicator

UI: while streaming, show only the trailing ~120 chars of assistant
text in a monospace ticker-tape bubble (cursor + ellipsis prefix) so
the scroll position doesn't jump as tokens arrive. The full response
still replaces the preview via addInferenceResponse on chat_done.

Backend bridge: ChannelInboundSubscriber now fires Telegram's typing
indicator as soon as an inbound message is received and refreshes it
every 4 s while the turn is in flight (Telegram's sendChatAction lasts
~5 s). New BackendOAuthClient::send_channel_typing hits
POST /channels/:channel/typing; the subscriber latches
typing_disabled after two failures so channels without the endpoint
stop getting hit.

* fix(providers): fall back to JSON parse when upstream ignores stream=true

Some OpenAI-compatible backends (including our e2e mock) accept
`stream: true` in the request but reply with a regular
`application/json` body rather than an SSE stream. The previous
implementation blindly pushed the body through the SSE line parser,
which produced an empty aggregated response because the body never
contained `\n\n` event separators.

Detect the non-SSE content-type and fall through to the existing
`parse_native_response` path so the caller still gets the aggregated
response. No deltas are emitted in this case — there's nothing to
stream — but correctness is preserved.

Unblocks the json_rpc_protocol_auth_and_agent_hello E2E test.

* Enhance event handling for progressive-edit streaming in ChannelInboundSubscriber

- Introduced a new `StreamingState` struct to manage the state of progressive-edit streaming, allowing for buffered text and tool deltas.
- Implemented a timer-based flushing mechanism for edits, ensuring timely updates to the channel.
- Refactored event handling logic to accommodate new event types (`text_delta`, `tool_call`, `tool_result`) and improved error handling for chat events.
- Updated the logic for finalizing channel replies to handle both streaming and atomic delivery scenarios, enhancing overall responsiveness and user experience.

This commit significantly improves the handling of real-time updates during user interactions, providing a smoother and more interactive experience.

* Enhance tool call tracking in AgentProgress and parsing logic

- Added a `call_id` field to `ToolCallStarted` and `ToolCallCompleted` variants in the `AgentProgress` enum to uniquely identify tool calls and link them to their respective events.
- Updated the `ParsedToolCall` struct to include an optional `id` field for tool calls originating from native responses, ensuring consistent tracking across different call types.
- Modified the `parse_tool_call_value` function to initialize the `id` field appropriately, enhancing the parsing logic for tool calls.
- Adjusted the `run_tool_call_loop` function to utilize the new `call_id` for progress events, ensuring accurate tracking of tool execution across iterations.
- Enhanced the `spawn_progress_bridge` function to handle the new `call_id` field in progress events, improving the overall event handling mechanism.

These changes improve the robustness of tool call tracking and enhance the clarity of event relationships within the agent's progress reporting.

* Enhance tool call tracking and event handling in Conversations component

- Added a `tool_call_id` field to `ChatToolCallEvent` and `ChatToolResultEvent` interfaces for improved tracking of tool calls across events.
- Updated event key generation in the Conversations component to include `tool_call_id`, ensuring unique identification of tool events.
- Enhanced the logic for managing tool timelines to prevent duplication of entries by reconciling existing tool calls based on `tool_call_id`.
- Improved handling of tool argument deltas and results, allowing for more accurate updates to the UI during tool execution.

These changes significantly improve the robustness of tool call tracking and enhance the clarity of event relationships within the Conversations component.

* Enhance tool call event handling and progress reporting in Agent

- Updated the `run_tool_call_loop` function to include stable IDs for tool calls, ensuring unique identification across iterations.
- Introduced early completion events for failed tool calls, improving client-side error handling and user experience.
- Refactored the `emit_progress` function to use asynchronous sending, ensuring lifecycle events are not dropped due to backpressure.
- Enhanced the `turn` method to await progress emissions, improving synchronization during user message processing.

These changes significantly improve the robustness of tool call tracking and enhance the clarity of event relationships within the agent's progress reporting.

* fix(channels): close stuck drafts, prevent duplicate post, add bridge logs

Findings #3, #5, #6 from the follow-up review:

channels/bus.rs
- chat_done handler no longer returns early on empty reply — it now
  finalizes with a "(No response from agent.)" fallback so any draft
  we posted during streaming gets closed off instead of being left
  showing "_working…_" forever.
- StreamingState gained `draft_sent: bool`, set whenever the initial
  send_channel_message succeeds (even when the backend's response
  didn't include an id). finalize_channel_reply now checks this flag
  and silently skips the "no draft → send atomic" fallback when a
  draft was posted but id was lost — fixes a duplicate-bubble bug
  where an id-less draft plus a chat_done finalize produced two
  messages in the user's channel.

channels/providers/web.rs
- spawn_progress_bridge now logs a scoped entry message on startup
  (client_id/thread_id/request_id), a per-variant trace/debug line on
  each AgentProgress event (with call_id/iteration correlation), and
  an exit message with final round + events_seen count. SubagentFailed
  is logged at warn level for visibility.
2026-04-13 20:38:00 -07:00
..