Skip to content

Implement useStreaming hook for chat responses #11772

Closed as not planned
Closed as not planned

Description

@randomm

Parent Epic

Part of #11762 (Ink-based TUI Rewrite)

Objective

Create React hook that handles streaming chat responses, managing the connection between user input and assistant output.

Architecture

function useStreaming(sessionId: string | null) {
  const [isStreaming, setIsStreaming] = useState(false)
  const [streamingText, setStreamingText] = useState('')
  const [error, setError] = useState<Error | null>(null)
  
  const sendMessage = useCallback(async (content: string) => {
    if (!sessionId || isStreaming) return
    
    setIsStreaming(true)
    setStreamingText('')
    setError(null)
    
    try {
      const response = await fetch(`http://localhost:${port}/session/${sessionId}/message`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ content })
      })
      
      const reader = response.body?.getReader()
      const decoder = new TextDecoder()
      
      while (true) {
        const { done, value } = await reader.read()
        if (done) break
        
        const chunk = decoder.decode(value)
        // Parse SSE chunks, extract text deltas
        for (const line of chunk.split('\n')) {
          if (line.startsWith('data: ')) {
            const data = JSON.parse(line.slice(6))
            if (data.type === 'text') {
              setStreamingText(prev => prev + data.content)
            }
          }
        }
      }
    } catch (err) {
      setError(err as Error)
    } finally {
      setIsStreaming(false)
    }
  }, [sessionId, isStreaming])
  
  const cancel = useCallback(() => {
    // Send cancel signal
  }, [])
  
  return { isStreaming, streamingText, error, sendMessage, cancel }
}

Integration with State

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState)
  const { isStreaming, streamingText, sendMessage, cancel } = useStreaming(state.session.id)
  
  // SDK events update tools/tasks
  useSDKEvents(state.session.id, dispatch)
  
  // When streaming ends, complete the message
  useEffect(() => {
    if (!isStreaming && streamingText) {
      dispatch({ type: 'MESSAGE_COMPLETE', text: streamingText })
    }
  }, [isStreaming, streamingText])
  
  return (
    <Box flexDirection="column">
      <Static items={state.messages}>...</Static>
      <LiveSection 
        text={streamingText} 
        tools={state.streaming.tools}
        tasks={state.streaming.tasks}
        isStreaming={isStreaming}
      />
      <InputLine onSubmit={sendMessage} onCancel={cancel} disabled={isStreaming} />
    </Box>
  )
}

Tasks

  • Create src/cli/ink/hooks/useStreaming.ts
  • Implement message sending
  • Implement SSE stream parsing
  • Handle text delta accumulation
  • Implement cancel functionality
  • Handle errors gracefully
  • Integrate with markdown rendering

Acceptance Criteria

  • Messages send successfully
  • Streaming text appears incrementally
  • Cancel works during streaming
  • Errors display appropriately
  • Input disabled during streaming

Estimated Effort

1.5 days

Risk

Medium - SSE parsing can be tricky

Dependencies

Activity

  1. added
    opentuiThis relates to changes in v1.0, now that opencode uses opentui
    on Feb 2, 2026
  2. randomm commented on Feb 3, 2026

    @randomm
    Author

    Reopening - Phase 1 & 2 components merged successfully. Proceeding with hook implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

opentuiThis relates to changes in v1.0, now that opencode uses opentui

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions