Skip to content

feat(grok): add stream command capturing full SSE response#1848

Draft
Daily-AC wants to merge 1 commit into
jackwener:mainfrom
Daily-AC:feat/grok-stream
Draft

feat(grok): add stream command capturing full SSE response#1848
Daily-AC wants to merge 1 commit into
jackwener:mainfrom
Daily-AC:feat/grok-stream

Conversation

@Daily-AC
Copy link
Copy Markdown
Contributor

@Daily-AC Daily-AC commented Jun 3, 2026

Summary

Adds grok stream — captures grok.com's POST /rest/app-chat/conversations/new SSE response in full and emits one row with response, thinking, model, conversationId, responseId, title, and newline-joined images. Companion / motivation: #1847.

grok ask stays untouched. The new command sits next to it for callers who want the assistant's reasoning trace, server IDs, model id, and generated-image URLs without an extra round-trip through the DOM.

How it works

  1. Installs INSTALL_STREAM_INTERCEPTOR_JS once per tab (idempotent via window.__opencliGrokStreamPatched).
  2. The interceptor wraps window.fetch; for any request hitting /rest/app-chat/conversations/new it response.clone().body.getReader()-drains the SSE chunks into a buffer, then pushes a {url, method, status, body, capturedAt} envelope onto window.__opencliGrokStream.
  3. The Node side reuses the existing ensureOnGrok / isLoggedIn / startNewChat / sendMessage helpers from clis/grok/utils.js to drive the composer, then polls the queue until the envelope shows up (or --timeout seconds elapse).
  4. summarizeResponse walks the newline-delimited JSON frames and reduces them: thinking-tagged tokens → thinking, untagged tokens → response, terminal modelResponse wins over the streamed concatenation, modelResponse.generatedImageUrlsimages.

Not using Strategy.INTERCEPT / page.installInterceptor() here because the upstream interceptor calls await response.clone().json(), which silently drops Grok's newline-delimited JSON. The fetch hook for stream keeps the raw body string and parses per site — same approach we'll need for chatgpt/claude/gemini/deepseek SSE shapes in the follow-up PRs.

Test plan

  • npx vitest run clis/grok/stream.test.js — 8 unit tests on iterFrames + summarizeResponse against a hand-crafted SSE body (final text, thinking trace, IDs, title, image URL list, malformed-line skipping, empty-body safety)
  • npx tsc --noEmit clean
  • npm run test:adapter — 380 files / 3722 tests pass with the new file added
  • npm run build — manifest regenerates, dist/src/main.js exposes opencli grok stream
  • Live E2E against grok.com:
    $ node dist/src/main.js grok stream "say only pong" -f json
    [
      {
        "response": "pong",
        "thinking": "Thinking about your request",
        "model": "grok-3",
        "conversationId": "00c9df44-9f05-4002-8914-fb708d9bacd2",
        "responseId": "dde78767-3bef-4e06-864e-7ae44a566cda",
        "title": "pong",
        "images": ""
      }
    ]
    

Follow-ups

If this lands cleanly I'll open separate PRs for chatgpt stream, claude stream, gemini stream, deepseek stream — each parser shape differs enough to deserve its own diff. Detail of each shape is in the issue.

`grok ask` polls the DOM for the next assistant bubble, which loses the
thinking trace, server-side conversationId/responseId, model id, and
generated image URLs that the SSE response carries.

`grok stream` adds a single-row command that installs a per-tab `window.fetch`
interceptor for `/rest/app-chat/conversations/new`, drains the response stream
through `response.clone().body.getReader()` so SSE chunks are captured even
while grok.com is also reading the body, and emits one structured row with
`response`, `thinking`, `model`, `conversationId`, `responseId`, `title`, and
newline-joined `images`.

The interceptor is idempotent via a `window.__opencliGrokStreamPatched` guard,
falls back to the streamed token concatenation when `modelResponse` is
missing, and skips malformed/keep-alive lines so a single bad chunk never
drops the whole reply.

Unit-tested against a hand-crafted SSE body that mirrors a live capture.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant